Spring 中的顺序问题(别迟疑就是你想知道的顺序问题)

这篇具有很好参考价值的文章主要介绍了Spring 中的顺序问题(别迟疑就是你想知道的顺序问题)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 中几乎所有需要对 Bean 排序的地方都会使用该注解比较器,如:

  • 在实例化 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

注册过程简单分析过程
  1. 因为 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 方法的调用相关

  1. 再看 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);
	}
}
  1. Application 优先级最高

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

  2. 其次是 Application 类所在的包

    也就是我们自定义的包扫描位置,即我们自定义的 Bean!

  3. 处理各种 ImportSelector 导入的类

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

  4. 最后是自动装配的类

    备注:

    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);
	}
}

方式 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);
	}
}

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

说明:

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 的顺序

传送门:保姆式Spring5源码解析

欢迎与作者一起交流技术和工作生活

联系作者文章来源地址https://www.toymoban.com/news/detail-430057.html

到了这里,关于Spring 中的顺序问题(别迟疑就是你想知道的顺序问题)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 有反爬机制就爬不了吗?那是你还不知道反反爬,道高一尺魔高一丈啊

    不知道你们在用爬虫爬数据的时候是否有发现,越来越多的网站都有自己的反爬机制,抓取数据已经不像以前那么容易,目前常见的反爬机制主要有以下几种: 数据是通过动态加载的,比如微博,今日头条,b站 需要登录,需要验证码,比如铁路12306,淘宝,京东 请求次数频

    2023年04月12日
    浏览(43)
  • 8个Python免费网站,一周熟练Python,知道就是赚到

    Python 已经成为一种再主流不过的编程语言了。 许多同学开始学习它,又不知道该如何入手,希望在一周内学习最强大和最流行的编程语言之一。 是的,你读得对,如果你有奉献精神, 你可以在一周内学习Python 。 今天呢,我给大家推荐 八个免费学习网站 ,大家赶紧码起来

    2024年02月10日
    浏览(39)
  • 哇~真的是你呀!今天是LINUX中的RSYNC服务

    目录 前言 一、概述 二、特性 三、rsync传输模式 四、rsync应用 五、格式 六、配置文件 七、守护进程传输 八、rsync+inotfy实时同步 rsync是linux 下一个远程数据同步工具;他可通过LAN/WAN快速同步多台主机间的文件和目录,并适当利用rsync 算法减少数据的传输;会对比两个文件的不同

    2024年02月16日
    浏览(43)
  • 🔥🔥你真的知道TCP协议中的序列号确认、上层协议及记录标识问题吗?

    在前面的内容中,我们已经详细讲解了一系列与TCP相关的面试问题。然而,这些问题都是基于个别知识点进行扩展的。今天,我们将重点讨论一些场景问题,并探讨如何解决这些问题。 当A主机与B主机建立了TCP连接后,A主机发送了两个TCP报文,分别大小为500和300字节。第一个

    2024年02月05日
    浏览(42)
  • 你真的知道TCP协议中的序列号确认、上层协议及记录标识问题吗?

    在前面的内容中,我们已经详细讲解了一系列与TCP相关的面试问题。然而,这些问题都是基于个别知识点进行扩展的。今天,我们将重点讨论一些场景问题,并探讨如何解决这些问题。 当A主机与B主机建立了TCP连接后,A主机发送了两个TCP报文,分别大小为500和300字节。第一个

    2024年01月18日
    浏览(36)
  • 客流量总是少?是你门店选址出了问题!

    零售行业最本质的需求就是降本增效、引流提销,实现利润最大化。如何利用大数据、人工智能、云计算、AIOT等前沿技术,助力企业数智化转型,全生态效率提升和可持续发展,是零售企业的核心诉求。 零售行业已进入大数据时代,数据分析能力将成为未来零售商的核心竞

    2024年01月17日
    浏览(68)
  • 数据结构—顺序表(如果想知道顺序表的全部基础知识点,那么只看这一篇就足够了!)

            前言:学习完了C语言的基础知识点之后,我们就需要使用我们所学的知识来进一步对存储在内存中的数据进行操作,这时候我们就需要学习数据结构。而这篇文章为数据结构中顺序表的讲解。 ✨✨✨ 这里是秋刀鱼不做梦的BLOG ✨✨✨ 想要了解更多内容可以访问我的

    2024年04月13日
    浏览(49)
  • async和await用法理解和快速上手 , 同步任务和异步任务顺序安排和轻松理解 , js代码执行顺序表面知道

    学习关键语句 : async , await 用法 await 怎么使用 同步任务和异步任务 微任务和宏任务 js中代码执行顺序 虽然说 async 和 await 是 Promise 的语法糖 , 但是用惯了Promise 的人(我) , 还真不能超快速使用上这个语法糖 , 所以赶紧写一篇文章出来让各位了解了解这个到底怎么用在我的项目

    2024年02月03日
    浏览(49)
  • Spring 太肥、太慢?你受不了?那 Solon Java Framework 就是你的西施

    Solon 是什么? Java 生态型应用开发框架 。它 从零开始构建 ,有自己的标准规范与开放生态(历时五年,已有全球第二级别的生态规模)。与其他框架相比,它 解决了两个重要的痛点:启动慢,费内存 。2023年6月, Maven 单月下载量突破200万 。 解决痛点? 由于Solon Bean容器的

    2024年02月11日
    浏览(33)
  • Spring Security系列教程之解决Spring Security环境中的跨域问题

    一. 启用Spring Security 的CORS支持 1. 普通的跨域 方式1:在接口方法上利用@CrossOrigin注解解决跨域问题 方式2:通过实现WebMvcConfigurer接口来解决跨域问题 二. Spring Security环境下的跨域问题解决 通过上面的配置,我们已经解决了Ajax的跨域请求问题,但是这个案例中也有潜在的威胁

    2024年02月05日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包