【小家Spring】控制Spring IoC容器对Bean(含@Configuration配置类)的加载顺序(@DependsOn注解的使用)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】控制Spring IoC容器对Bean(含@Configuration配置类)的加载顺序(@DependsOn注解的使用)

前言


首先,先说明一点:此篇博文相对来说是比较小的专题,只讲解Spring IoC加载Bean的顺序问题。

为了更好的了解这里面的原理,建议先了解Spring容器内部对Bean执行初始化的原理,因此推荐下面博文(若已了解,请忽略):

【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结

【小家Spring】AbstractBeanFactory#getBean()、doGetBean完成Bean的初始化、实例化,以及BeanPostProcessor后置处理器源码级详细分析


本文的讲解方式,以案例为主,进行各种case的分析讲解

为什么要控制Bean的加载顺序?


@Order注解等并不能控制Bean的加载顺序的~~因为你如果熟悉原理了就知道Spring在解析Bean的时候,根本就没有参考这个注解

另外@Configuration配置类的加载,也不会受到@Order注解的影响。因为之前源码解释过,它拿到配置的数组,仅仅就是一个for循环遍历去解析了


另外需要说明的一点是:@Configuration注解的解析顺序,在Spring Boot环境下会受到影响的(毕竟Boot都是自动的,而不是我们手动传值的) 相关注解有:@AutoConfigureAfter、@AutoConfigureBefore、@AutoconfigureOrder等等


Spring容器载入bean顺序是不确定的,spring框架没有约定特定顺序逻辑规范。


但是但Spring能保证如果A依赖B(如beanA中有@Autowired B的变量),那么B将先于A被加载(这属于Spring容器内部就自动识别处理了)。但如果beanA不直接依赖B,我们如何让B仍先加载?


需要的场景距离如下


  1. bean A 间接(并不是直接@Autowired)依赖 bean B。如bean A有一个属性,需要在初始化的时候对其进行赋值(需要在初始化的时候做,是因为这个属性其实是包装了其它的几个Bean的,比如说代理了Bean B),所以这就形成了Bean A间接的依赖Bean B了
  2. bean A是事件发布者(或JMS发布者),bean B (或一些) 负责监听这些事件,典型的如观察者模式。我们不想B 错过任何事件,那么B需要首先被初始化。


以上是两种典型的,Bean初始化的时候存在依赖关系的情况,都可以通过@DependsOn来解决,件下面Demo 。(当然有的时候可以通过别的方式间接解决,比如特殊接口SmartInitializingSingleton ,又或者是Spring Boot提供的CommandLineRunner、ApplicationRunner等接口,但这些都不是本文研究的重点)


Bean加载循序、依赖关系Case~


备注:这里面解答的方案,不考虑上面说到的使用SmartInitializingSingleton等间接的方案


准备工作:(两个controller和一个service)

@Controller
public class HelloController {
  public HelloController() {
        System.out.println("HelloController 初始化。。。");
    }
    @ResponseBody
    @GetMapping("/hello")
    public String helloGet() throws Exception {
        return "hello...Get";
    }
}
@Controller
public class AsyncHelloController {
  public AsyncHelloController() {
        System.out.println("AsyncHelloController 初始化。。。");
    }
}
@Service
public class HelloServiceImpl implements HelloService {
    public HelloServiceImpl() {
        System.out.println("HelloServiceImpl 初始化。。。");
    } 
}


启动容器,打印顺序(初始化顺序如下:)


HelloServiceImpl 初始化。。。
AsyncHelloController 初始化。。。
HelloController 初始化。。。


需要注意的是:这个demo的日志都是放在默认的构造函数里面的,因此即使你使用了@Autowired,也是不会打乱构造函数的执行顺序的,因为,因为@Autowired的解析发生在给属性赋值的populate()方法里(具体查之前博文或者源码),这个时候自己已经实例化了,才会去给属性赋值嘛

