Is there a way to execute a class before the Spring container has been initialized?
Basically, I have some resources which I have scattered across the jars that make up my application. Upon first the very first execution of the application, they won't be on the file system and people will need to be able to adjust these configuration files sometimes. So... instead of editing them and re-packaging, I would like to be able to copy all these resources outside the respective jars into an extracted directory structure where they could easily be changed. I have so far implemented this. However, I now need to be able to actually do this before Spring's container has been initialized so that they are present when Spring tries to initialize. How can this be done?
I would need to be able to do this both from a webapp perspective and in a JUnit test.
use a ServletContextListener:add the following to your web.xml before spring servelt.
<listener>
<listener-class>your.full.className.YourListener</listener-class>
</listener>
where YourListener looks like:
public class YourListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent evt) {
//execute anything here.
}
}
Related
I am using Spring 4 (not Spring Boot) in the web application. I need to run some initialization code before any of the beans in the application context would be created. I tried to create implementation of org.springframework.context.ApplicationContextInitializer and register it in spring.factories but was not picked up for some reason. How can I do it?
As it turned out implementing of org.springframework.context.ApplicationContextInitializer was a right way. Because in my project I do not use Spring MVC implementation of this initializer should be registered in web.xml instead of spring.factories. Here is an example:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>my.company.MyContextInitializer</param-value>
</context-param>
This should work. If not, please post your code.
#Component
public class SampleBootstrap implements ApplicationListener<ContextRefreshedEvent> {
....
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Do Something();
}
}
I have a servlet packaged in an ear file, and I cannot get it to resolve #Value annotated properties.
The app is in two parts: a servlet packaged in a war file, which is then included by different applications packaged in an ear file. The application supplies the implementation of a class (ResourceManager) defined by an interface in the servlet, and also the property file containing the value of the property for the #Value annotated field.
The war file contains:
web.xml:
<servlet>
<servlet-class>... extends MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/spring-ws.xml
classpath:/spring-app.xml
</param-value>
</init-param>
</servlet>
spring-ws.xml:
<context:annotation-config/>
<context:property-placeholder location="classpath:/spring-ws.properties"/>
The values in spring-ws.properties are referenced directly from spring-ws.xml, and all work fine.
The servlet class contains:
public class MyServlet extends MessageDispatcherServlet
#Autowired private ResourceManager resourceManager; // original annotation - got this to work
#Value("${app.name:}") private String appName; // added later - cannot get this to work
The app.name is optional, hence the trailing ":".
The ear file adds (packaged in a jar file in the ear lib directory):
spring-app.xml:
<context:property-placeholder location="classpath:/spring-app.properties"/>
<bean class="ResourceManagerImpl">
...
</bean>
spring-app.properties:
app.name=myApp
My first problem was with the #Autowired annotation: not sure I ever got to the bottom of that correctly, but I managed to get it to work by adding this code to the servlet:
#Override
protected void initFrameworkServlet() throws ServletException {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(getWebApplicationContext().getAutowireCapableBeanFactory());
bpp.processInjection(this);
}
This may not be the best way to fix this, but it was the first I found and it worked, so I used it and moved on. Then I added a #Value annotated field, and I can't find a way to make this work. The javadocs for AutowiredAnnotationBeanPostProcessor say that it should process #Value annotations as well, but it doesn't appear to: the appName property is always blank (presumably from the default).
I do have a workaround, using beans:
#Autowired #Qualifier("appName") private String appName;
<bean id="appName" class="java.lang.String">
<constructor-arg value="myApp"/>
</bean>
But I'm sure there must be a better way of doing this. I've read this post Difference between applicationContext.xml and spring-servlet.xml in Spring Framework, but I'm not sure it applies: I don't think I even have an application context here, just a servlet context defined in the web.xml for the (single) servlet class.
I've also seen this: Spring 3.0.5 doesn't evaluate #Value annotation from properties, which looks like the same problem, but the solution there appeared to be to move the "<context:property-placeholder>" from the application context to the servlet context, whereas as I said before, I don't think I even have an application context here...
Finally, I've had a quick go at simplifying the problem, removing the separate app and packaging everything in a single war, but I don't think that is the problem (although could obviously be wrong...): spring can resolve the class path for the app.xml file so it must be able to resolve the class path for the app.properties file.
Any help much appreciated, but I guess at this point it is a bit of an academic interest, as I have a workable solution (using beans) and the people that pay the bills don't really care how it works as long as it works! I'd just like to know so next time I don't have to copy (what I think is) a flawed workaround.
Using spring-ws-core 2.1.4, which pulls in spring 3.2.4 via maven dependencies.
Thanks
Update - Solved
Turns out the key to fixing this was M Deinum's comment that "You also have a duplicate element...". I had two separate PropertyPlaceholderConfigurers in the same context, so the second was ignored and its properties never used. There was no problem with the context itself (which is a servlet context not a root context), or the annotation processing.
So the fix was simply to remove the property placeholder from the spring-app.xml, and combine both property files in the spring-ws.xml
<context:property-placeholder
location="classpath:/spring-ws.properties,
classpath:/spring-app.properties"/>
TL;DR's answer here Spring: namespace vs contextConfigLocation init parameters in web.xml is also an awesome explanation of how the different contexts are processed!
Cheers,
Your servlet isn't a spring managed bean it is managed by the Servlet container. Hence the reason why #Autowired doesn't work in the first place. Spring will only process beans it knows.
Both the ContextLoaderListener and your servlet (basically any servlet that extends FrameworkServlet) have there own instance of an ApplicationContext. The one from the ContextLoaderListener is used as a parent by the context constructed in the servlet. So you indeed have an ApplicationContext in your servlet.
Now for this #Value to resolve properly you need to have a PropertyPlaceHolderConfigurer to resolve the placeholders. As PropertyPlaceHolderConfigurer is a BeanFactoryPostProcessor it only operates on beans in the same context, so the one loaded/configured by the ContextLoaderListener doesn't do anything for beans related to the context from your servlet. (Hence you need to add <context:property-placeholder ... /> element to the servlet context).
You also have a duplicate element in general you want to avoid that kind of situation as it will lead to problems at same time, either one overriding the other or you will get exceptions that placeholders cannot be resolved by either one of the PropertyPlaceHolderConfigurer
Your init code is also overly complex and you don't need the AutowiredAnnotationBeanPostProcessor simply use the available methods.
getWebApplicationContext().getAutowireCapableBeanFactory().autowireBean(this);
Which also does more then what only the AutowiredAnnotationBeanPostProcessor does.
for #Autowired annotation, you just need to have a class with those annotations:
#Configuration
#PropertySource("classpath:myFile.properties")
#ComponentScan("com.company.package")
The #Configuration is used by Spring to tell it's a class for configuration (it replaces the xml files that you talked about, xml conf file is Spring 2, annotation is new with Spring 3 and 4)
The #ComponentScan manage the IOC (#Autowire)
The #PropertySource load the prop file
Then the class to initialized at startup with the prop file:
#Component("myFileProperties")
public class MyFileProperties{
#Value("${app.name}")
private String appName;
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName= appName;
}
}
Take a look here if you want it can inspire you (a conf file in a web context that I developped): https://github.com/ebrigand/RSSLiker/blob/master/src/main/java/com/mrm/rss/init/WebAppConfig.java
There is a workaround for this.
You can create a static field in a java class to store the value in.
public class SystemPropertyHelper {
public static String propertyValue;
}
And create a java class annotated with #Service or #Component, which you can inject the property in, and pass this value to the static field.
#Component
public class SpringBeanClass {
#Value("${propertyName}")
private String propertyValue;
#PostConstruct
public void init() {
SystemPropertyHelper.propertyValue = propertyValue;
}
}
This way you can always access the property value through the static field SystemPropertyHelper.propertyValue
I want to insert some data into the database using the application API if the application has started up with some empty database tables. How do I go about this?
I'm using Spring 3.1, Hibernate 4.1.1.
[edit]
Thanks to AlexR the answer is to sublcass ContextLoaderListener, call super in contextInitialized and then do whatever it is you need to do:
public class MyContextLoaderListener extends ContextLoaderListener {
public void contextInitialized(final ServletContextEvent event) {
super.contextInitialized(event);
// ... doStuff();
}
}
You may also need to wire this up in web.xml instead of the Spring one:
<listener>
<listener-class>com.example.MyContextLoaderListener</listener-class>
</listener>
You can use spring context listener.
Take a look on: http://fusesource.com/docs/framework/2.2/deploy_guide/CXFServletDeploySpring.html
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/context/ContextLoaderListener.html
http://forum.springsource.org/showthread.php?88896-Spring-MVC-3-is-ContextLoaderListener-needed
Is there any spring specific way in the framework to perform initialization when MVC loads up?
Say I need to create global objects based on configuration files, is there a place to do this or do I just create my own servlet and do this in oninit?
What about standard #PostConstruct?
#Service
class AnySpringBean {
#PostConstruct
public void init() {
//run when bean is created
}
}
Works on #Controllers as well.
UPDATE: The more global place would be to subclass ContextLoaderListener) and override contextInitialized() and use it in web.xml (see user1076371 answer). I don't like this approach much, but at least the initialization is not tied to any Spring bean.
There is an ApplicationListener interface you can implement to hook into the startup completion event. I use this in my app to do things after I know Spring has finished starting up. I have a few different classes that I want to kick off background threads after the system is "up" and each implements this interface to do their particular post startup stuff.
It's sent after the app as a whole is done, but it's each listening spring bean that gets an event, so you'd could hook it into some existing bean or to create something like a PostStartupBean that exists only to implement this one method.
public void onApplicationEvent( ApplicationEvent applicationEvent )
{
if ( applicationEvent instanceof ContextRefreshedEvent )
{
..do stuff here..
}
}
Best thing would be to leave it to Spring. Add your global object beans (which could be singletons etc..) to your application context. Make sure Spring is initialized when the application is loaded by adding ContextLoaderListener to the web.xml.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4">
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
You can access your global objects from the WebApplicationContext anywhere in the application.
I have a JSP web site, not Spring MVC, and it has a config file web.xml.
There are a couple of settings within the web.xml file that I'd like to get.
However, I want to access those settings from within a class sitting in my Source Packages folder.
I know I can pass the ServletContect from the JSP to the class but I want to avoid this and just access the web.xml file from my class.
Is this possible?
EDIT
I have been looking at javax.servlet thinking what I want was in there but if it is I can't see it.
Using a javax.servlet.ServletContextListener implementation, that allows a singleton-like access to context:
package test.dummy;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
public class ContextConfiguration implements ServletContextListener {
private static ContextConfiguration _instance;
private ServletContext context = null;
//This method is invoked when the Web Application
//is ready to service requests
public void contextInitialized(ServletContextEvent event) {
this.context = event.getServletContext();
//initialize the static reference _instance
_instance=this;
}
/*This method is invoked when the Web Application has been removed
and is no longer able to accept requests
*/
public void contextDestroyed(ServletContextEvent event) {
this.context = null;
}
/* Provide a method to get the context values */
public String getContextParameter(String key) {
return this.context.getInitParameter(key);
}
//now, provide an static method to allow access from anywere on the code:
public static ContextConfiguration getInstance() {
return _instance;
}
}
Set it up at web.xml:
<web-app>
<listener>
<listener-class>
test.dummy.ContextConfiguration
</listener-class>
</listener>
<servlet/>
<servlet-mapping/>
</web-app>
And use it from anywhere at the code:
ContextConfiguration config=ContextConfiguration.getInstance();
String paramValue=config.getContextParameter("parameterKey");
I think this is very close to your description: link.
Basically, you want to read parameters from web.xml programatically, right?
Hmmm... I am assuming that once your web app is up then you are not going to make any change in the web.xml....
Now what you can do is a create a servlet which loads on the startup and initialize a singleton class. You can use the following setting in your web.xml.
<servlet>
<description></description>
<display-name>XMLStartUp</display-name>
<servlet-name>XMLStartUp</servlet-name>
<servlet-class>com.test.servlets.XMLStartUp</servlet-class>
<init-param>
<param-name>log4j-init-file</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
In tomcat if you set load-on-startup value 0, then it means that while loading it has got the highest priority. now in the servlets init method read all the init-parameters like this and set it in your singleton class.
String dummy= getInitParameter("log4j-init-file");
This is not easily possible and may not be an elegent solution. The only option I can suggest is to have your configuration options i a xml or properties file and put it in your WEB-INF/classes directory so you can look it up using ClassLoader.getResource or ClassLoader.getResourceAsStream
I know it may be a duplication of the configuration, but IMO its the elegent way.
I really don't like classes reading from web.xml... Why do you need that?
IMHO it would be easier, cleaner and by far much more manageable if you prepared a properties file and a manager class that reads from there.