Spring Integration
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Struts 7.4.0 (released 2026-10-02) with its struts2-spring-plugin 7.4.0, Spring
Framework 7.0.9, Apache Tomcat 11.0.26 and Java 21, verified on 2026-10-08 and 2026-10-10. Every example was built as a
WAR, deployed on Tomcat 11 and requested with curl; the plugin jar was also opened with javap to check class,
interface and constant names. Struts creates its own actions, interceptors and results with a class called the
ObjectFactory, and the Spring plugin replaces that factory with one that asks a Spring ApplicationContext first. The
result is that the services an action needs (a catalogue, a repository, a mail sender) can be ordinary Spring beans with
Spring’s scopes, proxies and annotations, while the action classes stay as they were. The running scenario is the
Bookshelf: a BookService that lists titles, an action that prints them, a Spring-managed action, a Spring-managed
interceptor and a session-scoped shelf cart. Related pages of this section are linked where they come up: Testing, Deployment and Production, Other Plugins and Dependency Injection and CDI.
What the Spring Plugin Does
Struts has its own small dependency injection container (the @Inject annotation and the <bean> element, described on
the dependency injection page of this group). That container wires the framework’s own objects. Objects that the
application owns (actions, interceptors, result types, validators, converters) are built by the ObjectFactory, and
the plugin page states the mechanism in one sentence: the Spring plugin works by overriding the Struts ObjectFactory to
enhance the creation of core framework objects. The class attribute of an action, interceptor or result in the Struts
configuration is matched with the id of a Spring bean; if there is no such bean, the class is created as usual and
then autowired by Spring.
The plugin jar contains fifteen classes and a struts-plugin.xml. The ones the application sees are these:
Type (package org.apache.struts2.spring) |
Role |
|---|---|
|
The factory the plugin registers under the name |
|
The base class. |
|
A variant for apps that proxy beans; named here for completeness, not needed for the normal setup |
|
The interceptor named |
|
A Spring web application context that can reload changed classes; development only |
|
The names of the class reloading constants and the code that reads them |
Source: Struts Spring Plugin — Features
The plugin’s own struts-plugin.xml (inside the jar) does four things that matter: it registers the object factory
under the name spring, it sets struts.objectFactory to spring (so adding the jar is enough), it sets
struts.disallowProxyMemberAccess to true and struts.json.result.excludeProxyProperties to true (Spring proxies
must not be reachable from OGNL or serialised by the JSON plugin), and it declares the spring-default package with the
autowiring interceptor.
The Bookshelf Used on This Page
All examples are classes and files of one Maven WAR, built three times with small changes: g11spring (XML Spring
configuration and the default autowire mode), g11springjc (Java configuration, autowire by type) and a few throw-away
variants for the failure cases. Each was deployed under its own context path on Tomcat 11.0.26. The package is
org.example.bookshelf; imports and package lines are left out of the listings. The service is a plain class with a
call counter, so that sharing between requests is visible:
public interface BookService {
List<String> titles();
int lookups();
}
public class InMemoryBookService implements BookService {
private final AtomicInteger lookups = new AtomicInteger();
public List<String> titles() {
lookups.incrementAndGet();
return List.of("Dune", "Neuromancer");
}
public int lookups() {
return lookups.get();
}
}
The Getting Started tutorial motivates the same split: an action that does new EditServiceInMemory() hard-codes its
dependency, cannot be tested with a stub and has to be edited everywhere the service is replaced; an action with only a
field and a setter can be given any implementation at run time.
Installation: Jar, Listener and Application Context
Three things are needed, and the plugin page lists the same ones: the plugin jar, the Spring listener in web.xml and
the bean definitions. With Maven the plugin is managed by the struts2-bom that every page of this section imports, and
it pulls the Spring jars it needs as transitive dependencies (spring-beans, spring-core, spring-context,
spring-web; spring-aop, spring-aspects, spring-context-support and commons-jci-fam are declared optional).
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>7.0.9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-bom</artifactId>
<version>7.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
</dependency>
</dependencies>
Importing the Spring BOM is what moves the Spring jars to a newer version than the one the plugin was built with. Struts 7.4.0 itself is built against Spring 6.2.19; the example WAR ran on Spring Framework 7.0.9
(spring-core-7.0.9.jar and friends in WEB-INF/lib) on Tomcat 11 without any other change, and the Getting Started page
gives the same advice in other words: exclude the transitive Spring dependencies and declare the version you want. Spring
7 keeps a Java 17 baseline and raises the servlet baseline to Servlet 6.1 (Tomcat 11, Jetty 12.1) according to its 7.0
release notes, so Spring 7 with Struts 7 means Tomcat 11 or Jetty 12.1; on Tomcat 10.1 stay on Spring 6.2, which is the
line the plugin is built against.
The listener goes in web.xml, before or after the Struts filter (the filter looks the context up when it starts, and
listeners are initialised before filters, so the order of the elements does not matter):
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
By default ContextLoaderListener reads /WEB-INF/applicationContext.xml. The service is one bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookService" class="org.example.bookshelf.InMemoryBookService"/>
</beans>
The context parameter contextConfigLocation changes where Spring looks. The plugin page shows a pattern that loads
every applicationContext-*.xml from WEB-INF and from the class path:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-*.xml,classpath*:applicationContext-*.xml</param-value>
</context-param>
The First Spring-Injected Action
The action is an ordinary Struts action with a field and a JavaBeans setter. There is no @Autowired and nothing in
struts.xml mentions Spring:
public class BookAction extends ActionSupport {
private BookService bookService;
private String message;
public void setBookService(BookService bookService) {
this.bookService = bookService;
}
@Override
public String execute() {
message = "titles=" + bookService.titles()
+ " lookups=" + bookService.lookups()
+ " service=" + System.identityHashCode(bookService)
+ " action=" + System.identityHashCode(this);
return SUCCESS;
}
public String getMessage() {
return message;
}
}
<action name="books" class="org.example.bookshelf.BookAction">
<result>/WEB-INF/views/result.jsp</result>
</action>
Two requests to /books print (the numbers are identity hash codes and differ on every run):
curl http://localhost:18080/g11spring/books
curl http://localhost:18080/g11spring/books
titles=[Dune, Neuromancer] lookups=1 service=2065885581 action=1709014946
titles=[Dune, Neuromancer] lookups=2 service=2065885581 action=1922376303
The service number is the same in both lines and lookups grows: Spring’s default scope is singleton, so one
InMemoryBookService serves every request. The action number differs: Struts still creates a new action instance per
request (see the thread-per-request section of the actions page), and Spring only filled the bookService property. The
setter name is what connected them: the default autowire mode is name, which matches the bean id bookService with
the property bookService.
Autowire Modes
The constant struts.objectFactory.spring.autoWire selects how a Struts-created object is autowired. The plugin page
and the 7.4.0 default.properties agree on the default, name.
| Value | Meaning on the plugin page |
|---|---|
|
Match the name of the Spring bean with the name of the property. The default |
|
Look for a bean of the same type as the property; there must be exactly one bean of that type |
|
Spring tries to detect the best method (the |
|
Autowire the parameters of the constructor |
|
Turn off externally defined autowiring; annotation-driven injection and the Spring |
The difference shows with a property whose name is not a bean id. The action below has a setCatalog(BookService)
setter, but the only bean is bookService:
public class CatalogAction extends ActionSupport {
private BookService catalog;
public void setCatalog(BookService catalog) {
this.catalog = catalog;
}
public String getMessage() {
return "catalog=" + (catalog == null ? "null" : catalog.titles());
}
}
With the default name mode, nothing matches and nothing fails:
curl http://localhost:18080/g11spring/catalog
catalog=null
One line in struts.xml (a <constant> element can also be a struts.properties entry) switches the application to
autowiring by type:
<constant name="struts.objectFactory.spring.autoWire" value="type"/>
and the same action, in the g11springjc WAR, now finds the only BookService bean:
catalog=[Dune, Neuromancer]
Source: Struts Spring Plugin — Settings
The name mode is the most predictable for teams that keep property names and bean ids aligned; the type mode removes the naming convention but fails as soon as a second bean of the same type is added, which is why the plugin page says it "requires you to have only one bean of this type registered".
The other settings of the plugin:
| Constant | Default | Meaning |
|---|---|---|
|
|
The strategy above |
|
|
Apply the configured strategy consistently; the plugin page says the old |
|
|
Let Spring cache classes; the |
|
|
Deprecated switch to an older bean construction logic for AOP; try it only if Spring beans and AOP misbehave |
|
empty, empty, |
Class reloading (see below); the plugin page names these |
Letting Spring Create the Action
The first example left the creation of the action to Struts and let Spring inject the properties. The plugin page calls the
alternative "initializing actions from Spring": declare the action as a Spring bean and put the bean id in the class
attribute of the mapping. This is useful for constructor injection, for AOP (transactions, security annotations) that
needs a Spring proxy, and for anything else that depends on Spring creating the instance. The page’s recommendation is
to prefer the declarative way (name or type autowiring, annotations) and keep the Spring XML for the cases that need it,
so that configuration is not maintained twice.
public class ManagedBookAction extends ActionSupport {
private final BookService bookService;
private String message;
public ManagedBookAction(BookService bookService) {
this.bookService = bookService;
}
@Override
public String execute() {
message = "managed titles=" + bookService.titles() + " action=" + System.identityHashCode(this);
return SUCCESS;
}
public String getMessage() {
return message;
}
}
<bean id="managedBookAction" class="org.example.bookshelf.ManagedBookAction" scope="prototype">
<constructor-arg ref="bookService"/>
</bean>
<action name="managed" class="managedBookAction">
<result>/WEB-INF/views/result.jsp</result>
</action>
curl http://localhost:18080/g11spring/managed
managed titles=[Dune, Neuromancer] action=277069676
The scope matters. A Struts action holds request data in its fields, so it must be created for every request. Spring’s
default scope is singleton; the Getting Started page says it plainly: actions must be created on each request, they
cannot be singletons, which is why the bean is declared scope="prototype". A singleton action would share its form
fields between users, the same defect as a static field on the actions page. The plugin page adds that other scopes
(session, application or a custom scope) are possible; that turns an action into a long-lived object and is almost never
what a form action wants.
A second way to reach a Spring bean from a Struts mapping, listed in the 7.4.0 Version Notes, is a bean whose id is the
fully qualified class name of the action. The mapping keeps class="org.example.bookshelf.CatalogAction", but the
object factory finds a bean with that exact id and uses it. The failing catalog example above becomes:
<bean id="org.example.bookshelf.CatalogAction" class="org.example.bookshelf.CatalogAction" scope="prototype">
<property name="catalog" ref="bookService"/>
</bean>
catalog=[Dune, Neuromancer]
(verified on the g11spring WAR with the default name mode, where the same action without the bean printed
catalog=null).
Annotation-Driven Injection
Because a Struts-created action is autowired by Spring’s AutowireCapableBeanFactory, Spring’s annotation processing
applies to it, so a field can be marked @Autowired and needs no setter. The g11springjc WAR contains
public class AnnotatedBookAction extends ActionSupport {
@Autowired
private BookService books;
public String getMessage() {
return "annotated titles=" + books.titles();
}
}
<action name="annotated" class="org.example.bookshelf.AnnotatedBookAction">
<result>/WEB-INF/views/result.jsp</result>
</action>
annotated titles=[Dune, Neuromancer]
The name of the field (books) differs from the bean id, and the lookup by type of @Autowired is what made it work;
the autowire constant was type in that WAR. The no mode of the plugin page is the one that leaves the XML-style
autowiring off and relies on annotations alone. Field injection hides the dependency from the constructor, which is the
usual argument for the setter and constructor styles above in code that has to be unit-tested without Spring.
Spring Framework 7 Java Configuration
The XML file is optional. Spring’s web context can be told to read annotated classes with two context parameters, and
Struts only sees the resulting ApplicationContext:
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.example.bookshelf.AppConfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
@Configuration
public class AppConfig {
@Bean
public BookService bookService() {
return new InMemoryBookService();
}
@Bean
@Scope("prototype")
public ManagedBookAction managedBookAction(BookService bookService) {
return new ManagedBookAction(bookService);
}
@Bean
public AuditInterceptor auditInterceptor(BookService bookService) {
return new AuditInterceptor(bookService);
}
}
All five mappings of the g11springjc WAR (books, managed, audited, catalog, annotated) answered 200 with the
expected output on this configuration, with Spring Framework 7.0.9 on Tomcat 11. The @Bean method name is the bean id,
which is therefore what the class attribute of the Struts mapping uses (managedBookAction). A @ComponentScan with
@Component and @Scope("prototype") on the action class works the same way. The WebApplicationInitializer route
(registering the context from Java, with no web.xml listener) is also a Spring feature that ends in the same servlet
context attribute; it is not covered by the Struts documentation and was not tested for this page.
Interceptors and Other Objects from Spring
Actions are not the only objects the class attribute names. The plugin page lists actions, interceptors and results
among the objects that can be created by Spring. An interceptor bean can receive collaborators by constructor injection
and be referenced from the stack by its bean id:
public class AuditInterceptor extends AbstractInterceptor {
private final BookService bookService;
public AuditInterceptor(BookService bookService) {
this.bookService = bookService;
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("AUDIT before " + invocation.getProxy().getActionName()
+ " lookups=" + bookService.lookups());
try {
return invocation.invoke();
} finally {
System.out.println("AUDIT after " + invocation.getProxy().getActionName()
+ " lookups=" + bookService.lookups());
}
}
}
<bean id="auditInterceptor" class="org.example.bookshelf.AuditInterceptor">
<constructor-arg ref="bookService"/>
</bean>
<interceptors>
<interceptor name="audit" class="auditInterceptor"/>
<interceptor-stack name="auditedStack">
<interceptor-ref name="audit"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<action name="audited" class="org.example.bookshelf.BookAction">
<interceptor-ref name="auditedStack"/>
<result>/WEB-INF/views/result.jsp</result>
</action>
curl http://localhost:18080/g11spring/audited
titles=[Dune, Neuromancer] lookups=4 service=2065885581 action=439060776
and the servlet container’s log shows the two lines around the action (the lookups value was 3 before and 4 after, the
shared singleton having served books twice and managed once earlier):
AUDIT before audited lookups=3
AUDIT after audited lookups=4
Source: Struts Spring Plugin — Features
An interceptor is a singleton shared by all requests (see the interceptors page and the custom interceptors page), so a
Spring-managed interceptor bean must stay a singleton too and must not hold request state; Spring only saves the
new and the wiring code.
Scoped Beans: Session and Request
Spring’s other scopes (request, session, application) are available for the beans actions use. A cart that lives as
long as the HTTP session is the Bookshelf example. A session-scoped bean cannot be injected into an action without
help, because the action is built for one request while the bean belongs to the session: the injected value must be a
scoped proxy, which resolves the real bean of the current session on each call.
<bean id="shelfCart" class="org.example.bookshelf.ShelfCart" scope="session">
<aop:scoped-proxy/>
</bean>
public class ShelfCart {
private final List<String> titles = new ArrayList<>();
public int add(String title) {
titles.add(title);
return titles.size();
}
}
public class CartAction extends ActionSupport {
private ShelfCart shelfCart;
private String message;
public void setShelfCart(ShelfCart shelfCart) {
this.shelfCart = shelfCart;
}
@Override
public String execute() {
message = "cart size=" + shelfCart.add("Dune") + " class=" + shelfCart.getClass().getSimpleName();
return SUCCESS;
}
public String getMessage() {
return message;
}
}
The aop namespace comes from spring-aop, which the plugin declares optional but which was present in the WAR. The
scope also needs a way to find the current request, which the Struts filter does not provide, so web.xml gets the
second Spring listener:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
Three requests with one cookie jar and one with a fresh jar:
curl -c jar.txt -b jar.txt http://localhost:18080/g11spring/cart
curl -c jar.txt -b jar.txt http://localhost:18080/g11spring/cart
curl -c jar.txt -b jar.txt http://localhost:18080/g11spring/cart
curl -c other.txt -b other.txt http://localhost:18080/g11spring/cart
cart size=1 class=ShelfCart$$SpringCGLIB$$0
cart size=2 class=ShelfCart$$SpringCGLIB$$0
cart size=3 class=ShelfCart$$SpringCGLIB$$0
cart size=1 class=ShelfCart$$SpringCGLIB$$0
The injected object is a CGLIB proxy (ShelfCartSpringCGLIB0), each session has its own cart and the plugin’s
struts.disallowProxyMemberAccess constant keeps OGNL away from the proxy internals. Without the
RequestContextListener the same request answers 500 with Spring’s ScopeNotActiveException: Scope 'session' is not
active for the current thread, followed by the hint that the code is running outside DispatcherServlet and needs
RequestContextListener or RequestContextFilter. In an application that also stores the cart in the Struts session
map, the Spring bean replaces that map; the sessions page of this section explains the Struts side.
Source: Spring Framework — Session Scope
The plugin page’s remark that Spring 2’s scope feature can scope an action to the session, the application or a custom scope is the same mechanism applied to the action itself.
Reaching the ApplicationContext
An action that implements Spring’s ApplicationContextAware receives the context after creation, because
SpringObjectFactory injects it (the class has a private step for this, and implements the interface itself). This is a
diagnostic and an escape hatch, not a style to copy: it ties the action to Spring.
public class ContextAction extends ActionSupport implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
public String getMessage() {
return context.getClass().getSimpleName() + " " + String.join(",", context.getBeanDefinitionNames());
}
}
XmlWebApplicationContext bookService,managedBookAction,auditInterceptor,scopedTarget.shelfCart,shelfCart
The list is exactly the beans of the XML file, plus Spring’s scopedTarget. bean that stands behind the cart proxy.
The Autowiring Interceptor
The plugin page says it provides two interceptors that autowire actions "if not using the Spring ObjectFactory"; in 7.4.0
the jar contains one, ActionAutowiringInterceptor, registered as autowiring in the package spring-default. It is for
applications that deliberately run another object factory (the page’s example is a second IoC plugin) and still want Spring
to wire the action. The application below keeps the ContextLoaderListener, sets struts.objectFactory back to the
default and extends both packages:
<constant name="struts.objectFactory" value="struts"/>
<package name="default" extends="struts-default,spring-default" namespace="/">
<action name="books" class="org.example.bookshelf.BookAction">
<interceptor-ref name="autowiring"/>
<interceptor-ref name="defaultStack"/>
<result>/WEB-INF/views/result.jsp</result>
</action>
<action name="plain" class="org.example.bookshelf.BookAction">
<result>/WEB-INF/views/result.jsp</result>
</action>
</package>
/books printed titles=[Dune, Neuromancer] lookups=1 ...: the interceptor wired bookService into an action that
Struts itself had created. /plain, with the same class and no interceptor, answered 500 with a
NullPointerException in BookAction.execute, because nothing wired it. Its setAutowireStrategy(Integer) setter lets a
mapping choose a strategy for that action. For normal applications the Spring object factory is simpler and covers
every action at once.
Source: Struts Spring Plugin — Features
Transactions, Security and AOP
The plugin page suggests using JDK 5 annotations for transaction and security requirements instead of explicit proxies in
the Spring XML, and mentions that a bean fully managed by Spring is the way to apply "more complex AOP or Spring-enabled
technologies" to an action. In the Bookshelf that means @Transactional on a service method (the service is a Spring
bean, so the proxy is normal Spring) rather than on the action. Two details of the Struts side are worth knowing; both
come from the 7.x Version Notes rather than from an experiment on this page. WW-5534 (fixed in 7.1.1) concerns actions
with Spring’s @Transactional combined with ModelDriven, which is the case where the Struts model-driven machinery
meets a proxied action; the model-driven page of this section explains the interface. WW-5717 (in 7.4.0) updates the
Spring Framework dependency after an OWASP dependency-check finding in spring-core. The legacy switch
struts.objectFactory.spring.enableAopSupport is deprecated and false by default.
Class Reloading
The plugin can reload changed classes without restarting the container. It is documented as experimental and never for
production. The steps on the plugin page are: set struts.devMode to true, list the directories or jars to watch, add
a context parameter that switches the Spring web context class, and put commons-jci-fam 1.0 on the class path.
<context-param>
<param-name>contextClass</param-name>
<param-value>org.apache.struts2.spring.ClassReloadingXMLWebApplicationContext</param-value>
</context-param>
The reloading class loader is also the source of the plugin page’s warning: instances of the same class loaded by two
class loaders cannot be assigned to each other, so reloading everything produces ClassCastException`s; limiting the
reloaded classes with a regular expression, so that only actions are handled, avoids it. One more point from the check
of the 7.4.0 jar: the plugin page names the constants `struts.class.reloading.watchList, ...acceptClasses and
...reloadConfig, but SpringConstants, struts-plugin.xml and StrutsSpringObjectFactory of 7.4.0 use
struts.objectFactory.spring.class.reloading.watchList, ...acceptClasses and ...reloadConfig. Class reloading was
not run for this page (it needs the experimental JCI jar and a watched directory); use the names in the jar and check the
log when trying it.
Source: Struts Spring Plugin — Settings
Spring Boot and Struts
There is no official Spring Boot support for Struts. The pages of the Struts documentation checked on 2026-10-08 (the
plugins index, Getting Started, the Core Developer Guide and the security pages) contain no Spring Boot guide, plugin or
starter, and the Struts examples repository has no Spring Boot module (it has a Quarkus one). The plugin works with a
WebApplicationContext in the servlet context, which is what the classic ContextLoaderListener provides; an
embedded Boot application is a different way of building that context. Two routes are commonly described in community answers: a Boot WAR
that is deployed to an external Tomcat (SpringBootServletInitializer) with the Struts filter registered through a
FilterRegistrationBean, and a fully classic WAR that does not use Boot. Neither route is documented by Struts, and the
first was not tested on this page, so treat it as unsupported and test it yourself before depending on it.
Two further facts shape the decision. Spring Boot’s own reference says JSPs are not supported in an executable jar and recommends avoiding them when possible (the JSP Limitations section; see Spring MVC with JSP), and Struts views in 7.x are JSP, FreeMarker or Velocity files, so a Struts application on Boot is in the same position as a JSP one. A team that wants Boot’s packaging, configuration and ecosystem for new work is better served by Spring MVC (see Web UI frameworks on Spring Boot and, for Spring fundamentals, Spring Boot core concepts). For the services behind a Struts application the Spring plugin is the integration; for the front end, moving to Spring MVC is the modernisation path that a later page of this section describes.
The Official spring-struts Example
The Getting Started page points to spring-struts in the struts-examples repository. It is the Themes tutorial’s
EditAction with its EditService dependency moved to Spring: an EditServiceInMemory bean named editService, a
setter in EditAction, the ContextLoaderListener in web.xml and the alternative mapping in which Spring creates the
action. It is the same two options as the books and managed mappings above, with other names. Read the
tutorial for the full source; this page shows the Bookshelf versions instead.
Pitfalls
| Symptom or error | Cause | Fix |
|---|---|---|
The application does not start; the log says "FATAL ERROR STARTING UP STRUTS-SPRING INTEGRATION" and the Struts filter fails with |
|
Add the listener (the log message prints the XML to add) |
A property of the action is |
The autowire mode is |
Rename the property, switch to |
|
Mode |
Keep one bean per type, mark one |
|
A request or session scoped bean is used and no |
Add the listener to |
A session-scoped bean cannot be injected into the action |
The bean has no scoped proxy |
Declare |
Two users see each other’s form data |
The action bean has Spring’s default singleton scope |
|
|
The plugin page’s XML example uses the removed |
Use |
|
Classes from two class loaders meet |
Limit the reloading class loader to the action classes with the accept pattern |
Reloading constants have no effect |
The constants named on the plugin page are not the ones the jar reads |
Use |
The Spring jars are an older version than expected |
The plugin pulls the version it was built with (6.2.19 for 7.4.0) unless the application manages it |
Import |
An OGNL expression or the JSON result touches Spring proxy internals |
A proxied bean exposes proxy properties |
The plugin already sets |
The service works on one request but changes between requests |
A prototype or request scoped service bean is used where a singleton was meant |
Make shared services singletons; prototypes only for per-request holders |
What Changed Recently
| Release | Change |
|---|---|
Struts 7.0 |
The Jakarta namespace and the |
Struts 7.1.1 |
WW-5534: actions with Spring’s |
Struts 7.2.0 |
|
Struts 7.2.1 |
WW-4291: a Spring bean name can be used for a type converter; WW-3647: a |
Struts 7.4.0 |
WW-5717: Spring Framework dependency updated after an OWASP finding; WW-3257 closed with the note that a bean registered under the fully qualified class name is picked up. The plugin is built against Spring 6.2.19 and verified here on Spring 7.0.9 |
Spring Framework 7 |
Jakarta EE 11 and Servlet 6.1 baseline, JDK 17 baseline; |
Source: Struts 7.4.0 Version Notes
Source: Struts 7.2.1 Version Notes
Source: Struts 7.1.1 Version Notes
Where the Books Are Outdated
The Spring chapter of Struts 2 in Action was written for Struts 2.0 and Spring 2. These differences matter today:
| In the book | Today |
|---|---|
Spring 2 XML with a DTD header and the |
XSD headers and |
|
|
Autowire by name as the only default, with |
Default |
JPA with Hibernate and |
Spring Framework 7 with Spring Data JPA or Hibernate; for an open-session-per-request pattern read the Spring and Hibernate pages of this site (see Hibernate and Spring Data JPA); filter-based open session in view is a Spring MVC topic and is usually avoided |
Spring 2 annotations such as |
Annotation-driven configuration and Java |
Struts 2.0 and |
|
JSP only (Spring tags and form tags) for views |
JSP, FreeMarker and Velocity; for JSP with Spring MVC see the Spring MVC with JSP page |
Inconsistencies in the Official Documentation
-
The plugin page’s XML example (
singleton="false", a-//SPRING//DTD BEAN//ENheader) does not load on Spring 7.0.9; usescope="prototype". -
The plugin page’s class reloading constants (
struts.class.reloading.*) are not the ones the 7.4.0 jar reads (struts.objectFactory.spring.class.reloading.*). -
The plugin page says
struts.objectFactoryneeds to be set only when several object factories are present; the plugin’s ownstruts-plugin.xmlalready sets it tospring. -
The plugin page’s text refers to two autowiring interceptors; the 7.4.0 jar has one.
-
Typing slips remain on the page (
applicationConext.xml,ransactional), and the Getting Started page refers to "section 3.5" of the Spring documentation, which no longer exists as a numbered section.
Summary
To use Spring with Struts 7.4.0, add struts2-spring-plugin, add ContextLoaderListener (plus
RequestContextListener for request and session scopes), define the services as Spring beans in XML or Java
configuration and give the actions setters whose names match the bean ids (or switch to autowire by type, or annotate).
Let Spring create the action itself only when it needs a Spring proxy or constructor injection, and then make the bean
prototype. Spring Framework 7.0.9 works with the 7.4.0 plugin, class reloading is experimental and its constants are
named differently in the jar, and Spring Boot is not officially supported.
References
-
Apache Struts — Spring Plugin, Initializing Actions from Spring
-
Apache Struts — Spring Plugin, Migration Note: autowire alwaysRespect default change (7.2.0)
-
Apache Struts — Getting Started, Spring and Struts, Introduction
-
Apache Struts — Getting Started, Alternative: Have Spring Manage Creation Of ActionSupport Class
-
Spring Framework Javadoc — AnnotationConfigWebApplicationContext
-
Spring Framework — Servlet Config (Code-based Servlet Container Initialization)
-
Maven Central — org.apache.struts:struts2-spring-plugin 7.4.0 (jar opened with javap)