Use property value in spring initialisation - java

I'm trying to change the session timeout in spring via Java Config (no web.xml).
It looks quite simple if you don't mind hard coding the timeout, however this isn't desired. Is there a way to use the #Value annotation during initialisation?
E.g.
#PropertySource("classpath:/com/example/demo.properties")
public class Initialiser implements WebApplicationInitializer {
/** Session timeout in seconds. */
#Value(value = "${session-timeout}")
private int sessionTimeout;
#Override
public void onStartup(final ServletContext container) {
container.addListener(new SessionListener(sessionTimeout));
}
}
The field at runtime is 0 as it appears that you can't inject values during startUp. Is there anyway round this?
Thanks for any help.

You can try this way :
#PropertySource("classpath:/com/example/demo.properties")
public class Initialiser implements WebApplicationInitializer {
#Resource
private Environment env;
#Override
public void onStartup(final ServletContext container) {
container.addListener(new SessionListener(env.getRequiredProperty("session-timeout")));
}
}

Related

spring dependency injection is not working in a class annotated with #WebListener

I tried constructor based Dependency injection in TestAppListener class which implements ServletContextListener.
I got this error
Exception sending context initialized event to listener instance of class [com.example.listener.TestAppListener].
I searched stack overflow but I couldn't find any solution for this scenario. Please any one help me in sort out this. I placed Implementation classes in META-INF.services folder also. My understanding is there is some problem in constructor dependency injection but this way of DI is need for my situation, because in real time I want to create datasource connection inside startup method.
Find all my classes below which i'm using,
#WebListener
public class TestAppListener implements ServletContextListener {
private static TestDao dao;
public TestAppListener(TestDao dao){
this.dao = dao;
}
public TestAppListener(){}
#Override
public void contextInitialized(ServletContextEvent sce) {
dao = ServiceLoader.load(TestDao.class).iterator().next();
dao.startUp();
System.out.println("Context initialized method called");
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Context destroyed method called");
dao.shutDown();
}
}
public interface TestDao {
void startUp();
void shutDown();
}
public class TestDaoImpl implements TestDao {
#Override
public void startUp() {
System.out.println("Database is initialized");
}
#Override
public void shutDown() {
System.out.println("Database is initialized");
}
}
#Configuration
public class SpringConfig {
public SpringConfig() {
}
#Bean
public ServletListenerRegistrationBean<ServletContextListener> listenerRegistrationBean() {
ServletListenerRegistrationBean<ServletContextListener> bean = new ServletListenerRegistrationBean<>();
bean.setListener(new TestAppListener());
return bean;
}
}
The Servlet #WebListeners are handled by Servlet containers(tomcat/Jetty) when the container is starting. So they know nothing about Spring.
For more details, see discussion in this issue.
A workaround solution is replace the #WebListener with Spring's #Component, thus you can inject Spring beans(declare your Dao as spring beans) into the listeners directly.
BTW, you have to add #ServletComponentScan on your Spring Boot application class to activate it.
I created an example project to demo #WebServlet, #WebFilter and #WebListener.
Change private static TestDao dao to private final TestDao dao. Spring doesn't allow statics to be used as targets for injection. Also, your TestDaoImpl class needs to be a component or a bean defined in a Spring configuration file.

Injecting a single Spring bean instance into a static variable of a utility class

I have this web application built with Spring and Vaadin, in which I wanted to do this, for the sake of convenience:
Create a utility class that wraps a Spring service, and allows the use of its static methods throughout the application, without having to worry about injecting this service everywhere, like so:
String configurationValue = ConfigurationUtil.getString("some.property.key");
If you work with Vaadin, you might see how convenient this is, because the whole presentation layer is written in Java and you can't always inject Spring services into your Vaadin components as these Vaadin components are not always Spring components themselves.
So this is my utility class:
public final class ConfigurationUtil {
// this is the spring service:
private static ConfigurationService configurationService;
public static void setConfigurationService(final ConfigurationService configurationService) {
ConfigurationUtil.configurationService = configurationService;
}
public static String getString(final String key) {
return configurationService.getString(key);
}
}
This is my service:
#Service("configurationService")
public class ConfigurationServiceImpl implements ConfigurationService, BeanFactoryAware {
private final FrameworkService frameworkService;
#Autowired
public ConfigurationServiceImpl(final FrameworkService frameworkService) throws IOException, ConfigurationException {
// this is where I set this service bean to the utility class
ConfigurationUtil.setConfigurationService(this);
this.frameworkService = frameworkService;
}
public String getString(String key) {
// code that retrieves a configuration value from our configuration files
}
}
My question here is: I'm a bit worried about this causing a bottleneck to access the configuration service, as multiple threads will be calling it, from multiple user sessions. Would this be a problem? Please explain why. Also, feel free to point out other problems with this solution.
I suggest to create a bean that implements ApplicationContextAware like this:
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext ac) {
context = ac;
}
public static String getString(final String key) {
ConfigurationService configurationService = context.getBean(ConfigurationService.class);
return configurationService.getString(key);
}
public static <T> T bean(Class<T> beanType) {
return context.getBean(beanType);
}
}
You can create a method like in the example to give static access to Spring Beans or what you requested to get a String from your ConfigurationService.
Btw. I use this a lot in Vaadin applications because I don't want to make every component a Spring Bean.

