一、Bean的加载顺序
spring容器载入bean顺序是不确定的,在一定的范围内bean的加载顺序可以控制。
spring容器载入bean虽然顺序不确定,但遵循一定的规则:
1、按照字母顺序加载(同一文件夹下按照字母数序;不同文件夹下,先按照文件夹命名的字母顺序加载)
2、不同的bean声明方式不同的加载时机,顺序总结:@ComponentScan > @Import > @Bean
这里的ComponentScan指@ComponentScan及其子注解,Bean指的是@configuration + @bean
同时需要注意的是:
(1)Component及其子注解申明的bean是按照字母顺序加载的
(2)@configuration + @bean是按照定义的顺序依次加载的
(3)@import的顺序,就是bean的加载顺序
(4)在xml中,通过<bean id="">方式声明的bean也是按照代码的编写顺序依次加载的
(5)同一类中加载顺序:Constructor >> @Autowired >> @PostConstruct >> @Bean
(6)同一类中加载顺序:静态变量 / 静态代码块 >> 构造代码块 >> 构造方法(需要特别注意的是静态代码块的执行并不是优先所有的bean加载,只是在同一个类中,静态代码块优先加载)
特别情况下,如果想手动控制部分bean的加载顺序,有如下方法:
方法1:构造方法依赖 (推荐)
@Component
public class CDemo1 {
private String name = "cdemo 1";
public CDemo1(CDemo2 cDemo2) {
System.out.println(name);
}
}
@Component
public class CDemo2 {
private String name = "cdemo 2";
public CDemo2() {
System.out.println(name);
}
}
CDemo2在CDemo1之前被初始化。
限制
要有注入关系,如:CDemo2通过构造方法注入到CDemo1中,若需要指定两个没有注入关系的bean之间优先级,则不太合适(比如我希望某个bean在所有其他的Bean初始化之前执行)
循环依赖问题,如过上面的CDemo2的构造方法有一个CDemo1参数,那么循环依赖产生,应用无法启动
另外一个需要注意的点是,在构造方法中,不应有复杂耗时的逻辑,会拖慢应用的启动时间
方法2:参数注入
在@Bean标注的方法上,如果你传入了参数,springboot会自动会为这个参数在spring上下文里寻找这个类型的引用。并先初始化这个类的实例。
利用此特性,我们也可以控制bean的加载顺序。
以上结果,beanB先于beanA被初始化加载。
需要注意的是,springboot会按类型去寻找。如果这个类型有多个实例被注册到spring上下文,那你就需要加上@Qualifier(“Bean的名称”)来指定
方法3:@Autowired 注入到所需的服务中
跟在xml配置中写 ref差不多的功能 spring 会解析到这个会依赖springBeanManager 所以会先加载springBeanManager
@Component
public class SystemInit {
@Autowired
private SpringBeanManager springBeanManager;
@PostConstruct
public void init() {
//初始化 script job bean
GroovyBeanInit.InitScriptJob();
}
}
方法4:@DependsOn(“xxx”)
没有直接的依赖关系的,可以通过@DependsOn注解,我们可以在bean A上使用@DependsOn注解 ,告诉容器bean B应该优先被加载初始化。
不推荐的原因:这种方法是通过bean的名字(字符串)来控制顺序的,如果改了bean的类名,很可能就会忘记来改所有用到它的注解,那就问题大了。
当一个bean需要在另一个bean实例化之后再实例化时,可使用这个注解。
@Component("dependson02")
public class Dependson02 {
Dependson02(){
System.out.println(" dependson02 Success ");
}
}
@Component
@DependsOn("dependson02")
public class Dependson01 {
Dependson01(){
System.out.println("Dependson01 success");
}
}
执行结果:
结果:
dependson02 Success
Dependson01 success
方法5:BeanDefinitionRegistryPostProcessor接口
通过实现BeanDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry方法中通过BeanDefinitionRegistry获取到所有bean的注册信息,将bean保存到LinkedHashMap中,并从BeanDefinitionRegistry中删除,然后将保存的bean定义排序后,重新再注册到BeanDefinitionRegistry中,即可实现bean加载顺序的控制。
/**
*配置类
*/
@Configuration
@ComponentScan(basePackages = "demo")
public class AppConfig extends WebMvcConfigurationSupport {
/**
* 注册用于控制bean加载顺序的处理器
* @return
*/
@Bean
public static DemoProcessor demoProcessor() {
return new DemoProcessor();
}
}
/**
*
*/
public class DemoProcessor implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
String[] beanDefinitionNames = registry.getBeanDefinitionNames();
int index = 0;
//保留前n个关键bean的顺序
for(;index<beanDefinitionNames.length;index++) {
if(AppConfig.class.getName().equals(registry.getBeanDefinition(beanDefinitionNames[index]).getBeanClassName())) {
break;
}
}
Map<String, BeanDefinition> beans = new LinkedHashMap<>(beanDefinitionNames.length-index);
for(;index<beanDefinitionNames.length;index++) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionNames[index]);
beans.put(beanDefinitionNames[index], beanDefinition);
registry.removeBeanDefinition(beanDefinitionNames[index]);
}
//TODO ...排序逻辑,注意beans中可能还包含有其他spring自身定义的bean
List<String> orderdBeanNames = new ArrayList<>();
//将排好序的bean再次注册到容器中
orderdBeanNames.forEach(beanName->{
registry.registerBeanDefinition(beanName, beans.get(beanName));
});
}
}
注意:
执行顺序:Constructor > @Autowired > @PostConstruct
所以正常情况下,所有的bean都已经执行了构造器Constructor,也就是bean都已经存在容器中了。
也就是正常情况下,都是可以正常执行了。
Autowired为null的情况如下:
(1)该类没有托管给spring 管理
一般在类的上面添加@Component 就可以了
(2)不能new出来的实例,
例如:A a = new A();//new的对象不会交给Spring容器管理, 所以是不行的
特殊情况:@Configuration + @Bean 这种方式是可以的
SpringBoot通过@AutoConfigureOrder、@AutoConfigureBefore、@AutoConfigureAfter注解,控制自动配置类的实例化顺序。
Spring中控制Bean的实例化顺序
Spring中默认实例化顺序
创建实体类A、B、C
@Component
public class A {
public A() {
System.out.println("A construct");
}
}
@Component
public class B {
public B() {
System.out.println("B construct");
}
}
@Component
public class C {
public C() {
System.out.println("C construct");
}
}
启动SpringBoot
实例化顺序: A > B > C
Spring中指定实例化顺序(C > B > A)
修改实体类A、B、C
@Component
@DependsOn("b")
public class A {
public A() {
System.out.println("A construct");
}
}
@Component
@DependsOn("c")
public class B {
public B() {
System.out.println("B construct");
}
}
@Component
public class C {
public C() {
System.out.println("C construct");
}
}
启动SpringBoot
实例化顺序: C > B > A
SpringBoot中控制自动配置类的实例化顺序
SpringBoot中默认实例化顺序
在 resources 文件夹下创建 META-INF 文件夹,在 META-INF 文件夹下创建 spring.factories 文件,整体结构如下:
spring.factories 明细如下:
Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ys.boot.auto.D,\
com.ys.boot.auto.E,\
com.ys.boot.auto.F
创建实体类D、E、F
public class D {
public D() {
System.out.println("D construct");
}
}
public class E {
public E() {
System.out.println("E construct");
}
}
public class F {
public F() {
System.out.println("F construct");
}
}
启动SpringBoot
实例化顺序: D > E > F
使用@DependsOn解决实例化顺序?
修改实体类D、E、F
@DependsOn("e")
public class D {
public D() {
System.out.println("D construct");
}
}
@DependsOn("f")
public class E {
public E() {
System.out.println("E construct");
}
}
public class F {
public F() {
System.out.println("F construct");
}
}
启动SpringBoot
抛出 org.springframework.beans.factory.NoSuchBeanDefinitionException
异常原因 : 一般情况下,自动配置的类都不是当前项目的文件,即Spring不会将相关文件扫描成BeanDefinition对象。当我们使用@DependsOn注解的时候,Spring就尝从当前环境中查找或实例化指定名称的Bean,因为找不到相关BeanDefinition对象,所以抛出异常。
SpringBoot中指定实例化顺序(方式一)
修改实体类D、E、F
@AutoConfigureOrder(3)
public class D {
public D() {
System.out.println("D construct");
}
}
@AutoConfigureOrder(2)
public class E {
public E() {
System.out.println("E construct");
}
}
@AutoConfigureOrder(1)
public class F {
public F() {
System.out.println("F construct");
}
}
启动SpringBoot
实例化顺序: F > E > D
SpringBoot中指定实例化顺序(方式二)
修改实体类D、E、F
@AutoConfigureAfter(E.class)
public class D {
public D() {
System.out.println("D construct");
}
}
@AutoConfigureAfter(F.class)
public class E {
public E() {
System.out.println("E construct");
}
}
public class F {
public F() {
System.out.println("F construct");
}
}
启动SpringBoot
实例化顺序: F > E > D
二、Bean的执行顺序
注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响。
特别介绍
@PropertySource:用于注解类,告诉当前类使用什么配置文件,配置文件必须是.property或者.xml类型。
@PropertySource(value = {"classpath:config/user1.properties","classpath:config/user2.properties"})
@ImportResource:通过配置文件注入bean,用于注解主配置类,导入一个或多个定义bean的配置文件,配置文件必须是.xml类型。
@ImportResource(value = {"classpath:/beans.xml"})
@SpringBootApplication(scanBasePackages = {"team.seagull.client"})
public class DeployApplication {
public static void main(String[] args) {
SpringApplication.run(DeployApplication.class, args);
}
}