How exactly does Java-based configuration work in Spring MVC - java

I have no trouble in understanding how XML-based configuration works with Spring MVC. There is a standard file ("web.xml") in a standard directory ("WEB-INF/").
How does a server knows that this class is responsible for front-servlet initialization though?
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootApplicationContextConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebApplicationContextConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Looking at this file, I can easily understand that a server will load the servlet on startup with the highest priority.
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

Since Servlet specification 3.0 you can use web.xml or annotations to configure servlets, and with the annotation based configuration additional mechanisms were included to make web.xml unnecessary. I'm not sure if there are cases where web.xml is still useful, I can't remember touching one in years.
SpringServletContainerInitializer class implements the javax.servlet.ServletContainerInitializer which is the starting point for the servlet container (e.g. Tomcat) to set things up.

Related

#Context not injecting ServletContext in RESTEasy JAX-RS application

I am deploying a JAX-RS application to JBoss EAP 6.2.
I am trying to obtain a ServletContext from inside of a JAX-RS resource class so that I can read some context-param values that I have set in the WEB-INF/web.xml file.
I.e., after I've gotten hold of the ServletContext I was planning to call ServletContext#getInitParam to get the value.
I am using injection to get the ServletContext as advised here.
The relevant portion of my web.xml is:
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>foo.MyApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/jax-rs/*</url-pattern>
</servlet-mapping>
So I am using RESTEasy which is bundled with JBoss.
Class MyApplication is:
public class MyApplication extends Application {
private Set<Object> singletons = new HashSet<>();
public MyApplication() {
singletons.add( new MyResource() );
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
… and finally in class MyResource I have the following:
#Path(...)
public class MyResource {
#Context
ServletContext context;
public MyResource() {
// I understand context is supposed to be null here
}
// ... but it should have been injected by the time we reach the service method.
#Path("/somePath")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response someMethod( ) {
if (context==null) throw new RuntimeException();
...
}
}
The above code always results in the RuntimeException being thrown. I.e. RESTEasy somehow fails to inject the ServletContext. Note that I don't have any other JAX-RS problems. I.e. if I hardcode the context-param values I was hoping to be able to retrieve via `ServletContext#getInitParameter", then the JAX-RS rest functionality works as expected when the WAR is deployed to JBoss.
Experimenting further I discovered that the ServletContextis only injected if I perform the injection at an argument of the service method like this:
#Path("/somePath")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response someMethod(#Context ServletContext servletContext) {
...
}
… however I would prefer not to change the API. Moreover, I would like to perform some costly initialization based on the context-param value once and for all, not on every service method invocation.
My questions are:
why is the injection failing?
I am a little tired of the annotations magic failing at runtime, is there a way to get the ServletContext without using annotations?
Alternatively, is it possible for my MyApplication class to obtain the ServletContext and pass it to the MyResource class as a constructor parameter?
If all else fails I guess I can always read and parse the web.xml file myself using Class#getResourceAsStream ?
Based on the comment by FrAn that links to this answer, this is what I ended up doing:
public class JaxRsApplication extends Application {
private Set<Object> singletons = new HashSet<>();
public JaxRsApplication(#Context ServletContext servletContext) {
Assert.assertNotNull(servletContext);
singletons.add( new UserDatabaseResource(servletContext) );
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
… and then, in the UserDatabaseResource class I have the following:
public UserDatabaseResource(ServletContext servletContext) {
Assert.assertNotNull(servletContext);
...
String jndiNameForDatasource = servletContext.getInitParameter("whatever")) ;
...
}
This works as the UserDatabaseResource class which is my DAL layer is a singleton and I just needed to get the JNDI name of the datasource to use (from the web.xml file). But maybe this approach also works with some minor adjustments for non-singleton classes as well.

Descriptor-less Jersey servlet container run as filter in Servlet 3.x container

Is there a way to run the Jersey servlet container (2.x) descriptor-less as a javax.servlet.Filter in a Servlet 3.x container? I need to serve static resources alongside my services and therefore need to use jersey.config.servlet.filter.forwardOn404 or jersey.config.servlet.filter.staticContentRegex which only work when run as a filter according to Javadoc
The property is only applicable when Jersey servlet container is configured to run as a javax.servlet.Filter, otherwise this property will be ignored.
I'd like to get rid of the web.xml completely
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>My-Webservice</display-name>
<filter>
<filter-name>Jersey Filter</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.foo.webservices.MyApplication</param-value>
</init-param>
</filter>
</web-app>
and have everything in my custom Applicationclass
#ApplicationPath(value = "/")
public class MyApplication extends ResourceConfig
{
public MyApplication()
{
packages("com.foo.webservices.services");
property(ServletProperties.FILTER_FORWARD_ON_404, true);
}
}
The official documentation (https://jersey.java.net/documentation/latest/deployment.html#deployment.servlet.3) doesn't state anything about filters unfortunately.
It's possible, but not gonna be as easy as just setting some config property. It would help if you understand a little about how it actually works. With Servlet 3.x, introduced a ServletContainerInitializer that we can implement to load servlets dynamically (this is discussed further here). Jersey has an implementation that it uses. But it follows the JAX-RS which says that the application should be loaded as a servlet. So Jersey doesn't doesn't offer any way around this.
We could write our own ServletContainerInitializer or we can just tap into Jersey's. Jersey has a SerletContainerProvider we can implement. We would need to register the servlet filter ourselves. The implementation would look something like this
#Override
public void preInit(ServletContext context, Set<Class<?>> classes) throws ServletException {
final Class<? extends Application> applicationCls = getApplicationClass(classes);
if (applicationCls != null) {
final ApplicationPath appPath = applicationCls.getAnnotation(ApplicationPath.class);
if (appPath == null) {
LOGGER.warning("Application class is not annotated with ApplicationPath");
return;
}
final String mapping = createMappingPath(appPath);
addFilter(context, applicationCls, classes, mapping);
// to stop Jersey servlet initializer from trying to register another servlet
classes.remove(applicationCls);
}
}
private static void addFilter(ServletContext context, Class<? extends Application> cls,
Set<Class<?>> classes, String mapping) {
final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(cls, classes);
final ServletContainer filter = new ServletContainer(resourceConfig);
final FilterRegistration.Dynamic registration = context.addFilter(cls.getName(), filter);
registration.addMappingForUrlPatterns(null, true, mapping);
registration.setAsyncSupported(true);
}
Once we have our implementation, we need to create a file
META-INF/services/org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider
Which should be at the root of the class path. The contents of that file should be the fully qualified name of our implementation.
You can see a complete example in this GitHub Repo

Swagger JAX-RS with Jersey 1.19 gives 'Conflicting URI templates' error

I am trying to add Swagger to an existing application that uses Jersey 1.19. For adding Swagger to the application, I have been following this guide: https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-1.X-Project-Setup-1.5.
When I deploy the application on Apache Tomcat, I get the following error:
SEVERE: Conflicting URI templates. The URI template / for root resource class io.swagger.jaxrs.listing.ApiListingResource and the URI template / transform to the same regular expression (/.*)?
The odd thing is that my Jersey servlet is not deployed at the root context, but at the /api/* context as shown in the web.xml file:
<servlet>
<servlet-name>MyApp Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>app.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyApp Service</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
My MyApplication class is defined below:
public class MyApplication extends Application {
private final Set<Object> singletons = new HashSet<Object>();
private final Set<Class<?>> classes = new HashSet<Class<?>>();
public MyApplication() {
MyResource resource= new MyResource();
singletons.addAll(Arrays.asList(resource));
BeanConfig beanConfig = new BeanConfig();
beanConfig.setBasePath("/api");
beanConfig.setResourcePackage(getClass().getPackage().getName());
beanConfig.setTitle("REST API");
beanConfig.setVersion("1.0.0");
beanConfig.setScan(true);
classes.add(io.swagger.jaxrs.listing.ApiListingResource.class);
classes.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> getSingletons() {
return singletons;
}}
I have tried other configurations, such as defining the Swagger servlet in the web.xml file instead of using the BeanConfig, but the same error still occurs. I have gotten Swagger to work this way on a different project that uses Jersey 2, but unfortunately the current project has to remain on Jersey 1.19. Here is the Swagger dependency defined in the pom.xml file:
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey-jaxrs</artifactId>
<version>1.5.0</version>
</dependency>
Any help would be appreciated.
Update 2: Looks like version 1.5.8 of swagger-core fixes that issue. See this commit for details.
Update: Instead of adding Swagger resource as sub-resource it much easier to just override #Path mapping. See Solution 2 for details.
I was facing exactly the same problem. The cause of that is Swagger resource being mapped to root #Path("/") public class ApiListingResource.
Solution 1 - No concurring mappings
One simple and inflexible way around it, is not to define any resource mapping to root path #Path("/").
Solution 2 - Override #Path mapping
2.1 Override Swagger classes
ApiListingResource should get a new #Path mapping
package my.api.package.swagger
import javax.ws.rs.Path;
#Path("swagger")
public class ApiListingResource extends io.swagger.jaxrs.listing.ApiListingResource {}
SwaggerSerializers should get new package
package my.api.package.swagger;
import javax.ws.rs.ext.Provider;
#Provider
public class SwaggerSerializers extends io.swagger.jaxrs.listing.SwaggerSerializers {}
2.2 Configure overriden classes
Add my.api.package.swagger instead of io.swagger.jaxrs.listing in Swagger package config.
Solution 3 - Swagger resource as sub-resource
Other solution is to move Swagger to a different path, allowing your resources to be mapped anywhere you like. To achieve this you need to:
3.1 Remove ApiListingResource from provider classes.
if you subclass Application:
public MyApplication() {
//init BeanConfig
//add your resource classes
//classes.add(io.swagger.jaxrs.listing.ApiListingResource.class);
classes.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
}
if you configure via web.xml using com.sun.jersey.config.property.packages param:
<servlet>
<servlet-name>your-rest-api</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
{your_application_packages},
<!--io.swagger.jaxrs.json,-->
<!--io.swagger.jaxrs.listing-->
</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.classnames</param-name>
<param-value>
io.swagger.jaxrs.listing.SwaggerSerializers,
io.swagger.jaxrs.json.JacksonJsonProvider
</param-value>
</init-param>
</servlet>
BTW, I have noticed that GF 3.1.2.2 with Jersey configured using <filter/> in web.xml does not work with Swagger due to Grizzly related bug.
3.2 Add ApiListingResources as a subresource to one of your resources
#Path("/")
class RootResource {
#Context ServletContext context;
#Path("/swagger")
public ApiListingResource swagger() {
return new ApiListingSubResource(context);
}
}
As ApiListingResource is now not managed by Jersey, it does not get ServletContext injected. To overcome this problem you have to pass it as a constructor parameter, and for that subclass ApiListingResource and provide proper constructor:
// context has 'default' visibility
// so we need to stay in the same package
// to be able to access it
package io.swagger.jaxrs.listing;
import javax.servlet.ServletContext;
public class ApiListingSubResource extends ApiListingResource {
public ApiListingSubResource(ServletContext sc) { this.context = sc; }
}
Now your Swagger descriptors will be under http://hostname/app-path/swagger/swagger.json and you will still be able to use the root resource.
It's a little bit longer way , but works! Hope that helps.

Spring Java Config with Multiple Dispatchers

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;
}
}
It can and should be done using several AbstractAnnotationConfigDispatcherServletInitializer classes, one for each dispatcher. #Serge Ballesta's answer is incorrect on this.
The solution is precisely setting rootConfigClasses to null for the 2nd initializer to prevent ContextLoaderListener setting the root context twice, which is the error you are getting. When loading the 2nd DispatcherServlet, it would look for the root context registered in servletContext, so both dispatcher contexts will finally share the same root context without any issue.
But you have to take care of:
Configuring orders of initializers. If one dispatcher has the default mapping "/", it should be the last one.
2nd and beyond dispatcher initializers to return null in getRootConfigClasses to avoid ContextLoaderListener registering root context twice.
Configuring desired LoadOnStartup order.
This fix is required to question code:
#Order(1)
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
... // This class is ok
}
#Order(2)
public class AnotherWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// All is ok but this
#Override
protected Class<?>[] getRootConfigClasses() {
// Set to null to prevent registering root context again. Let FrameworkServlet load it from servletContext.
return null;
}
#Override
public void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setLoadOnStartup(2);
}
}
Or you can do it everything by hand with a single WebApplicationInitializer as in the answer of #Serge Ballesta.
Some additional notes:
ContextListener is not mandatory, it just initializes the context, this can be either done calling refresh method on context.
If using WebApplicationInitializer class, you can have different ones for each dispatcher, ordered using #Order annotation.
The different dispatcher web contexts don't need to share a root context. This is usual, but you can create completely independent dispatchers with unrelated contexts. For example, if you want to serve a REST api along with static context and want to keep configurations separated.
When having several dispatchers, it's recommendable to configure the RequestMappingHandlerMapping to pass the full URL to controllers for those without default mapping ("/"), otherwise by default it trims the dispatcher mapping part. This will simplify you tests. Spring-boot does this automatically, or if you don't use it, it can be done with a WebMvcConfigurer:
#Configuration
#EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// Configure controller mappings to match with full path
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setAlwaysUseFullPath(true);
configurer.setUrlPathHelper(urlPathHelper);
}
}
If using the abstract initializer class, you can prevent ContextLoaderListener to be registered at all, overwriting registerContextLoaderListener method and registering it by hand in other initializer. Although it's usually worth it to let the 1st initializer to do it. But this can be useful for example if you have 2 dispatchers with different parent contexts and need to avoid registering both of them as root contexts.
Spring security
An important question when having multiple dispatchers is Spring Security configuration. This can be done adding a class extending AbstractSecurityWebApplicationInitializer to your context. It registers a filter called DelegatingFilterProxy after the dispatcher configuration mapped to "/*". This filter looks for a securityFilterChain bean in root context by default. This bean is added to context when using #EnableWebSecurity annotation which is usually located in root context so you can share the security config between different dispatchers. But you can also put security config in one dispatcher context and tell the filter to load it with init-parameter contextAttribute.
You can have one shared security configuration with beans WebSecurityCustomizer & SecurityFilterChain (from Spring Security 5.7) or extending the previous WebSecurityConfigurer class. Or you can have different beans for each dispatcher, configuring several web and http elements.
Or even you can have separated configurations for different dispatchers by registering a filter for each one. Filters must have different names, and name is hardcoded in AbstractSecurityWebApplicationInitializer class (up to spring security 5.7). So you can create:
one of the filters with a standard AbstractSecurityWebApplicationInitializer with last order.
the other one using the onStartup method of you other dispatcher so you can set a different name. Like this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
// Register DelegatingFilterProxy for Spring Security. Filter names cannot repeat.
// It can not be used here AbstractSecurityWebApplicationInitializer because the filter name is hardcoded.
final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";
FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("springSecurityFilterChain2", DelegatingFilterProxy.class);
filterRegistration.addMappingForUrlPatterns(null, false, getServletMappings()[0]);
// Spring security bean is in web app context.
filterRegistration.setInitParameter("contextAttribute", SERVLET_CONTEXT_PREFIX + getServletName());
// TargetBeanName by default is filter name, so change it to Spring Security standard one
filterRegistration.setInitParameter("targetBeanName", AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
}
Additional references:
Understanding Spring Web Contexts

HK2 dependency injection with Jersey 2 and Apache Shiro

I'm creating a rest api using Jersey 2.5.1. I'm using HK2 for dependency injection. Later on I decided to use Apache Shiro for authentication and authorization.
While creating my own custom Shiro Realm I ran into some problems. In my realm I wanted to inject a dependency. However, when I ran my application the dependency was not resolved.
Here is my setup:
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.app.api.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
shiro.ini
[main]
authcBasicRealm = my.app.api.MyCustomRealm
matcher = my.app.api.MyCustomCredentialsMatcher
authcBasicRealm.credentialsMatcher = $matcher
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
[urls]
/** = authcBasic
MyApplication.java
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new ApplicationBinder());
packages(true, "my.app.api");
}
}
ApplicationBinder.java
public class ApplicationBinder extends AbstractBinder {
#Override
protected void configure() {
bind(UserDAO.class).to(new TypeLiteral<Dao<User>>(){});
bind(RealDatasource.class).to(DataSource.class);
}
}
MyCustomRealm.java
public class MyCustomRealm extends JdbcRealm {
#Inject DataSource source;
public MyCustomRealm() {
super();
}
#PostConstruct
private void postConstruct() {
// postConstruct is never executed
setDataSource(source);
}
}
So, the problem is that source is not injected in MyCustomRealm. All other classes that isn't created by Shiro gets its dependencies injected.
Could the problem be that Shiro is creating my CustomRealm via the ini file?
I ran into a similar issue, and, while this is probably no longer an issue for you, I wanted to provide the work-around I used.
The issue is ownership of MyCustomRealm. It is being created by shiro in the org.apache.shiro.web.env.EnvironmentLoaderListener by reading the ini file which is outside the scope of the hk2 provider in the Jersey servlet.
Dependency injection is only done when the object is being provided by hk2's ServiceLocator--shiro has no knowledge of this locator and only constructs an instance of MyCustomRealm with its default constructor.
I worked around this by implementing a org.glassfish.jersey.server.spi.ContainerLifecycleListener that gets a handle to the ServiceLocator and shiro's SecurityManager (through the ServletContext which is registered with the ServiceLocator). It then manually injects the data into the realm created by shiro.
If you're interested, I can post the code as a gist.
One problem I see in MyCustomRealm is that you are expecting DataSource to be filled in at construction time. There are two ways to resolve this issue; one is to use constructor injection and the other is to use a post construct. Here would be using constructor injection:
public class MyCustomRealm extends JdbcRealm {
private final DataSource source;
#Inject
public MyCustomRealm(DataSource source) {
super();
this.source = source;
// source does not get injected
setDataSource(source);
}
}
Here is how you would do it with postConstruct:
public class MyCustomRealm extends JdbcRealm {
#Inject DataSource source;
public MyCustomRealm() {
super();
}
#javax.annotation.PostConstruct
private void postConstruct() {
// source does not get injected
setDataSource(source);
}
}

Categories

Resources