How to get bean from ServletContextListener in contextDestroyed

I want to get bean in contextDestroyed so I have this code.
public class MyContextListener implements ApplicationContextAware, ServletContextListener {
private final Logger logger = LogManager.getLogger(getClass());
private ApplicationContext appContext;
#Override
public void contextInitialized(ServletContextEvent event) {
logger.warn("Start");
}
#Override
public void contextDestroyed(ServletContextEvent event) {
logger.warn("End" + appContext);
appContext.getBean("myBean")
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.appContext = applicationContext;
}
}
problem is that it log null. But when I change it to
private static ApplicationContext appContext;
then I can see my appContext. Why ? What is right options to get bean
You have 2 instances of the MyContextListener. One configured by Spring and one from the web container.
The one configured by Spring will have the ApplicationContextAware callback and have the appContext property set, but it isn't known to your servlet container and as such will not participate in the lifecycle callbacks.
The second instance is the one in your servlet container, however as that isn't a spring managed one it will not receive the callback for the ApplicationContextAware and as such the appContext is going to be null.
Making it static kind of "solves" it as it is now a class variable instead of an instance variable. Now all instances share that variable.
It is better to remove the ApplicationContextAware and use the WebApplicationContextUtils.getRequiredWebApplicationContext method in your listener instead.
public class MyContextListener extends BaseTask implements ServletContextListener {
private final Logger logger = LogManager.getLogger(getClass());
#Override
public void contextInitialized(ServletContextEvent event) {
logger.warn("Start");
}
#Override
public void contextDestroyed(ServletContextEvent event) {
logger.warn("End" + appContext);
WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()).getBean("myBean");
}
}
Now you only need the instance for the servlet container (web.xml or other means of configuring it) and you can remove the spring managed instance.

Spring override properties at runtime and keep them persistent

