【问题标题】:Automatically configure beans in root and dispatcher application context with single annotation使用单个注释在根和调度程序应用程序上下文中自动配置 bean
【发布时间】:2014-08-22 10:10:43
【问题描述】:

假设我创建了一个名为@EnableFeauture 的注解,它导入了一个bean 配置类EnableFeatureConfiguration。此注释通常放置在调度程序配置的顶部。视图解析器等 bean 必须属于该调度程序配置,但一些 bean 确实属于根上下文。

如何在不需要其他注释的情况下定义这些 bean?我的第一个想法是自动连接WebApplicationContext 并调用context.getParentBeanFactory() 来注册bean,但我不确定这是否是实现我目标的最佳方式。这通常是如何完成的?


更新

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

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

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

【问题讨论】:

  • 很难理解您的问题。澄清一下:1)Spring ref 中的以下引用为您保留[...],您通常会通过 Spring 的 ContextLoaderListener 加载一个根 WebApplicationContext,并通过 Spring 的 DispatcherServlet 加载一个子 WebApplicationContext。? 2) 你想通过你自己的注释将额外的bean引入你的子上下文,一些新bean甚至是父上下文。如果是这样,你想如何区分? “您的注释中”的代码是否包含决定是在子级还是父级中注册 bean 的逻辑?
  • 您能否详细说明您要达到的目标(功能级别)?以及为什么你认为你需要一个自定义注释来做到这一点?
  • 查看我更新的问题。我希望这可以减少混乱
  • @Hille 我有一个ImportAware 配置,它是通过使用配置一些bean 的注释@EnableFeature 导入的。然而,一个 bean(模板工厂)将更适合根上下文。但我不确定如何以干净的方式实现这一目标。
  • @SergeBallesta 我希望现在更清楚了:)

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


【解决方案1】:

我花了一些时间才清楚地了解您想要做什么以及超越的含义。从 servlet 中查找根应用程序上下文将是很容易的部分,context.getParentBeanFactory() 或直接通过 context.getParent() 在任何 ApplicationContextAware 类中立即提供它,或通过直接注入 ApplicationContext

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

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

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

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

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

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

如果您想将 bean 从将在 servlet 上下文中声明为具有单个配置点的 feature 注入到根上下文中,您可以使用类似的东西:

@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 类,在DispatcherServlet 的应用程序上下文的配置类中添加@import(FeatureConfig.class) 注释就足够了。

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

【讨论】:

  • 我得出的结论是,尝试修改根上下文会更麻烦。但是,您对 @Conditional 注释的评论给了我一些方向,我喜欢这种灵活性。我现在允许将使用 @Bean 的 bean 添加到根配置中,并让 servlet 配置有条件地配置它自己的实例。如果您也可以在答案中扩展@Conditional,如果没有更好的解决方案出现(我怀疑会发生),我可能会接受它作为答案。 Here is a gist of my current solution.
  • 感谢您的时间和精力。我真的很感激。
  • @Bart :我可能有另一个使用 Java 配置的解决方案。我希望我能在几天内详细说明(包括@Conditional 用法的详细信息)
  • 酷!因此needIt 部分将防止多次注册,并使上下文远离连续刷新循环。一旦我得到改变,我就会尝试这个想法。享受赏金:)
【解决方案2】:

据我了解,您所要做的就是提供自定义配置,这些配置将通过 @EnableFeature 注释 @Configuration 类来导入。然后,您只需在 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 {
}

【讨论】:

  • 这正是我现在正在做的事情。问题是注释通常被放置在 Web 应用程序上下文配置的顶部(应该如此)。我正在寻找的是一种将特定 bean 放置在根上下文中的解决方案。使其可用于该根上下文中的服务。
  • @Bart 会是一些公共框架项目还是只是自定义解决方案?
  • 我不确定我是否理解您的问题,但这将是一个公共解决方案。不知道我是否可以称它为框架:)
猜你喜欢
  • 2016-06-27
  • 2014-05-18
  • 2011-11-21
  • 2015-08-31
  • 2012-08-17
  • 1970-01-01
  • 2015-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多