Spring:无法从父上下文自动装配 bean

Posted

技术标签:

【中文标题】Spring:无法从父上下文自动装配 bean【英文标题】:Spring: Cannot autowire beans from parent context 【发布时间】:2016-08-24 11:12:47 【问题描述】:

我有一个 Spring Boot (1.4.0) 应用程序,它在初始化期间启动第二个上下文(我需要它,因为我必须使用特定类型的授权发布 Web 服务,而父上下文发布不同的服务)。

我像这样创建了一个子上下文:

@Configuration
@ConditionalOnClass(Servlet.class, DispatcherServlet.class)
@ConditionalOnWebApplication
public class ChildContextConfiguration implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> 

    private final Logger logger = LoggerFactory.getLogger(ChildContextConfiguration.class);
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
        this.applicationContext = applicationContext;
    

    private void createChildContext() 
        final AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext(ChildConfiguration.class);
        childContext.setParent(this.applicationContext);
        childContext.setId(this.applicationContext.getId() + ":child");
    

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) 
        logger.info("creating child context");
        createChildContext();
    

子上下文的配置类如下所示:

@Configuration
@ComponentScan(basePackages = "com.example.child")
@PropertySource("file:some-config.properties")
@ConfigurationProperties(prefix = "child")
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class)
public class ChildConfiguration 

    private Integer port;
    private String keyStore;
    private String keyStorePass;
    private String keyPass;
    private String trustStore;
    private String trustStorePass;
    private String packageBase;

    public void setPort(Integer port) 
        this.port = port;
    

    public void setKeyStore(String keyStore) 
        this.keyStore = keyStore;
    

    public void setKeyStorePass(String keyStorePass) 
        this.keyStorePass = keyStorePass;
    

    public void setKeyPass(String keyPass) 
        this.keyPass = keyPass;
    

    public void setTrustStore(String trustStore) 
        this.trustStore = trustStore;
    

    public void setTrustStorePass(String trustStorePass) 
        this.trustStorePass = trustStorePass;
    

    public void setPackageBase(String packageBase) 
        this.packageBase = packageBase;
    

    @Bean
    public Jaxb2Marshaller swpMarshaller() 
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setPackagesToScan(packageBase);
        return marshaller;
    

    @Bean
    public Unmarshaller swpUnmarshaller() throws JAXBException 
        JAXBContext jaxbContext = JAXBContext.newInstance(packageBase);
        return jaxbContext.createUnmarshaller();
    

    @Bean
    public Filter encodingFilter() 
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        return encodingFilter;
    

    @Bean
    public ServerProperties serverProperties() 
        ServerProperties props = new ServerProperties();
        props.setPort(port);
        props.setSsl(ssl());

        return props;
    

    private Ssl ssl() 
        Ssl ssl = new Ssl();
        ssl.setEnabled(true);
        ssl.setKeyStore(keyStore);
        ssl.setKeyStorePassword(keyStorePass);
        ssl.setKeyStoreType("JKS");
        ssl.setKeyPassword(keyPass);
        ssl.setTrustStore(trustStore);
        ssl.setTrustStorePassword(trustStorePass);
        ssl.setClientAuth(Ssl.ClientAuth.NEED);


        return ssl;
    

到目前为止,这是可行的。但是当我尝试从父上下文中自动装配一个 bean 时,我收到一条错误消息,指出没有候选者。

另一个有趣的事情是,当我使用 ApplicationContextAware 接口将(子)上下文注入到我的子上下文的一个 bean 中时,该上下文的 getParent() 属性当时为空。

我现在所做的是实现如下 getter 函数:

private SomeBean getSomeBean() 
    if (this.someBean == null) 
        this.someBean = applicationContext.getParent().getBean(SomeBean.class);
    
    return this.someBean;

总结一下:在构建子上下文的bean期间,没有设置父上下文,所以我不能使用自动装配。

有什么方法可以让我的设置使用 autowire 吗?

【问题讨论】:

【参考方案1】:

构造函数在内部注册刷新上下文 - 尝试设置类并在设置父上下文后手动刷新。

private void createChildContext() 
    final AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext();
    childContext.setParent(this.applicationContext);
    childContext.setId(this.applicationContext.getId() + ":child");
    childContext.register(ChildConfiguration.class);
    childContext.refresh();

【讨论】:

这无疑是朝着正确方向迈出的一步。不幸的是,我陷入了刷新循环。第一个 ContextRefreshedEvent 是由父上下文触发的,以下是来自子上下文的。所以我只在根上下文发出事件时才调用 createChildContext() 。这似乎可行,但随后子上下文的 tomcat 尝试使用 SSL(为子上下文配置,而不是为父级配置)绑定在根上下文的端口上 更新:当我注释掉 childContext.setParent(this.applicationContext);我只得到一个 ContextRefreshedEvent 并且其他一切都有效(除了从父上下文访问 bean,因为没有)。 我将一个示例项目推送到 github:github.com/thomashoell/SpringBootChildContext。有趣的部分位于 ChildContextConfig 类中。当我设置父上下文 BEFORE 调用刷新时,它不起作用。从我注意到的情况来看,子上下文似乎使用了父上下文的 tomcat。 目前的问题是什么? - 您的示例通过了单一测试,并且成功运行 - 在 9090 和 9091 上有两个 Web 侦听器。 在应用程序的当前状态下,我在子上下文上调用 refresh() 后设置了父上下文。它启动了,但我不能使用自动装配,因为在子上下文启动期间未设置父上下文。当您更改订单时 (setParent() before refresh()) ,或调用 createChildContext_failing();而不是 createChildContext_working();在 ChildContextConfig 中,应用程序不会启动。

以上是关于Spring:无法从父上下文自动装配 bean的主要内容,如果未能解决你的问题,请参考以下文章

Spring之Bean的自动装配

Spring-bean的自动装配

Spring随笔-bean装配-自动装配

Spring学习Bean自动装配与注解开发

Spring笔记:bean的自动装配

3.Spring自动装配