I've some experience Spring now and also have some pure java config web-apps in use. However, these are usually based on a quiet simple setup:
- application config for services / repositories
- dispatcher config for one dispatcher (and some controllers)
- (optional) spring security to secure the access
For my current project I need to have separate dispatcher contexts with different configuration. That's not a problem with the XML based configuration as we have a dedicated ContextLoaderListener that's independent from Dispatcher Configuration. But with java config I'm not sure if what I'm doing is fine so far ;)
Here's a common DispatcherConfig:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new class[]{MyAppConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MyDispatcherConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/mymapping/*"};
}
@Override
protected String getServletName() {
return "myservlet";
}
}
As said, I need a second (third, ...) dispatcher with another mapping (and view resolvers). So, I copied the config and added for both getServletName() (otherwise both will be named as 'dispatcher' which will cause errors). The second config was looking like that:
public class AnotherWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new class[]{MyAppConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{AnotherDispatcherConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/another_mapping/*"};
}
@Override
protected String getServletName() {
return "anotherservlet";
}
}
When I use it like this, starting application results in a problem with ContextLoaderListener:
java.lang.IllegalStateException: Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:277)
...
So I removed the second MyAppConfig.class return from one of the
AbstractAnnotationConfigDispatcherServletInitializer and it works fine. However, that doesn't feel to be the right way ;)
For my understanding: should all DispatcherConfig be handled within one AbstractAnnotationConfigDispatcherServletInitializer or should I separate them as I did? I tried to configure them in one class but then my config was totally mixed (so I believe that's not the desired way).
How do you implement such a case? Is it possible to set the ContextLoaderListener in java config outside of the AbstractAnnotationConfigDispatcherServletInitializer? Or should I create a DefaultServlet which has only the root config? What about implementing the base interface of that configuration WebApplicationInitializer?
Mahesh C. showed the right path, but his implementation is too limited. He is right on one point : you cannot use directly AbstractAnnotationConfigDispatcherServletInitializer
for multiple dispatcher servlet. But the implementation should :
- create a root application context
- gives it an initial configuration and say what packages it should scan
- add a ContextListener for it to the servlet context
- then for each dispatcher servlet
- create a child application context
- gives it the same an initial configuration and packages to scan
- create a DispatcherServlet using the context
- add it to the servlet context
Here is a more complete implementation :
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// root context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class); // configuration class for root context
rootContext.scan("...service", "...dao"); // scan only some packages
servletContext.addListener(new ContextLoaderListener(rootContext));
// dispatcher servlet 1
AnnotationConfigWebApplicationContext webContext1 =
new AnnotationConfigWebApplicationContext();
webContext1.setParent(rootContext);
webContext1.register(WebConfig1.class); // configuration class for servlet 1
webContext1.scan("...web1"); // scan some other packages
ServletRegistration.Dynamic dispatcher1 =
servletContext.addServlet("dispatcher1", new DispatcherServlet(webContext1));
dispatcher1.setLoadOnStartup(1);
dispatcher1.addMapping("/subcontext1");
// dispatcher servlet 2
...
}
That way, you have full control on which beans will end in which context, exactly as you would have with XML configuration.
I think you can work it out if you use generic WebApplicationInitializer interface rather than using abstract implementation provided by spring - AbstractAnnotationConfigDispatcherServletInitializer.
That way, you could create two separate initializers, so you would get different ServletContext on startUp() method and register different AppConfig & dispatcher servlets for each of them.
One of such implementing class may look like this:
public class FirstAppInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(container);
ServletRegistration.Dynamic servlet = container.addServlet(
"dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/control");
}
}
I faced the same issue. Actually I had a complex configuration with multiple dispatcher servlets, filters and listeners.
I had a web.xml like below
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>
<listener-class>MyAppContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>${config.environment}</param-value>
</context-param>
<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>MyAppConfig</param-value>
</context-param>
<servlet>
<servlet-name>restEntryPoint</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>MyRestConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restEntryPoint</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>webSocketEntryPoint</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>MyWebSocketWebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>webSocketEntryPoint</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>webEntryPoint</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>MyWebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>webEntryPoint</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>exceptionHandlerFilter</filter-name>
<filter-class>com.san.common.filter.ExceptionHandlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionHandlerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>validationFilter</filter-name>
<filter-class>MyValidationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>validationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>lastFilter</filter-name>
<filter-class>MyLastFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>lastFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
I replaced above web.xml with below java file
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(MyAppContextLoaderListener.class);
servletContext.setInitParameter("spring.profiles.active", "dev");
servletContext.setInitParameter("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
servletContext.setInitParameter("contextConfigLocation", "MyAppConfig");
// dispatcher servlet for restEntryPoint
AnnotationConfigWebApplicationContext restContext = new AnnotationConfigWebApplicationContext();
restContext.register(MyRestConfig.class);
ServletRegistration.Dynamic restEntryPoint = servletContext.addServlet("restEntryPoint", new DispatcherServlet(restContext));
restEntryPoint.setLoadOnStartup(1);
restEntryPoint.addMapping("/api/*");
// dispatcher servlet for webSocketEntryPoint
AnnotationConfigWebApplicationContext webSocketContext = new AnnotationConfigWebApplicationContext();
webSocketContext.register(MyWebSocketWebConfig.class);
ServletRegistration.Dynamic webSocketEntryPoint = servletContext.addServlet("webSocketEntryPoint", new DispatcherServlet(webSocketContext));
webSocketEntryPoint.setLoadOnStartup(1);
webSocketEntryPoint.addMapping("/ws/*");
// dispatcher servlet for webEntryPoint
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(MyWebConfig.class);
ServletRegistration.Dynamic webEntryPoint = servletContext.addServlet("webEntryPoint", new DispatcherServlet(webContext));
webEntryPoint.setLoadOnStartup(1);
webEntryPoint.addMapping("/");
FilterRegistration.Dynamic validationFilter = servletContext.addFilter("validationFilter", new MyValidationFilter());
validationFilter.addMappingForUrlPatterns(null, false, "/*");
FilterRegistration.Dynamic lastFilter = servletContext.addFilter("lastFilter", new MyLastFilter());
lastFilter.addMappingForUrlPatterns(null, false, "/*");
}
@Override
protected Class<?>[] getRootConfigClasses() {
// return new Class<?>[] { AppConfig.class };
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return null;
}
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return null;
}
}