I am using Spring 3.2.8 and keep my settings in a properties file. Now I want some of them override at runtime. ​​I want to keep the new values persistent by overwriting the old values in the properties file​​.
How can I do this in Spring? Some properties ​​I inject with # Value and others get with MessageSource.getMessage(String, Object [], Locale). The beans are already instantiated with these values​​. How can I access the properties, store them and update all beans system wide?
Thanks!
OK, given your follow up answers I would keep this fairly simple and use what you already know of Spring. I'll make some assumptions that annotation configuration is OK for you.
In my example, I'll assume all the properties that you want to configure relate to something called a ServerConfiguration and that initially these are read from server.properties on the classpath.
So part 1, I would define a bean called ServerProperties that has the original values from the server.properties injected into it.
So:
#Component
public class ServerProperties
{
#Value("${server.ip}");
private String ipAddress;
...
public void setIpAddress(String ipAddress)
{
this.ipAddress = ipAddress;
}
public String getIpAddress()
{
return this.ipAddress;
}
}
Secondly, anywhere that relies on these properties, I would inject an instance of ServerProperties rather than using #Value e.g:
#Component
public class ConfigureMe
{
#AutoWired
private ServerProperties serverProperties;
#PostConstruct
public void init()
{
if(serverProperties.getIpAddress().equals("localhost")
{
...
}
else
{
...
}
}
}
Thirdly I would expose a simple Controller into which ServerProperties is injected so that you can use your web page to update system properties e.g:
#Controller
public class UpdateProperties
{
#AutoWired
private ServerProperties serverProperties;
#RequestMapping("/updateProperties")
public String updateProperties()
{
serverProperties.setIpAddress(...);
return "done";
}
Finally, I would use #PreDestroy on ServerProperties to flush the current property values to file when the ApplicationContext is closed e.g:
#Component
public class ServerProperties
{
#PreDestroy
public void close()
{
...Open file and write properties to server.properties.
}
}
That should give you a framework for what you need. I'm sure it can be tweaked, but it will get you there.

#Autowired in ServletContextListener

i hava aclass InitApp
#Component
public class InitApp implements ServletContextListener {
#Autowired
ConfigrationService weatherConfService;
/** Creates a new instance of InitApp */
public InitApp() {
}
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println(weatherConfService);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
and listener in web.xml:
<listener>
<listener-class>com.web.Utils.InitApp</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
the confService print --> null
what the problem?
A couple of ideas came to me as I was having the same issue.
First one is to use Spring utils to retrieve the bean from the Spring context within the listener:
Ex:
#WebListener
public class CacheInitializationListener implements ServletContextListener {
/**
* Initialize the Cache Manager once the application has started
*/
#Override
public void contextInitialized(ServletContextEvent sce) {
CacheManager cacheManager = WebApplicationContextUtils.getRequiredWebApplicationContext(
sce.getServletContext()).getBean(CacheManager.class);
try {
cacheManager.init();
} catch (Exception e) {
// rethrow as a runtime exception
throw new IllegalStateException(e);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
}
This works fine if you only have one or two beans. Otherwise it can get tedious. The other option is to explicitly call upon Spring's Autowire utilities:
#WebListener
public class CacheInitializationListener implements ServletContextListener {
#Autowired
private CacheManager cacheManager;
/**
* Initialize the Cache once the application has started
*/
#Override
public void contextInitialized(ServletContextEvent sce) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
try {
cacheManager.init();
} catch (Exception e) {
// rethrow as a runtime exception
throw new IllegalStateException(e);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
}
The caveat in both these solutions, is that the Spring context must by loaded first before either of these can work. Given that there is no way to define the Listener order using #WebListener, ensure that the Spring ContextLoaderListener is defined in web.xml to force it to be loaded first (listeners defined in the web descriptor are loaded prior to those defined by annotation).
By declaring
<listener>
<listener-class>com.web.Utils.InitApp</listener-class>
</listener>
in your web.xml, you're telling your container to initialize and register an instance of InitApp. As such, that object is not managed by Spring and you cannot #Autowired anything into it.
If your application context is set up to component-scan the com.web.Utils package, then you will have a InitApp bean that isn't registered as a Listener with the container. So even though your other bean will be #Autowired, the servlet container won't ever hit it.
That is the trade-off.
There are workarounds to this if you use programmatic configuration with a ServletContainerInitializer or a WebApplicationInitializer for servlet 3.0. You wouldn't use #Autowired, you would just have setter/getter that you would use to set the instance.
Here's an example
class SpringExample implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = ...;
SomeBean someBean = context.getBean(SomeBean.class);
CustomListener listener = new CustomListener();
listener.setSomeBean(someBean);
// register the listener instance
servletContext.addListener(listener);
// then register DispatcherServlet and ContextLoaderListener, as appropriate
...
}
}
class CustomListener implements ServletContextListener {
private SomeBean someBean;
public SomeBean getSomeBean() {
return someBean;
}
public void setSomeBean(SomeBean someBean) {
this.someBean = someBean;
}
#Override
public void contextInitialized(ServletContextEvent sce) {
// do something with someBean
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
Note that Spring has some custom implementation of WebApplicationInitializer that are quite sophisticated. You really shouldn't need to implement it directly yourself. The idea remains the same, just deeper in the inheritance hierarchy.
#WebListener
public class StartupListener implements ServletContextListener {
#Autowired
private MyRepository repository;
#Override
public void contextDestroyed(ServletContextEvent event) {
}
#Override
public void contextInitialized(ServletContextEvent event) {
AutowireCapableBeanFactory autowireCapableBeanFactory = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()).getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(this);
repository.doSomething();
}
}
As others have said this listener observes by the web servlet(tomcat) context (Not the Spring Container) and is notified of servlet startup/shutdown.
Since it is created by the servlet outside of the Spring container it is not managed by Spring hence #Autowire members is not possible.
If you setup your bean as a managed #Component then Spring will create the instance and the listener wont register with the external servlet.
You cannot have it both ways..
One solution is the remove the Spring annotations and manually retrieve your member from the Spring Application context and set your members that way.
ie
public class InitApp implements ServletContextListener {
private ConfigrationService weatherConfService;
private static ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:web-context.xml");
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
weatherConfService = applicationContext.getBean(ConfigrationService.class);
System.out.println(weatherConfService);
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
With current versions of Spring Boot 2, you can also register Servlets, filters, and listeners as Spring beans and use autowired components normally:
Registering Servlets, Filters, and Listeners as Spring Beans
Any Servlet, Filter, or servlet *Listener instance that is a Spring bean is registered with the embedded container. This can be particularly convenient if you want to refer to a value from your application.properties during configuration.
More info here Register #WebListeners in a way that allows them to register servlets and filters.
This means that you simply have to annotate your ServletContextListener as #Comonent.
Since Spring Boot 2.4 using #WebListener does not work anymore, mentioned in the release notes.
A side-effect of this change is that the Servlet container now creates the instance of the WebListener and, therefore, dependency injection such as with #Autowired can no longer be used. In such cases, #Component should be used instead.

Categories