spring动态创建bean:动态创建方式的时机影响(一)
1.前言由于所在公司的的现有的开发框架要改造成基于SpringBoot构建,以此来兼容SringBoot的宽泛的技术积累,在此结合过程中,需要根据配置来动态生成Bean,但发现一些方式生成的bean会导致如@ConditionalOnBean等注解的失效,这里记录一下过程。2.如何动态创建beanSpring在进行bean的实例化之前都会在BeanFactory中注册成一个个的BeanDefini
1.前言
由于所在公司的的现有的开发框架要改造成基于SpringBoot构建,以此来兼容SringBoot的宽泛的技术积累,在此结合过程中,需要根据配置来动态生成Bean,但发现一些方式生成的bean会导致如@ConditionalOnBean等注解的失效,这里记录一下过程。
2.如何动态创建bean
Spring在进行bean的实例化之前都会在BeanFactory中注册成一个个的BeanDefinition
,这些BeanDefinition包含了bean创建的属性,比如单例或者多例,以及设置Bean创建的过程的bean创建工厂方法。也就是说,只要把这些BeanDefinition注册到BeanFactory中,就能通过getBean的方式获取到bean的实例。刚开始我的方式简写后是这样的:
2.1第一种
准备一个要被动态创建的Bean类
package org.test;
/**
* @author LHL
* @version 1.0
* @Description
* @date 2021/9/20 12:08
* @since
*/
public class Mybean {
private String des ="this is a Bean";
@Override
public String toString() {
return "Mybean{" + "des='" + des + '\'' + '}';
}
}
准备用来注册和获取bean的类
package org.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
/**
* @author LHL
* @version 1.0
* @Description
* @date 2021/9/20 12:07
* @since
*/
@Configuration
public class RegisterAndGetBean {
@Autowired
private ApplicationContext applicationContext;
public void registerBean() {
System.out.println("registerBean");
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Mybean.class);
BeanDefinition beanDefinition = null;
beanDefinition = beanDefinitionBuilder.getBeanDefinition();
((DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory())
.registerBeanDefinition("myBean", beanDefinition);
}
public void getBean() {
System.out.println("getBean");
applicationContext.getBean(Mybean.class).toString();
}
}
然后动态注册和获取
package org.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
/**
* @author LHL
* @version 1.0
* @Description
* @date 2021/9/19 22:02
* @since
*/
@SpringBootApplication
@Configuration()
public class AppMain {
@Autowired
private RegisterAndGetBean registerAndGetBean;
public static void main(String[] args) {
new SpringApplication(AppMain.class).run(args);
}
@EventListener
public void started(ApplicationStartedEvent startedEvent) {
registerAndGetBean.registerBean();
registerAndGetBean.getBean();
}
}
运行的结果
registerBean
getBean
Mybean{des='this is a Bean'}
显然这种方式是正常的使用ioc的,但是注册Bean的时机太靠后了,也就会导致如连@Autowired这种注解注入bean的方式也会失效:
如下:
@SpringBootApplication
@Configuration()
@SpringBootApplication
@Configuration()
public class AppMain {
@Autowired
private RegisterAndGetBean registerAndGetBean;
public static void main(String[] args) {
new SpringApplication(AppMain.class).run(args);
}
@Autowired
private Mybean mybean;
@EventListener
public void started(ApplicationStartedEvent startedEvent) {
registerAndGetBean.registerBean();
registerAndGetBean.getBean();
mybean.toString();
}
}
输出的会是
Action:
Consider defining a bean of type 'org.test.Mybean' in your configuration.
2.1第二种
所以上面的方式显然是不行的,时候我尝试着提前创建Bean定义的时机,根据Spring的生命周期,我们可以在实例化之前,通过Spring的扩展接口:Bean定义后置处理器,即BeanDefinitionRegistryPostProcessor来注册Bean定义。这个时候bean都没有实例化,在即将实例化之前会调用这个接口。代码如下:
package org.test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Configuration;
/**
* @author LHL
* @version 1.0
* @Description
* @date 2021/9/20 12:48
* @since
*/
@Configuration
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(Mybean.class);
BeanDefinition beanDefinition = null;
beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinitionRegistry.registerBeanDefinition("Mybean", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException {
}
}
然后获取的方式改成
@SpringBootApplication
@Configuration()
public class AppMain {
@Autowired
private RegisterAndGetBean registerAndGetBean;
public static void main(String[] args) {
new SpringApplication(AppMain.class).run(args);
}
@Autowired
private Mybean mybean;
@EventListener
public void started(ApplicationStartedEvent startedEvent) {
System.out.println(mybean.toString());
}
}
运行结果
Mybean{des='this is a Bean'}
现在,可以正常使用@Autowired的功能了,但是这时我们试试其他的注解能不能正常使用,比如说@ConditionalOnBean
我尝试改成如下
@SpringBootApplication
@Configuration()
public class AppMain {
@Autowired
private RegisterAndGetBean registerAndGetBean;
public static void main(String[] args) {
new SpringApplication(AppMain.class).run(args);
}
@ConditionalOnBean(Mybean.class)
@Bean
public String hello(){
System.out.println("hello ConditionalOnBean");
return "";
}
@Autowired
private Mybean mybean;
// @EventListener
// public void started(ApplicationStartedEvent startedEvent) {
// System.out.println(mybean.toString());
// }
}
如果在我预想的情况下,会成功调用hello()方法,但事实是没有,那也就是说spring在进行@ConditionalOnBean是判断到没有这个bean,那么,是不是我的时机还不够靠前,导致做此判断是还没有运行到我的后置处理器,我打在 @ConditionalOnBean的处理方法处打了个断点:
发现正是我想的那样,他更靠前,我查询源码,发现最终的根源出现在@ConditionalOnBean做判断时,起源调用点是从ConfigurationClassPostProcessor
开始的,它也是一个后置处理器,但此处理器比我定义的更靠前,那现在解决方式就有了,只要我在它之前就可以注册好我的bean定义就可以了,于是我进一步翻看源码,想寻找一个后置处理器是什么时候加入处理器链的,追踪找到了如下代码
调用栈如下:
这样的话,这个后置处理器是spring在容器中默认扫描加入的也就是说:我现在的方式是无法正常能做到比他靠前的,@Order这种也不行,那开源的一些著名框架是怎么做到的?我参考了mybatis的自动配置启动器。
发现了这样一段代码:
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors())
// Need to mybatis-spring 2.0.2+
.filter(x -> x.getName().equals("lazyInitialization")).findAny()
.ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
是的,这里也动态创建了bean定义,那这个后置处理器是如何被注册的?找到注册点发现代码如下:
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
2.1第二种
这也就引出了第三种,这里在@Configuration注解配置使用了@Import(),同时它的后置处理器实现的是ImportBeanDefinitionRegistrar ,这种方式的时机要更靠前么?我将自己动态定义bean的代码改造成了如下:
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author LHL
* @version 1.0
* @Description
* @date 2021/9/20 13:10
* @since
*/
public class MyBeanImportDefinitionRegistryPostProcessor implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//构造bean定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(Mybean.class);
BeanDefinition beanDefinition = null;
beanDefinition = beanDefinitionBuilder.getBeanDefinition();
registry.registerBeanDefinition("Mybean", beanDefinition);
}
}
@Configuration
//这里引入
@Import(MyBeanImportDefinitionRegistryPostProcessor.class)
public class RegisterAndGetBean {
}
@SpringBootApplication
@Configuration
public class AppMain {
@Autowired
private RegisterAndGetBean registerAndGetBean;
public static void main(String[] args) {
new SpringApplication(AppMain.class).run(args);
}
@Bean
@ConditionalOnBean(Mybean.class)
public String hello(){
System.out.println("hello ConditionalOnBean");
return "";
}
@Autowired
private Mybean mybean;
// @EventListener
// public void started(ApplicationStartedEvent startedEvent) {
// System.out.println(mybean.toString());
// }
}
然后调用输出:
hello ConditionalOnBean
发现可以正常使用了,虽然这里可以正常使用了,但是有给新的疑问点,当我把hello()移动到@Configuration 配置类RegisterAndGetBean 时,会发现它失效了。
@Configuration
@Import(MyBeanImportDefinitionRegistryPostProcessor.class)
public class RegisterAndGetBean {
@Bean
@ConditionalOnBean(Mybean.class)
public String hello(){
System.out.println("hello ConditionalOnBean");
return "";
}
}
这里分两个,第一步,为什么这种方法能成功,第二步,为什么在hello()放到里面会失效.
第一步
探究源码发现,之所以能成功是在ConfigurationClassPostProcessor
中针对ImportBeanDefinitionRegistrar 做了扩展处理,源码如下:
这里会再次收集bean定义,也就是能在ConfigurationClassPostProcessor
过程动态注册好bean的原因。
第二步
那为什么放在内部就无法成功了?通过源码探究如下:
首先找到具体匹配的逻辑源码
发现当发现失效的情况下,是先进行判断逻辑再去动态生成bean的,那么表面原因就找到了,那根本原因时什么,最后找到了如下代码:
上面那个箭头会获取内部方法bean定义并进行bean存在判断,第二个方法会import动态bean后置处理器,会发现,如果你@import所在的Confuguration在你的 @ConditionalOnBean后的话,那么将会因为先后顺序导致判断失败,也就是这个是否我们只要想办法调整下@Confuguration类的顺序就可以了,把@import所在的@Confuguration类调的更靠前,进一步找到了处理链的存放容器,如下:
再往上查找:
最后一直查找发现并没有什么能决定顺序,是通过扫描的包名和类名类决定的,这样的不可控因素太多,显然是不行的。那就只能从@Configuration的创建方式进行解决了,经了解一般有如下创建方法:
- 手动:构建ApplicationContext时由构建者手动传入,可手动控制顺序。高优先级
- 自动1:被@ComponentScan自动扫描进去,无法控制顺序。高优先级
- 自动2:通过SPI的方式,可以控制顺序,低优先级。
那也就是最后的解决方案如下:
- 如果是EnableAutoConfiguration可以通过@AutoConfigureBefore的方式
- 第二种就是通过手动指定的方式:
ApplicationContext context = new AnnotationConfigApplicationContext(Config1.class, Config2.class);
也就是只能在局限的范围内决定先后顺序…
总结
spring的bean的注册时机对bean的管理影响很大,如果是如@ConditionalOnBean这些注解,还是不要用在@Component及其子类的判断上,因为先后顺序的不确定性会导致系统的不稳定,同时如果这个bean是在SPI的方式bean里创建的,一定是判断不通过的。
更多推荐
所有评论(0)