Ordering Listeners in Spring's WebApplicationInitializer - java

I have a custom ServletContextListener that I am using to initialize and start up a Cron4J scheduler.
public class MainListener implements ServletContextListener {
#Value("${cron.pattern}")
private String dealHandlerPattern;
#Autowired
private DealMoqHandler dealMoqHandler;
}
I am autowiring some objects in the Listener as shown, and would like for Spring to manage the listener's instantiation. I am using programmatic web.xml configuration through WebApplicationInitializer, but so far the Listener isn't being autowired (NullPointerExceptions whenever I try to access the supposedly autowired objects).
I've already tried to add my customer listener after adding the ContextLoaderListener, shown below:
public class CouponsWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(SpringAppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
container.addListener(new MainListener()); //TODO Not working
}
I checked these past questions Spring - Injecting a dependency into a ServletContextListener and dependency inject servlet listener and tried to implement the following code inside the contextInitialized method of my listener:
WebApplicationContextUtils
.getRequiredWebApplicationContext(sce.getServletContext())
.getAutowireCapableBeanFactory()
.autowireBean(this);
However, I just get the following exception:
Exception sending context initialized event to listener instance of class com.enovax.coupons.spring.CouponsMainListener: java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:90) [spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
How can I make sure that Spring has already finished instantiation before adding my listener?

My mistake. It turns out the code in the original post is correct, and the listeners are being added in the correct order.
The issue was that I had annotated my custom listener with a #WebListener annotation (Servlet 3.0). This was causing the web app to disregard my addListener() code in WebApplicationInitializer, and instantiate the custom listener AHEAD of Spring's ContextLoaderListener.
The following code block illustrates the ERRONEOUS code:
#WebListener /* should not have been added */
public class CouponsMainListener implements ServletContextListener {
#Autowired
private Prop someProp;
}

You cannot use new with Spring beans - Java doesn't care about Spring and Spring has no way to modify the behavior of the new operator. If you create your objects yourself, you need to wire them yourself.
You also need to be careful what you do during initialization. When using Spring, use this (simplified) model: First, Spring creates all the beans (calls new for all of them). Then it starts to wire them.
So you must be extra careful when you start to use the autowired fields. You can't always use them right away, you need to make sure that Spring is finished initializing everything.
In some cases, you can't even use autowired fields in #PostProcess methods because Spring couldn't create the bean because of cyclic dependencies.
So my guess is that "whenever I try to access" is too early.
For the same reason, you can't use WebApplicationContextUtils in the WebApplicationInitializer: It hasn't finished setting up Spring, yet.

Related

Validate database entries at startup of SpringBoot Application

In my spring boot application I have multiple #Service implementations of an interface. Which of these implementations is used at runtime for any given request is configured in a databse.
Something like this:
Value
Service Bean
Hello
ServiceA
World
ServiceB
Foo
ServiceA
Bar
ServiceC
The correct bean is then retrieved using the application context and the defined Service Bean Name from the database. However it could be possible that a Service Bean is mentioned in the database that does not exist in the application context. I'd rather detect this during startup than at runtime.
This question basically boils down to how you add your own validation to the spring boot startup process or what's the best practice? I tried throwing an Exception when creating the bean, that deals with the mapping of values to Service Beans, and handling it with my own FailureAnalyzer. But the FailureAnalyzer never gets called because due to the missing bean an UnsatisfiedDependencyException is also thrown and causes the application to stop.
I found a solution that I'm happy with.
I did not register my FailureAnalyzer in my resource folder in the META-INF/spring.factories file.
As described in the question throwing my Exception during bean creation caused an UnsatisfiedDependencyException. So instead I used an ApplicationListener and perform my check, when the application is ready but has not yet started.
#Component
public class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if(!checkConsistency())
throw new MyRuntimeException();
}
}
Now my FailureAnalyzer gets called and handles MyRuntimeException and stops the application with a proper message.
#Component
public class MyRuntimeExceptionFailureAnalyzer extends AbstractFailureAnalyzer<MyRuntimeException> {
#Override
protected FailureAnalysis analyze(Throwable rootFailure, MyRuntimeException cause) {
return new FailureAnalysis(buildErrorMessage(cause), buildActionMessage(cause), cause);
}
}

Automatically configure beans in root and dispatcher application context with single annotation

