Spring 3 receive servletContext in custom bean - java

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.

Related

How are the beans retrieved in Spring-MVC?

In a simple spring application you register your bean in the spring IoC container by using #Component annotation and then to retrieve the bean you first read the spring configuration file and then retrieve the bean from container using:
ClassPathXMLApplicationContext context = new ClassPathXMLApplicationContext("spring config file")
Coach theCoach=context.getBean("beanId","classname")
Now, you can call the methods on theCoach.
How are we retrieving the bean from the container as we are not using
context.getBean();
Is the DispatcherServlet handling this?
After editing-
/*********************Spring Application*******************************/
applicationContext.xml
<beans _______>
<context:component-scan base-package="packageName"/>
</beans>
Coach.java
public interface Coach{
public String getDailyWorkOut();
}
TennisCoach.java
#Component
public class TennisCoach implements Coach{
public String getDailyWorkOut(){
return "practise back hand volley";
}
ApplicationDemo.java
public class ApplicationDemo{
public static void main(String[] args){
ClassPathXMLApplicationContext context = new ClassPathXMLApplicationContext("applicationContext.xml");
Coach theCoach=context.getBean("tennisCoach",Coach.class)
theCoach.getDailyWorkOut();
}
}
/*********************Spring Application*******************************/
Now for Spring MVC-
/*****************Spring-MVC Application**************************/
web.xml
<web-app>
<servlet>
<servlet-name>HelloWeb</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigurationLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloWeb</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
applicationContext.xml
<beans _______>
<context:component-scan base-package="packageName"/>
</beans>
Coach.java
public interface Coach{
public String getDailyWorkOut();
}
TennisCoach.java
#Component
public class TennisCoach implements Coach{
#RequestMapping("/")
public String getDailyWorkOut(){
return "practise back hand volley";
}
/*********************Spring-MVC Application*********************/
What I want to know is -
That in above given spring application I am retrieving bean from the container using context.getBean(), how is the Coach bean being retrieved in Spring-MVC application?
Yes, you can just create field with annotation #Autowired and spring inject it for you. Make sure your class where you are going to use this bean is also spring bean.
In you example you are retrieving bean via bean lookup from ApplicationContext.
Coach theCoach=context.getBean("tennisCoach",Coach.class)
In this case you know exact class name you need(such you are an author of your bean), and you simply get it from the context.
For DispatcherServlet it is not so easy, because it knows nothing about beans you've added to the context.
The only option it has is a full scanning of all defined in the context beans and detecting anything it can recognise (Controller, RestController, RequestMapping). Example of such detector is AbstractDetectingUrlHandlerMapping with it's implementations. SpringMvc has various implementations of such detectors, you can implement your own if you need.

#Value not working inside a class which extends other

Is it possible to use #Value inside a class that extends another class?
Below are the relevant code snippets. In the Lo_Controller class it works perfectly, but in the Lo_DisplayHandler always returns null. The only reason I can think of is because it depends on another class, which is not annotated with #Component. If that is the cause, what is the recommended option to read a value from properties file similar to #Value?
Just to test it out, I changed from #Component to #Controller in Lo_DisplayHandler to see, if they are somehow related to each other, however it returns null as well.
This works:
package com.ma.common.controller;
imports ...
#Controller
#RequestMapping("/log")
public class Lo_Controller {
#Value("${log.display.lastpage}")
private String lastPageUrl;
...
This always returns null:
package com.ma.log.handler;
imports ...
#Component
public class Lo_DisplayHandler extends Lo_Handler {
public Lo_DisplayHandler() {
super();
}
#Value("${log.display.lastpage}")
private String lastPageUrl;
...
mvc-dispatcher-servlet.xml
<context:component-scan base-package="com.ma.common.controller, com.ma.log.handler" />
<context:property-placeholder location="classpath:restServices.properties"/>
<mvc:annotation-driven />
<mvc:resources mapping="/**" location="/" />
#Component
public class Lo_DisplayHandler extends Lo_Handler {
#Value("${log.display.lastpage}")
private String lastPageUrl;
public void anyOtherMethod(){
String _a = lastPageUrl; //FAIL - always null
}
#PostConstruct
public void initIt() throws Exception {
String _a = lastPageUrl; //OK - when the application is deployed and started for the first time
}
#PreDestroy
public void cleanUp() throws Exception {
String _a = lastPageUrl; //OK - when the application is stopped
}
web.xml
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Three possible problems here:
First, I disencourage to extends classes as Spring works better autowiring dependencies. But it should work
Second, you have to focus on the lifecycle of a bean. The property will be set after the instanciation. #PostConstruct to validate the content.
Third, The visibility in hierarchical context of the property holder is not straight forward. So if you define #value in the root applicationContext, it will not set by your dispatcherServlet context. To test it, please inject the dependency of the bean defined at root level, you will see that your #Value will be take in account.
The lastPageUrl property will be accessible from a bean within the same context (by push creation or pull creation).
In case of push creation, if another bean autowire the Lo_DisplayHandler bean and call your method anyOtherMethod(), it will get the value.
#Component
public class ScannableIntoYourContext{
#Autowired
private Lo_DisplayHandler myHandler;
}
Other way is to pull the bean from ObjectFactory.
#Autowired
private ObjectFactory<Lo_DisplayHandler> bean;
Lo_DisplayHandler instanceFromContext = bean.getObject();

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.

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

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.

Categories