使用单个注释在根和调度程序应用程序上下文中自动配置Bean

时间:2014-08-22 10:10:44

标签: java spring spring-mvc applicationcontext spring-java-config

我们说我创建了一个名为@EnableFeauture的注释,它导入了一个bean配置类EnableFeatureConfiguration。此注释通常位于调度程序配置之上。像视图解析器等必须的bean属于该调度程序配置,但是一些bean实际上属于根上下文。

如何在不需要其他注释的情况下定义这些bean?我的第一个想法是自动装配WebApplicationContext并致电context.getParentBeanFactory()来注册豆类,但我不确定这是否是实现目标的最佳方式。这通常是怎么做的?


更新

稍微澄清一下这个问题。我正在开发一个项目,将模板引擎与Spring MVC集成在一起。该项目包括以下类别/部分:

  • 配置
  • 注释,例如EnableFeature(导入配置)
  • 查看
  • 的ViewResolver
  • 模板工厂

逻辑上,所有类类别都可以存在于Web应用程序上下文中。但是,模板工厂也可以被其他服务使用(电子邮件等)。主要存在于根上下文中的服务。所以我基本上要问的是,如何以干净的方式使工厂可用于根上下文。我希望配置尽可能低。截至目前,设置只需要在调度程序配置类的顶部放置一个注释。

2 个答案:

答案 0 :(得分:3)

我花了一些时间来清楚地了解你想做什么以及超越的含义。从servlet中查找根应用程序上下文可以是一个简单的部分,context.getParentBeanFactory(),或直接context.getParent()在任何ApplicationContextAware类中立即提供它,或者直接注入ApplicationContext }。

困难的部分是,在初始化servlet应用程序上下文时,根应用程序上下文已经刷新。如果我看看Tomcat会发生什么:

  • 在部署时,根应用程序上下文已完全初始化并刷新
  • 接下来,首先请求DispatcherServlet初始化子上下文作为父上下文。

这意味着当初始化servlet上下文时,在根上下文中注入bean是为时已晚:所有单例bean都已创建。

可能有解决方法,都有自己的缺陷:

  • 在父上下文中注册一个新的Configuration类并执行一个新的refresh()。恕我直言,这将是最不好的解决方案,因为通常WebApplicationContextes支持多次刷新。潜在的问题是:
    • 所有其他bean必须在没有新bean的情况下初始化一次:配置必须能够容忍非现有bean
    • 其他组件(过滤器,安全性,DAO等)可能已在运行,并且必须针对热上下文刷新进行全面测试
  • 创建一个中间ApplicationContext,当前root作为包含bean的父级,不应该进入servlet应用程序上下文,然后使用此中间上下文作为父级创建servlet应用程序上下文。
    • 对于根本不了解整个操作的根应用程序上下文没有问题
    • 但根本上下文的bean不能注入任何新bean
  • 直接在根上下文中注册所有新bean。好的,一切都适用于root初始化,servlet context的bean可以访问所有bean。但是如果需要从servlet上下文中注入一个bean,那么你必须在servlet上下文初始化时手动注入它,仔细测试(或祈祷)它之前不能使用它......你会得到一些使用仅与servlet相关的bean污染根上下文
  • 仅使用根上下文和空servlet上下文。
    • 好的,每个bean都可以访问任何其他的
    • 但它打破了root和servlet上下文之间的分离,并为根上下文添加了一些污染

我的结论是,针对2个不同的应用程序上下文进行单一配置有点违反Spring哲学,我建议您保留2个单独的配置类,一个用于根上下文,另一个用于servlet上下文。但是如果你愿意,我可以详细说明从servlet上下文刷新根上下文(第一个解决方案)。

如果要将bean从 feature 注入根上下文中,而 feature 将在servlet上下文中声明为具有单个配置点,则可以使用以下内容:

@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
}

使用这样的@Configuration类,就可以在@import(FeatureConfig.class)的应用程序上下文的配置类中使用DispatcherServlet注释。

但我找不到任何方法允许配置在正常的servlet应用程序上下文初始化之前发生。结果是来自特殊配置的任何bean只能在根上下文中注入@Autowired(required=false),因为根上下文将刷新两次,第一次没有特殊配置类,第二次使用它。

答案 1 :(得分:0)

据我所知,您所要做的就是提供自定义配置,该配置将通过@Configuration注释@EnableFeature类来导入。然后,您只需在EnableFeatureConfiguration类中包含自定义bean。

@Configuration
public class EnableFeatureConfiguration {

  @Bean
  public MyBean myBean() {
    return MyBean();
  }
}

然后你的EnableFeature看起来像:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableFeatureConfiguration.class)
public @interface EnableFeature {
}

就是这样。在项目中你必须使用:

@Configuration
@EnableFeature
public class MySpringConfig {
}