Reputation: 17361
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?
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:
EnableFeature
(imports configuration)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.
Upvotes: 4
Views: 1395
Reputation: 148965
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 :
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 :
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.
Upvotes: 3
Reputation: 14149
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 {
}
Upvotes: 0