所以如果你要求的时机稍微比较晚可以在赋值期间、或者实例化期间去


@DependsOn:让HelloController在AsyncHelloController之前实例化


//@DependsOn // 这里面写String数组。不写不会生效,但是若写了,名字要写正确,否则会报错的
@DependsOn({"helloController"}) // 名称必须写对,必须是容器里存在的Bean,否则启动报错的(fast-fail是好事)
@Controller
public class AsyncHelloController {
  ...
}
HelloServiceImpl 初始化。。。
HelloController 初始化。。。   --> HelloController先被实例化了~~~
AsyncHelloController 初始化。。。


需要特别注意的是,使用@DependsOn注解时,一定要注意父子容器的问题(因为它底层也是getBean())。比如下面这样在service层依赖controller的话,就报错:


@DependsOn({"helloController"}) //NoSuchBeanDefinitionException: No bean named 'helloController' available
@Service
public class HelloServiceImpl implements HelloService {
  ...
}


SpringBoot环境下,不会报错。具体原因请关注SpringBoot的原理分析相关博文吧


使用@Lazy间接实现

@Lazy
public class AsyncHelloController {
  ...
}
HelloServiceImpl 初始化。。。
HelloController 初始化。。。


我们发现它只有两句输出,这个时候AsyncHelloController还没有实例化。只有首次访问它的时候才会实例化,所以我们是通过间接的方式实现了这个效果。


这种方式不建议使用在这种DependsOn的场景,因为它不是为了这个而生的。若有别的Bean @Autowired了它之类的,这种做法显然就失效了~~~


//May be used on any class directly or indirectly annotated with @Component
// or on methods annotated with @Bean
// 若这个Bean xml里也配置了,就会议xml里配置的为准
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {
  String[] value() default {};
}


@DependsOn 用于@Bean注解上的使用


由于使用方式很简单,因此略过~


@Configuration配置类顺序控制


@Configuration配置类也是容器里面一个特殊的Bean,因为它不需要完成业务功能,因此它


纯Spring环境


由于在纯Spring环境下,Config配置类都是由我们手动指定传进去的,所以Spring并没有再对它进行排序处理。如下非web环境和web环境:

    public static void main(String[] args) {
        // 这里Config是自己指定、所以加载顺序就是我们传入的顺序
        new AnnotationConfigApplicationContext(RootConfig.class, Root2Config.class);
    }
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class, Root2Config.class};
    }

@Configuration的加载顺序,并不影响@Bean的互相引用:


@Configuration
public class RootConfig {
  // 虽然入参里的Parent 在配置类Root2Config里,但spring还是能够去容器中找过来的。
    @Bean
    public Child child(Parent parent) {
        System.out.println(parent); 
        return new Child();
    }
}
@Configuration
public class Root2Config {
    @Bean
    public Parent parent() {
        return new Parent();
    }
}
配置文件加载顺序为:RootConfig 、 Root2Config
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class, Root2Config.class};
    }


有这效果我们可以看到,Config的先后顺序,并不影响@Bean的引用。


此处需要特别说明的一点是:请不要循环引用,否则会报错~(笔记这个和Bean的属性赋值方面的循环引用还是不一样的,有点类似构造器的循环引用。我们知道的是,Spring是不能解决构造器的循环引用的)


Spring Boot环境


略,具体使用方法大都同Spring。但是在基础上增强了,它支持用户自定义@Configuration的加载顺序


总结


如果了解了Spring IoC容器初始化的原理后,再去看看这些依赖、循环引用等Case,是很容易被解释和理解的。这就是为为什么我把这种偏应用的东西,反而放到后面博文来书写的重要原因吧。

万变不离其宗,根基稳了才能决定上层建筑

相关文章
|
14天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
30 2
|
13天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
26 1
|
1月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
37 0
|
2月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
37 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
76 0
|
2月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
53 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
219 2
|
1天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
28 14