Spring can't see beans between servlet-context and contextConfigLocation beans - java

I have a spring mvc project set up like so:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-contexts/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-contexts/configuration-context.xml</param-value>
</context-param>
It appears if I make a bean in configuration-context.xml and reference a bean in servlet-context.xml it cannot find it. Are these created as two different contexts? Why does this happen / work like this in general?

Yes there are two contexts stacked on each other (parent and child context).
The beans from the DispatcherServlet (servlet-context.xml) can access the beans from the ContextLoaderListener (configuration-context.xml), but not the other way around.
So put the basic stuff in the configuration-context.xml and the web related once into servlet-context.xml.
#See also this Stack Overflow question: ContextLoaderListener or not?

Spring container can definitely see the components decided by component scan base package of context and you can get the bean from the context.
There are two types of context in spring
1. Root context (ApplicationContext)
2. Servlet context (WebApplicationContext)
Visiblity of beans defined in rootContext to servletContext - YES
Beans defined in root context is always visible in all servlet contexts by default. for example dataSource bean defined in root context can be accessed in servlet context as given below.
#Configuration
public class RootConfiguration
{
#Bean
public DataSource dataSource()
{
...
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.pvn.mvctiles")
public class ServletConfiguration implements WebMvcConfigurer
{
#Autowired
private DataSource dataSource;
...
}
Visiblity of beans defined in servletContext to rootContext - yes*
(Why * in Yes)
1. Initialization of context order is rootContext first and servletContext next.
In the root context configuration class/xml if you try to get the bean defined in servletContext you will get NULL. (because servletContext is not initialized yet, hence we can say beans not visible while initialization of rootContext)
But you can get beans defined in servletContext after initialization of servletContext(you can get beans through application context)
you can print and confirm it by
applicationContext.getBeanDefinitionNames();
2. If you want to access beans of servlet context in the filter or in the another servlet context, add "org.springframework.web.servlet" base package to your root config class/xml
#Configuration
#ComponentScan(basePackages = "org.springframework.web.servlet" )
public class RootConfiguration
after adding you can get all below beans from application context
springSecurityConfig, tilesConfigurer, themeSource, themeResolver, messageSource, localeResolver, requestMappingHandlerMapping, mvcPathMatcher, mvcUrlPathHelper, mvcContentNegotiationManager, viewControllerHandlerMapping, beanNameHandlerMapping, resourceHandlerMapping, mvcResourceUrlProvider, defaultServletHandlerMapping, requestMappingHandlerAdapter, mvcConversionService, mvcValidator, mvcUriComponentsContributor, httpRequestHandlerAdapter, simpleControllerHandlerAdapter, handlerExceptionResolver, mvcViewResolver, mvcHandlerMappingIntrospector
If you want to get your custom beans from rootContext add base package value to rootContext component scan as given below.
#Configuration
#ComponentScan(basePackages = { "com.your.configuration.package", "org.springframework.web.servlet" })
public class RootConfiguration
Above given configuration will be helpful if you want injected dependency be available in your rootContext and can be accessed in your servlet-filter. For example If you catch exception in filter and want to send error response which is same as response sent by HttpMessageConverter but it is configured in servletContext, then you may want to access that configured converter to send the same response.
Note this, below autowiring will not work in servlet-filters
#Autowired
private ApplicationContext appContext;
this will not work in servlet filter, as filters are initialized before spring container got initialized.(Depends on order of filter and DelegatingProxyFilter)
So, to get applicationContext in filter
public class YourFilter implements Filter
{
private ApplicationContext appContext;
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
Filter.super.init(filterConfig);
appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
}
}
Hope it gives clear idea of how beans can be accessed between contexts.

Related

"Multiple ContextLoader* definitions error" occurred while using my own custom ContextLoaderListener

I have a spring boot application, and use the embedded jetty container, with no web.xml file.
I have a custom ContextLoaderLister class, which is a sub-class of default Spring ContextLoaderListener:
public class MyContextListener extends org.springframework.web.context.ContextLoaderListener
and register it as below:
#Configuration
public class ApplicationConfiguration {
#Bean
public ServletContextListener listener(){
return new ContextDestroyListener();
}
}
But while start the container, get the following error:
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!
I just want to use the custom ContextLoaderListener, rather than the default ContextLoaderListener. But get this error. It seems when I use the #Bean annotation to register a ContextLoaderListener, the default Spring ContextLoaderListener is still being used, so they are 2 ContextLoader in ApplicationContext, which cause this error.
I want to know what should I do, if I want to register my own ContextLoaderListener, and let Sprint not to add the default one automatically.
You can make your configuration extend SpringBootServletInitializer and then override onStartup method:
public class MyApplication extends SpringBootServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new MyContextListener());
}
}
Please be aware that this listener will not get contextInitialized event since context is already initialized in this moment.