Let's say I've create a annotation called #EnableFeauture which imports a bean configuration class EnableFeatureConfiguration. This annotation is typically placed on top of the dispatcher configuration. Must beans like view resolvers etc. belong to that dispatcher config but a few beans really belong to the root context.
How can I define those beans without the need for another annotation? My first thought was to autowire the WebApplicationContext and call context.getParentBeanFactory() to register beans but I'm not sure if this is the best way to achieve my goal. How is this typically done?
UPDATE
To clarify the problem a bit. I'm working on a project to integrate a template engine with Spring MVC. The project consists of the following categories / parts:
Configuration
Annotation e.g. EnableFeature (imports configuration)
View
ViewResolver
Template factory
Logically all class categories could exist in the web application context. However, the template factory could be used by other services as well (e-mail, etc.). Services that mostly exist in the root context. So what I'm basically asking is, how can I make the factory available to the root context in a clean way. I would like the configuration required to be as low as possible. As of now the setup only requires one annotation placed on top of the dispatcher configuration class.
It took me some time to clearly understand what you want to do and the implications beyond. Finding the root application context from the servlet one would be the easy part, context.getParentBeanFactory(), or directly context.getParent() gives it immediately in any ApplicationContextAware class, or through direct injection of the ApplicationContext.
The hard part, is that at the time of initialization of the servlet application context, the root application context has already been refreshed. If I look at what happens in Tomcat :
at deploy time the root application context is fully initialized and refreshed
next, at first request for the DispatcherServlet the child context is initialized with root context as parent.
That mean that when servlet context is initialized, it is way too late to inject beans in root context : all singleton beans have already been created.
There may be workaround, all with their own flaws :
register a new Configuration class in parent context and do a new refresh().IMHO, this would be the least bad solution as normally WebApplicationContextes support multiple refresh. The potentials problems are :
all other beans must be initialized once without the new beans : the configuration must be tolerant to non existing beans
other components (filters, security, DAO, etc.) may already be running, and they have to be thoroughly tested against a hot context refresh
create an intermediate ApplicationContext with current root as parent containing beans that should not go into servlet application context, and then create the servlet application context with this intermediate context as parent.
no problem for the root application context that is not even aware of the whole operation
but no bean of root context can be injected with any new bean
register all new beans directly in root context. Ok, all is fine for root initialization, and beans of servlet context will have access to all beans. But if one new bean need to be injected with a bean from servlet context, you will have to to it manually at servlet context initialization, with careful tests (or prayers) that it cannot be used before that ... and you will have some pollution of root context with beans only relevant for the servlet
use only only root context and an empty servlet context.
ok, each bean has access to any other one
but it breaks the separation between root and servlet context and adds some pollution to the root context
My conclusion is that having a single piece of configuration for 2 different application contextes is a little against Spring philosophy and I would advice you to keep with 2 separate configuration classes one for the root context and one for the servlet context. But if you wish, I can elaborate on the refreshing of root context from servlet context (1st solution).
If you want to inject beans into root context from a feature that would be declared in servlet context to have a single configuration point, you can use something like:
#Configuration
public class FeatureConfig implements ApplicationContextAware {
static boolean needInit = true;
#Override
// Register the configuration class into parent context and refreshes all
public void setApplicationContext(ApplicationContext ac) throws BeansException {
AnnotationConfigWebApplicationContext parent =
(AnnotationConfigWebApplicationContext) ((AnnotationConfigWebApplicationContext) ac).getParent();
if (needInit) { // ensure only one refresh
needInit = false;
parent.register(RootConfig.class);
parent.refresh();
((AnnotationConfigWebApplicationContext) ac).refresh();
}
}
#Configuration
#Conditional(NoParentContext.class)
// Can only be registered in root context
public static class RootConfig {
// configuration to be injected in root context ...
}
// special condition that the context is root
public static class NoParentContext implements Condition {
#Override
public boolean matches(ConditionContext cc, AnnotatedTypeMetadata atm) {
logger.debug(" {} parent {}", cc.getBeanFactory(), cc.getBeanFactory().getParentBeanFactory());
return (cc.getBeanFactory().getParentBeanFactory() == null);
}
}
// other beans or configuration that normally goes in servlet context
}
With such a #Configuration class it is enough to have a #import(FeatureConfig.class) annotation in a configuration class for the application context of the DispatcherServlet.
But I could not find any way to allow the configuration to happen before the normal servlet application context initialisation. An outcome is that any bean from the special configuration can only be injected in root context with a #Autowired(required=false), because the root context will be refreshed twice, first time without the special configuration class, second time with it.
As I understand all you have to do is to provide custom configuration that will be imported by annotating #Configuration class by #EnableFeature. Then you just have to include custom beans in your EnableFeatureConfiguration class.
#Configuration
public class EnableFeatureConfiguration {
#Bean
public MyBean myBean() {
return MyBean();
}
}
Then your EnableFeature looks like:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(EnableFeatureConfiguration.class)
public #interface EnableFeature {
}
And that's all. In project you have to use:
#Configuration
#EnableFeature
public class MySpringConfig {
}

