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

Posted

技术标签:

【中文标题】使用单个注释在根和调度程序应用程序上下文中自动配置 bean【英文标题】:Automatically configure beans in root and dispatcher application context with single annotation 【发布时间】: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 我希望现在更清楚了:) 【参考方案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 会是一些公共框架项目还是只是自定义解决方案? 我不确定我是否理解您的问题,但这将是一个公共解决方案。不知道我是否可以称它为框架:)

以上是关于使用单个注释在根和调度程序应用程序上下文中自动配置 bean的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JAVA 在 Blob 存储容器中一起下载存储在根和子文件夹中的多个文件?

如何在 Spring Boot 中将 keycloak 与不同的上下文根和反向代理集成

使用应用程序上下文播放 2.4.x Akka 调度程序

根上下文和调度程序 servlet 上下文到底是如何进入 Spring MVC Web 应用程序的?

在进程调度的上下文中调度程序和调度程序有啥区别

如何检查spring boot应用程序在自动配置类中是不是有某个注释