一文详解Spring 中的顺序问题

 更新时间:2023年05月04日 15:19:06   作者:Fire Fish  
本文主要介绍了Spring 中的顺序问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Spring 中的顺序问题并不是不重要,比如多个 BeanFactoryPostProcessor如何知道先执行哪一个;为什么自定义的 Bean 可以覆盖默认的自动装配的 Bean;AOP 拦截器链中拦截器的顺序是如何确定的等等问题。

相信看完这篇文档你应该能够对 Spring 的顺序问题有一些理解!

结论:总体上是无序的,但局部是有顺序的

1、有特殊接口或注解,就用它的顺序

特殊接口或注解就是:PriorityOrdered 接口、Ordered 接口、@Priority 注解、@Order 注解

2、没有,就用 beanDefinitionNames 顺序

那么什么决定了 beanDefinitionNames 的顺序呢???在下文中有说明

下面会列举一些 Spring 中的一些常见的顺序代码来分析

举例 1:Spring 注册非懒加载的的 Bean

// DefaultListableBeanFactory.class
public void preInstantiateSingletons() throws BeansException {
    // <1> 收集beanDefinitionNames集合
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    for (String beanName : beanNames) {
        // <2> 依次注册 Bean
        getBean(beanName);
    }
}
  • <1> 处,收集 BeanDefinition 集合
  • <2> 处,实例化的顺序总体上是跟注册的顺序一致,但是实例化过程中处理属性依赖构造器参数依赖等等依赖时,会先对依赖的 Bean 进行实例化!

举例 2:Spring 处理 BeanFactoryPostProcessor 接口

// PostProcessorRegistrationDelegate.class
public static void invokeBeanFactoryPostProcessors(
    ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    // <1> 收集BeanDefinitionRegistryPostProcessor集合
    String[] postProcessorNames =
        beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    for (String ppName : postProcessorNames) {
        // <2> 优先处理 PriorityOrdered 接口
        if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
            processedBeans.add(ppName);
        }
    }
    // <3> 再处理 Ordered 接口的
    for (String ppName : postProcessorNames) {
        if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
        }
    }
    // <4> 最后处理不是这 2 个接口的
}
  • <1> 处,收集 BeanDefinitionRegistryPostProcessor 集合,但是是根据注册的顺序(即 beanDefinitionNames )收集的
  • <2> 处,优先处理 PriorityOrdered 接口
  • <3> 处,再处理 Ordered 接口的
  • <4> 处,最后处理不是这 2 个接口的 举例 3:AnnotationAwareOrderComparator 注解排序比较

作用:该比较器的作用是对标注了注解的内容进行排序,@Priority 优先 @Order,同注解要看 value 大小

在 Spring 中几乎所有需要用到比较的地方都会使用该注解比较器,如:

在实例化 ApplicationContextInitializer 接口时,代码及注释如下:

// 代码位置:AbstractContextLoader.class
private void invokeApplicationContextInitializers(ConfigurableApplicationContext context,
                                                  MergedContextConfiguration mergedConfig) {
	... ...略
    // <1> 对 initializerInstances 排序
    AnnotationAwareOrderComparator.sort(initializerInstances);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
        // <2> 比较后挨个实例化
        initializer.initialize(context);
    }
}

loadFactories 方法从配置中加载和实例化指定类型的类,代码及注释如下:

// 代码位置:SpringFactoriesLoader.class
// 功能:加载配置中的 factoryClass 类型的定义
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
    // <1> 加载factoryClass 类型的列表
    List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    List<T> result = new ArrayList<>(factoryNames.size());
    for (String factoryName : factoryNames) {
        // <2> 逐个实例化
        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    }
    // <3> 排序
    AnnotationAwareOrderComparator.sort(result);
    return result;
}

AOP 进行排序时,代码及注释如下:

// 代码位置:AbstractAdvisorAutoProxyCreator.class
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // <1> 查找候选的 Advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // <2> 适配当前 beanClass 的 Advisor
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // <3> 对 Advisor 排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

什么决定了 beanDefinitionNames 的顺序

需要了解的背景知识

1、Spring 的启动流程、Spring Boot 的启动流程

2、Spring Boot 自动装配原理

3、BeanFactoryPostProcessor 后置处理器是如何工作的,特别的重点掌握 ConfigurationClassPostProcessor

注册过程简单分析过程

因为 beanDefinitionNames 属性只能通过如下代码添加(有且仅有这一处 add 方法)

// 代码位置:DefaultListableBeanFactory.class
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    throws BeanDefinitionStoreException {
    // 注册
    this.beanDefinitionNames.add(beanName);
}

所以该属性的顺序主要与 this.registry.registerBeanDefinition 方法的调用相关

ConfigurationClassPostProcessor 简单分析

调用容器registry来注册 BeanDefinition 的代码省略如下:

// 代码位置:ConfigurationClassPostProcessor.class
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    do {
        // <1> 解析 candidates
        parser.parse(candidates);
        // <2> 获取【有序的】配置类集合
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        // <3> 处理配置类
        this.reader.loadBeanDefinitions(configClasses);
    }
    while (!candidates.isEmpty());
}
  • <1> 处,最开始一般 candidates 只有一个类就是 @SpringApplication 注解标注的类;然后在 parse 方法内部经历了各种各种递归调用处理完所有的配置类
  • <2> 处,获取 parse 阶段解析好的所有配置类
  • <3> 处,处理【有序的配置类】的注册,注册到 beanDefinitionNames 属性中

基于以上的简单分析给出如下结论

注册顺序的结论

