I am inside a RESTEasy PreProcessInterceptor and need to access a parameter I've configured in my WAR's web.xml (either as an init-param in the servlet definition of the RestEasy servlet - as described here - or as a context-param).
How do I achieve that?
Alternatively, is there some other place where I should be configuring parameters that I need to be made available to my interceptors at runtime?
What worked for me is the following. In the past I had my PreProcessorInterceptor defined in the WAR's web.xml as follows:
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>my.package.MyPreProcessorInterceptor</param-value>
</context-param>
I now moved it away from the web.xml and placed it inside my Application as follows:
public class JaxRsApplication extends Application {
private Set<Object> singletons = new HashSet<>();
public JaxRsApplication(#Context ServletContext servletContext) {
Assert.assertNotNull(servletContext);
singletons.add( new MyPreProcessorInterceptor(servletContext) );
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
Related
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.
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
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
I have a singleton spring bean named gameContext, my spring bean definition;
<bean name="gameContext" scope="singleton"
class="tr.com.hevi.game.numblock.core.context.GameContext"/>
I also use this class for session listening, here is my web.xml
<listener>
<listener-class>
tr.com.hevi.game.numblock.core.context.GameContext
</listener-class>
</listener>
The problem is that gameContext is being created twice. one; at the very beginning before the spring context is being loaded, and the second; within the spring context.
I am sure that I do not component-scan more than once.
I understand the reason behind but don't know how to tackle the problem. One possible solution should be adding the listener within the spring context not web.xml, or there might be a proxy object solution.
In your problem, there are 2 objects for spring because you are configuring the listener twice
The first is in the web.xml (outside the spring context)
Within the spring context as a bean.
The easiest way to have only 1 instance is if you are using the Servlet 3.0 specification. Here the ServletContext has a addListener() method make use of the same. Do something like the below:
#Component
public class MyCustomListener implements javax.servlet.http.HttpSessionListener, ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof WebApplicationContext) {
((WebApplicationContext) applicationContext).getServletContext().addListener(this);
} else {
//Either throw an exception or fail gracefully, up to you
throw new RuntimeException("Must be inside a web application context");
}
}
}
The above approach will cause you to create only 1 object of the listener, and have the same object registered as a Servlet listener and spring bean.
I need to start a daemon when I deploy a war. The daemon itself uses objects that should be injected with Spring. I did the following:
In web.xml
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springapp-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>example.AppListener</listener-class>
</listener>
AppListener.java
public class AppListener implements ServletContextListener {
...
#Override
public void contextInitialized(final ServletContextEvent sce) {
log.info("======================= Begin context init =======================");
try {
// final ApplicationContext context = new ClassPathXmlApplicationContext("WEB-INF/springapp-servlet.xml");
final ApplicationContext context = new ClassPathXmlApplicationContext("src/main/webapp/WEB-INF/springapp-servlet.xml");
//final ApplicationContext context = new ClassPathXmlApplicationContext("//Users/.../WEB-INF/springapp-servlet.xml");
final SessionServiceDaemon sessionServiceDaemon = context.getBean(SessionServiceDaemon.class);
sessionServiceDaemon.start();
} catch (final Exception e) {
log.error("Was not able to start daemon",e);
}
}
SessionServiceDaemon.java
#Service
#Singleton
public class SessionServiceDaemon {
private final static Logger log = LoggerFactory.getLogger(SessionServiceDaemon.class);
private final SessionServiceHandler handler;
#Inject
public SessionServiceDaemon(final SessionServiceHandler handler) {
log.info("+++++++++++++++++++++++++++++++ SessionServiceDaemon injected");
this.handler = handler;
}
My springapp-servlet.xml simply has the packages required for the injection:
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
<context:component-scan base-package="example" />
<mvc:annotation-driven />
</beans>
In the startup logs, I see as expected:
+++++++++++++++++++++++++++++++ SessionServiceDaemon injected
followed by
======================= Begin context init =======================
Problem is: I then get an exception that the file does not exist no matter which path I use to point to springapp-servlet.xml:
org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [src/main/webapp/WEB-INF/springapp-servlet.xml]; nested exception is java.io.FileNotFoundException: class path resource [src/main/webapp/WEB-INF/springapp-servlet.xml] cannot be opened because it does not exist
I tried different relative paths and even the absolute path without success. I even edited the code above and added just above my attempt to load the context the following:
try {
log.info(org.apache.commons.io.FileUtils.readFileToString(new File("src/main/webapp/WEB-INF/springapp-servlet.xml")));
} catch (final Exception e) {
log.error("Unable to find file",e);
}
and that printed the content of springapp-servlet.xml just fine.
My 2 questions:
How can I get a "class path resource [src/main/webapp/WEB-INF/springapp-servlet.xml] cannot be opened because it does not exist" when I am able to display the file content using the exact same path from the same method?
Do I have the correct approach anyway for starting a Daemon that has dependencies that are injected?
PS: I use Tomcat.
You are starting two different spring application contexts. The first, the built-in ContextLoaderListener, is likely picking up your springapp-servlet.xml configuration from default locations. (You didn't say if you are specifying a contextConfigLocation.)
In your custom listener, you then construct a new application context using ClassPathXmlApplicationContext with an explicit path. Of the three lines you've shown, only the one with ""WEB-INF/springapp-servlet.xml" looks like a possible candidate for classpath resolution, although it really depends on how you've configured and startup your Tomcat instance. (i.e. What is the classpath from Tomcat's point-of-view?)
Regardless, there are better ways to get the Spring application context in to a servlet/listener. A direct approach is to use the ContextLoaderListener as you have done, but then in your custom servlet/listener, make use of Spring's WebApplicationContextUtils.getWebApplicationContext.
Spring has direct support for servlets as well, including configuration via annotations, the HttpServletBean class, or even using FrameworkServlet directly.