springboot的bean以及一些注解@Primary、@Qualifier

发布时间 2024-01-03 15:58:29作者: spiderMan1-1

1、bean的加载顺序

spring容器载入bean顺序是不确定的,在一定的范围内bean的加载顺序可以控制。
image
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也是按照代码的编写顺序依次加载的
(5)同一类中加载顺序:Constructor >> @Autowired >> @PostConstruct >> @Bean
(6)同一类中加载顺序:静态变量 / 静态代码块 >> 构造代码块 >> 构造方法(需要特别注意的是静态代码块的执行并不是优先所有的bean加载,只是在同一个类中,静态代码块优先加载)

2、部分控制bean加载顺序

注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响。
@Order注解等并不能控制Bean的加载顺序的~~因为Spring在解析Bean的时候,根本就没有参考这个注解。另外@Configuration配置类的加载,也不会受到@Order注解的影响,它拿到配置的数组后,仅仅就是一个for循环遍历去解析。

  • 特别情况下,如果想手动控制部分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的加载顺序。
image
以上结果,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("dependson02")
public class Dependson02 {
 
    Dependson02(){
        System.out.println(" dependson02 Success ");
    }
}

执行结果

dependson02 Success 
Dependson01 success

3、@Primary和@Qualifier

在某些情况下,需要注册多个相同类型的bean。
在此示例中,有Employee类型的zhangSanEmployee()和liSiEmployee()Bean:

@Configuration
public class PrimaryConfig {
    @Bean
    public Employee zhangSanEmployee() {
        return new Employee("张三");
    }

    @Bean
    public Employee liSiEmployee() {
        return new Employee("李四");
    }
}

如果尝试运行应用程序,与@Autowired一起应用于注入。Spring会抛出NoUniqueBeanDefinitionException。

要访问相同类型的bean,常使用@Qualifier(“beanName”)注解,通过别名控制访问相同类型。

@Configuration
public class PrimaryConfig {

   @Bean
   @Qualifier("zhangSanEmployee")
   public Employee zhangSanEmployee() {
       return new Employee("张三");
   }

   @Bean
   @Qualifier("liSiEmployee")
   public Employee liSiEmployee() {
       return new Employee("李四");
   }
}

注入

@Resource
private Employee zhangSanEmployee;

@Resource
private Employee liSiEmployee;

(1)将@Primary和@Bean一起使用
使用

@Configuration
public class PrimaryConfig {

    @Bean
    public Employee zhangSanEmployee() {
        return new Employee("张三");
    }

    @Bean
    @Primary
    public Employee liSiEmployee() {
        return new Employee("李四");
    }
}

用@Primary标记liSiEmployee()bean。 Spring将优先于zhangSanEmployee()注入liSiEmployee()bean。

@Test
public void test1() {
    AnnotationConfigApplicationContext context
        = new AnnotationConfigApplicationContext(PrimaryConfig.class);

    Employee employee = context.getBean(Employee.class);
    System.out.println(employee);//Employee(name=李四)

}

(2)将@Primary与@Component一起使用

public interface Manager {
    String getManagerName();
}

有一个Manager接口和两个子类

@Component
public class DepartmentManager implements Manager {
    @Override
    public String getManagerName() {
        return "Department manager";
    }
}
@Component
@Primary
public class GeneralManager implements Manager {
    @Override
    public String getManagerName() {
        return "General manager";
    }
}

都覆盖Manager接口的getManagerName()。 另外,请注意,用@Primary标记了GeneralManager bean。
测试:

@Service
public class ManagerService {

    @Autowired
    private Manager manager;

    public Manager getManager() {
        return manager;
    }
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class PrimaryTest {
    @Resource
    private ApplicationContext context;
    @Test
    public void test2() {
        ManagerService service = context.getBean(ManagerService.class);
        Manager manager = service.getManager();
        System.out.println(manager.getManagerName());//General manager
    }
}