以以下代码举例来说明 beanDefinitionNames 的注册顺序

@SpringBootApplication
@EnableCaching
@EnableTransactionManagement
@MapperScan(basePackages = "cn.iocoder.springboot.lab21.cache.mapper")
public class Application {
	public static void main(String[] args) {
        // 先处理 run 方法中传入的"配置类",即 Application 类
		SpringApplication.run(Application.class);
	}
}

Application 优先级最高

因为一般只有唯一一个 candidates 类,即启动类 Application,它是配置类 Configuration 注册的入口

其次是 Application 类所在的包

处理各种 ImportSelector 导入的类

先处理 @EnableCaching 注解、再处理 @EnableTransactionManagement 注解、再处理 @MapperScan 注解

最后是自动装配的类

备注:

1、排序在前面的 Configuration 类会先被注册

2、Configuration 的顺序是可能控制的,如可通过 @AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder等方式

解释一些顺序相关的问题

因为有了上面的结论,我们就可以尝试解决一些顺序相关的问题

问题 1:我们自定义的 Bean 存在时,不会加载默认的 Bean,只有我们自定义 Bean 不存在时才会加载默认的 Bean

答案:按照上面的注册顺序,先扫描我们自定义的包,所以我们自定义的 Bean 先被注册;而在自动装配时可能使用了@ConditionalOnBean等条件注解,从而导致条件不满足,就不再注册默认的 Bean

问题 2:注册的顺序影响大吗

答案:

1、有很大影响,除了标注或实现特殊注解或接口的类没有影响外,其他类都会受到一定的影响

2、很难全面把握 Spring Boot 自动装配的顺序,这也是导致 Spring Boot 比较晦涩难懂的一方面

留下一个疑问供各位看官答疑

问题 :如下@EnableCaching注解、@EnableTransactionManagement的先后顺序对 2 种拦截器的顺序有影响???

方式 1

@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
@MapperScan(basePackages = "cn.iocoder.springboot.lab21.cache.mapper")
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class);
	}
}

image-20230429043546519

方式 2

@SpringBootApplication
@EnableCaching
@EnableTransactionManagement
@MapperScan(basePackages = "cn.iocoder.springboot.lab21.cache.mapper")
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class);
	}
}

image-20230429043748409

答案:从截图的结果上来看是有影响的,为啥呢

说明:

1、因为@EnableCaching和@@EnableTransactionManagement都是通过 ImportSelector 机制,上面的注解会先被处理;所以方式 1 的事务会被先注册,缓存会被后注册

2、其次虽然在 AOP 会被 Advisor 进行排序

// 代码位置:AbstractAdvisorAutoProxyCreator.class
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // <1> 查找候选的 Advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // <2> 适配当前 beanClass 的 Advisor
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // <3> 对 Advisor 排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

但是事务的BeanFactoryTransactionAttributeSourceAdvisor 和缓存的BeanFactoryCacheOperationSourceAdvisor均没有顺序接口或注解,所以排序失效,注册的顺序就是 Advisor 的顺序

到此这篇关于一文详解Spring 中的顺序问题的文章就介绍到这了,更多相关Spring 顺序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot拦截器的使用小结

    SpringBoot拦截器的使用小结

    今天给大家总结一下SpringBoot下拦截器的使用,需要的朋友参考下吧
    2017-05-05
  • Java @Autowired报错原因分析和4种解决方案

    Java @Autowired报错原因分析和4种解决方案

    这篇文章主要介绍了Java @Autowired报错原因分析和4种解决方案,文章围绕主题展开详细内容介绍,具有一定的参考价值,需要的小伙伴可以参考以一下
    2022-05-05
  • Java String类用法详解

    Java String类用法详解

    今天给大家带来的是关于Java的相关知识,文章围绕着Java String类用法展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • SpringBoot内置tomcat调优测试优化

    SpringBoot内置tomcat调优测试优化

    这篇文章主要介绍了SpringBoot内置tomcat调优测试优化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • SpringBoot 图形验证码的生成和校验

    SpringBoot 图形验证码的生成和校验

    随着系统和业务的不停升级,前后端代码放在一起的项目越来越臃肿,已经无法快速迭代和职责区分了,于是纷纷投入了前后端分离的怀抱,发现代码和职责分离以后,开发效率越来越高了,但是以前的验证码登录方案就要更改了。本文来看一下SpringBoot 图形验证码的生成和校验
    2021-05-05
  • 浅谈JDK7和JDK8的区别在哪

    浅谈JDK7和JDK8的区别在哪

    面试总是遇到这个问题,做一个小总结,文中有非常详细的介绍,对正在学习java的小伙伴们很有帮助,需要的朋友可以参考下
    2021-06-06
  • 详解Spring中的@Scope注解

    详解Spring中的@Scope注解

    这篇文章主要介绍了详解Spring中的@Scope注解,@Scope注解是Spring IOC容器中的一个作用域,在Spring IOC容器中,他用来配置Bean实例的作用域对象,需要的朋友可以参考下
    2023-07-07
  • java swing 实现加载自定义的字体

    java swing 实现加载自定义的字体

    这篇文章主要介绍了java swing 实现加载自定义的字体,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java线程的生命周期命名与获取代码实现

    Java线程的生命周期命名与获取代码实现

    这篇文章主要介绍了Java线程的生命周期命名与获取代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • 在Windows系统下安装Thrift的方法与使用讲解

    在Windows系统下安装Thrift的方法与使用讲解

    今天小编就为大家分享一篇关于在Windows系统下安装Thrift的方法与使用讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12

最新评论