Failing to #AutoWire a member in a #WebServlet

I can't seem to get my servlet's fields to #AutoWire; they end up null. I have a pure annotation-configured webapp (no XML files). My servlet looks like this:
#WebServlet("/service")
public
class
SatDBHessianServlet
extends HttpServlet
{
#Autowired protected NewsItemDAO mNewsItemDAO;
}
Other #AutoWired things seem to work fine, both #Service objects and #Repository objects. But not this one, and I can't figure out why. I even tried adding its package to the ComponentScan(basePackages) list for my other classes.
Additional Info:
I added the following to my servlet’s init() method, and everything seemed to wire up properly, but I'm confused as to why Spring can't wire it up without that.
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, inConfig.getServletContext());
Servlet are web components that are not being created by Spring container, based on that lifecycle is not managed by the container and a lot of stuff that spring provides such as autowired or aspect can not run from them.
You need to indicate to the spring container that a component was created outside of IoC container and need to be part of it.
And as the API said SpringBeanAutowiringSupport is for:
Convenient base class for self-autowiring classes that gets
constructed within a Spring-based web application
This generic servlet base class has no dependency on the Spring ApplicationContext concept.
There is another way to indicate servlets being created by spring container using an interface
Spring MVC uses the DispatcherServlet for handling all requests in a Servlet environment.
The DispatcherServlet accordingly forwards the requests to the appropriate #Controller class (if any) based on the #RequestMapping on that class and/or it's methods.
What is happening in your Servlet is that it is not managed by Spring (but by the Servlet container) and there for no injection of dependencies is occurring.

Spring mvc on application load event to load collection

I have to load something from the database to initialize a hashMap object that I want to add to a application wide variable that all my mvc controllers can access.
I won't be modifying this object, and I don't care if it can only be updated when the application reloads.
Does spring MVC have a place where I can do this and have spring manage the lifecycle also?
I know servlets has the WebListener annotation:
#WebListener
But not sure if spring has this also (wrapped in their iOc container).
I think the best place is to declare a bean like the following:
public class MyInitializingBean {
#PostConstruct
private void init() {
//do initialization here
}
}
Then you declare that bean in your xml file, if you don't like the annotation, there are non-annotation approaches for initializing a bean, but that gets executed when the spring context starts.

Executing a Java class at application startup using Spring MVC [duplicate]

This question already has answers here:
Execute method on startup in Spring
(13 answers)
Closed 6 years ago.
What is the best way to execute a Java class at application startup using Spring MVC ?
There's not necessarily a "best" way. As usual, there are many ways to do it, and the "best" is whichever fits into your project the best:
Use init-method="..." on a bean element in XML, as cjstehno mentioned
Implement Spring's InitializingBean interface. When deployed in an ApplicationContext, the afterPropertiesSet() method will be called when the bean is created.
Annotate a method on a bean with #PostConstruct. Again, if deployed to an ApplicationContext, the annotated method will be called when the bean is created.
If your bean is more of an infrastructure bean to be tied into the Spring lifecycle, implement ApplicationListener<ContextRefreshedEvent>. The onApplicationEvent(..) method will be called during Spring's startup, and you can do whatever work you need there.
Assuming your context is loaded on startup, create a bean in your spring application context with an init method explicitly called out in the XML config (or implement Springs InitializingBean). If you have lazy-loading enabled you will need to make sure this bean is not lazy.
<bean name="starter" init-method="start" class="com.my.StarterBean" lazy="false" />
(please double-check the params in the docs).
If your context is not loaded on startup you can register an server context listener (part of Servlet API, not Spring).
You can use either implementations:
1) Implement interface InitializingBean. This approach is granted load all your beans then call afterPropertiesSet method.
#Override
public void afterPropertiesSet() throws Exception {
init();
}
2) Using JSR-250's Annotation #PostConstruct. This approach will not wait for spring beans to load.
#PostConstruct
public void init() {
}

Categories