AnnotationConfigApplicationContext.getBean returns a different bean, Spring

I have a problem that I have a ClassA needs RoomService to be injected, and it works fine that I find in ClassA, the roomService's id is the same.
While for some reason, I need roomservice to create room instance based on some input param for me, so I use below config to achieve this:
#Configuration
#EnableAspectJAutoProxy
public class Application {
private static ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
public static ApplicationContext getApplicationContext(){
return ctx;
}
#Bean
public RoomService roomService(){
return new RoomService();//Singleton
}
#Bean
#Scope("prototype")
public AbstractRoom room(AbstractRoom.Mode roomMode){
RoomService roomService = (RoomService) ctx.getBean(RoomService.class);
LogUtil.debug("--------from application:" +roomService.id1);//here, I find the id is different every time
return roomService.newRoom(roomMode);
}
}
The problem is that I need RoomService to be singleton, but I find that in the Application.java , the ctx.getBean(roomService) always returns a different bean which has different id. Isn't Spring should reuse the same bean? Why is that?
Here is how I create a room in RoomService.java
public AbstractRoom createRoom(String userID,int playerCount,Mode roomMode){
ApplicationContext ctx =Application.getApplicationContext();
AbstractRoom room = (AbstractRoom)ctx.getBean(AbstractRoom.class,roomMode);
}
Update:
I tried reusing the same ctx and it does not work. One hint is that I find my constructor of RoomService() is called several times(I put a break point in it.) when I run tomcat to start it
Here is my web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>wodinow</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Please help!
Each time you retrieve your RoomService you are creating a new ApplicationContext instance. But singleton beans are only guaranteed to be singleton within a single instance of ApplicationContext.
So if you want the bean to be singleton you must use the same ApplicationContext instance each time you retrieve it.
From Spring documentation:
singleton: (Default) Scopes a single bean definition to a single
object instance per Spring IoC container.
Update 1
You can just call roomService() in your room() method to get the room service without creating application context and Spring will ensure that they are the same instance since it is marked as #Bean which has implicit singleton scope.
Update 2
Based on the updated question here are couple of issues with your code in general:
1. Do not create ApplicationContext in your configuration class. When you start your Spring application in Tomcat, application context is created for you by Spring. You just need to tell Spring which configuration classes it should register.
2. Remove the room() bean definition from your configuration class. Method for creating a room should be in RoomService. So for example if you needed to create a new room in Spring MVC controller you would inject the RoomService and call createRoom method on it. The injected service would be singleton. Example:
#Controller
#RequestMapping("/rooms")
public class RoomController {
#Autowired
private RoomService roomService;
#RequestMapping(value="/create", method=POST)
public String createRoom() {
roomService.createRoom(/* Parameters for room creation */);
return "redirect:/somelocation";
}
}
Try to rework your code based on these suggestions and it should work.

Spring web context without Spring MVC

I want to use Spring to manage my JPA DAOs in web application and trying to follow the Spring website examples to figure out how to do it.
I am considering to use AnnotationConfigWebApplicationContext instead of AnnotationConfigApplicationContext to bootstrap Spring.
In the examples, it is used together with org.springframework.web.servlet.DispatcherServlet but I would like to use it without Spring MVC.
My web.xml looks following:
...
<web-app>
<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>com.foo.Application</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...
</web-app>
My configuration class looks following:
...
#Configuration
#ComponentScan(basePackages={"com.foo.service"})
public class Application {
#Bean
MessageService mockMessageService() {
return new MessageService() {
#Override
public String getMessages() {
return "Hello Spring World!";
}
};
}
}
Is it possible to inject mockMessageService in this example into my servlets or do I have to create a separate central hub for my web application where the services are called from by the servlets?
The problem is that if I have my servlets in package com.foo.service and try to use
#Autowired
to get dependancy or try some similar approach the service doesn't get injected and I don't know where's the problem. Is it possible to do it like that? Or should I add something for AnnotationConfigWebApplicationContext to work like that?
Servlets are managed by the servlet container and not by spring. Spring will not inject servlets, because it does not instantiate them.
If you want to get access to beans managed by spring within a servlet you must do it the same way as spring's DispatcherServlet does.
E.g. to get the WebApplicationContext from the ServletContext do
javax.servlet.ServletContext servletContext = ...;
WebApplicationContextUtils.getWebApplicationContext(servletContext);
This works because the ContextLoaderListener ensures that the WebApplicationContext is available as a ServletContext attribute.
Since the WebApplicationContext is an ApplicationContext you can autowire beans that have been instantiated outside the spring container.
For example
ApplicationContext appContext = ...;
AutowireCapableBeanFactory acbf = context.getAutowireCapableBeanFactory();
SomeBean someBean = new SomeBean();
acbf.autowireBean(someBean);
Just put the #Autowired annotation on fields of SomeBean.
A much easier way is to extend spring's org.springframework.web.servlet.FrameworkServlet and implement the doService method. Read the javadoc of the FrameworkServket for details.
If you have a servlet, you can wire it up on the init method using something like the below:
public class MyServlet implements javax.servlet.Servlet {
#Autowired
MyBean myBean;
#Override
public void init(ServletConfig arg0) throws ServletException {
final AutowireCapableBeanFactory autowireCapableBeanFactory=WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()).getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(this);
}
//....
}
If I have have a lot of servlets, it can be handy to extract the init method into some reusable parent class e.g.
public class AutowiredServlet implements javax.servlet.Servlet {
#Override
public void init(ServletConfig arg0) throws ServletException {
final AutowireCapableBeanFactory autowireCapableBeanFactory=WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()).getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(this);
}
}
and extend that class for every servlet that requires Autowiring.

