Bart
Bart

Reputation: 17361

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:

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

Answers (2)

Serge Ballesta
Serge Ballesta

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 :

  • 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.

Upvotes: 3

Jakub Kubrynski
Jakub Kubrynski

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

Related Questions