How to configure Spring Security with already existing WebApplicationInitializer?

I am doing a web site project with Spring/ Spring MVC I learned how to configure a spring with the java classes and annotations approach, which by far is better than the XML.
Now I want to use Spring Security with my application. However I could not understand how to configure it with already existing WebApplicationInitializer ?
The Spring documentation is not so clear.
Here is my code and what I have so far:
public class AppInitializer implements WebApplicationInitializer{
//public class AppInitializer {
private static final Class<?>[] CONFIG_CLASSES = new Class<?>[]{SiteConfigs.class, AdminConfigurations.class};
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(CONFIG_CLASSES);
DispatcherServlet servlet = new DispatcherServlet(appContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", servlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
If I add AbstractSecurityWebApplicationInitializer to the classpath the container throws
exception.
HTTP Status 500 - No WebApplicationContext found: no ContextLoaderListener registered?
So how to config Spring Security so that springSecurityFilterChain get initialized
The exception tells you that you don't have a ContextLoaderListener which, in your case, is true. You only have a DispatcherServlet. By default Spring Security will only lookup the filters from the root application context (the one loaded by the ContextLoaderListener).
If you want to let it use a DispatcherServlets context instead you have to tell it that. You can tell it which to use by overriding the getDispatcherWebApplicationContextSuffix() method.
You aren't limited to a single WebApplicationInitializer you can have multiple in general you want to have one for your application bootstrapping and another to add security. You can then leverage the Spring convenience classes to save a couple of lines of code. See the Spring Security Reference for a sample.

Spring 3 receive servletContext in custom bean

my problem is that I can't get servletcontext in my bean.
I created custom bean "FileRepository" and I need to get ServletContext there.
here is the code
package com.pc.webstore.utils;
import java.io.File;
import java.nio.file.Files;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ServletContextAware;
public class FileRepository implements ServletContextAware {
private ServletContext servletContext;
public String saveFile(File file){
File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
...
}
#Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
registration in ApplicationContext.xml
<bean id="fileStorage" class="com.pc.webstore.utils.FileRepository"/>
when saveFile(File file) starts, I recive Nullpointerexception because servletContext == null.
So why servletcontext does not injected?
I have ContextLoaderListener registred in web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
I found that there is some scopes. May be problem is there. Tell me briefly about applicationsontext scope or give the link pleas.
Thanks for help. I spent a lot of time for this problem.
After some debuging I understood that setServletContexr method of servletcontextaware is actually was called when app starts but when I tried to store file with FileRepository from my controller, it was already anather object with null servletContext field.
Is there a way just to autowier servlet context in my custom bean when i want, like in controller?
Finaly I get servletContext via ServletContextAware. I change the way of creation fileRepository bean. From this
public String create(#Valid Item item, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest, FileRepository fileRepository) {
to this
#Autowired
private FileRepository fileRepository;
#RequestMapping(method = RequestMethod.POST, produces = "text/html")
public String create(#Valid Item item, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
ContextLoaderListener loads an ApplicationContext that becomes the global parent context for the application. There is no ServletContext there. ServletContext only exists within (pardon the overloading of terms) the CONTEXT of a SERVLET- for instance, the DispatcherServlet. Every DispatcherServlet (typically you'd only have one) registers a child context that points back to the global parent context registered by the ContextLoaderListener. ApplicationContexts are like classloaders. When the IOC container goes "looking" for a bean, each ApplicationContext can look "up" to its parent to try to find it, but it can't look down. Children can also override bean definitions from their parent context.
Now... it would seem your problem is that your bean is defined in global parent context where there is no ServletContext to be found. (It can't look "down" to its children to find it.)
What you need to do is move the fileStorage bean definition "down" into the ApplicationContext for the DispatcherServlet.
When you define a DispatcherServlet in your web.xml, you usually specify where it can find the files that define its child context. Like so:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/web-context/*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Move that bean definition down into the location specified by contextConfigLocation and everything should work as you expect.

Categories