文章目录
- 1、编程式Bean加载控制
- 2、注解式Bean加载控制
- 3、@Conditional派生注解
- 4、Bean依赖的属性配置
Bean的加载控制指
根据特定情况
对bean进行选择性加载
以达到适用于项目的目标
上篇Bean声明的方式中,后4种可以实现对Bean加载的控制,分别是:
- AnnotationConfigApplicationContext调用register方法
- @Import导入ImportSelector接口
- @Import导入ImportBeanDefinitionRegistrar接口
- @Import导入BeanDefinitionRegistryPostProcessor接口
且上篇中已演示了部分对Bean加载的控制,这里演示编程式和注解式两种。
1、编程式Bean加载控制
下面实现:当A类被加载了,我才将B类的Bean加载。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
Class<?> clazz = Class.forName("com.plat.bean.Mouse");
if(clazz != null) { //如果老鼠的类被加载了(注意不关心是不是Bean,只看是不是类加载了)
return new String[]{"com.itheima.bean.Cat"}; //那就把猫这个类注册成Bean
}
} catch (ClassNotFoundException e) {
//e.printStackException();
return new String[0]; //条件类老鼠不存在时,返回一个空数组
}
return null;
}
}
2、注解式Bean加载控制
上面代码里的判断的逻辑对所有类都大同小异,因此考虑抽成一组注解,公共逻辑部分用AOP去实现就好。Spring提供了@Conditional注解:
注解的属性类型是一个Condition接口或者其子类,且Condition接口有抽象方法需要实现,这样写还是很繁琐。因此常使用@Conditional注解的派生注解
。注意很多派生注解都是SpringBoot的。
<!--非Boot项目记得导入SpringBoot的起步依赖才能找到这些派生注解-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.4</version>
</dependency>
<!--注意boot的starter包含Spring的一些依赖,比如spring-context,若旧项目是Spring项目,可直接注释掉相关依赖,省的依赖冲突,版本混乱-->
以下分别整理这些派生注解。
3、@Conditional派生注解
@ConditionalOnClass:有这个类了(是不是Bean无所谓),就加载下面这个Bean
@Component
@ConditionalOnClass(Mouse.class)
public class Cat{
}
//有老鼠这个类被加载了(注意强调的是类,不管你是不是Bean),就加载Cat这个Bean
使用注解的value属性,这么写有点此地无银三百两了,编译器报错的时候,即导包失败,就说明没这个类,那还判断个啥,所以常用name属性,而不是直接class
@Component
@ConditionalOnClass(name = "com.plat.po.Mouse")
public class Cat{
}
@ConditionalOnMissingClass:没这个
类
时(是不是Bean无所谓),就加载下面这个Bean
@Component
@ConditionalOnMissingClass("com.plat.po.Wolf") //没有狼来时,加载猫成为一个Bean
public class Cat{
}
这样控制Bean加载的一个很经典的例子就是DataSource,如下:即只有用到了MySQL的驱动类时,再加载dataSource的Bean
public class SpringConfig {
@Bean
@ConditionalOnClass(name = "com.mysql.jdbc.Driver")
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
再往大了想,有一个技术框架的jar包,当你用到某个标志类,那就加载该技术所有的Bean,否则就不加载,此时,使用者只需做一些简单的配置即可。
@ConditionalOnBean : 有这个
Bean
时,才加载下面的这个Bean
public class SpringConfig {
@Bean
@ConditionalOnBean(name="jerry")
@ConditionalOnMissingBean(name = "com.plat.po.Dog")
public Cat tom(){
return new Cat();
}
}
//不关心有没有老鼠的类被加载,而是要求有叫杰瑞的Bean,且没有狗的Bean,Tom才加载到Ioc中成为Bean
@ConditionalOnNotWebApplication和@ConditionalOnWebApplication :非Web或者Web环境下再加载
注意,这些派生注解同时出现在同一个类上方时,它们之间是且的关系。
4、Bean依赖的属性配置
结论:当业务Bean运行需要一些属性时,抽取一个独立的属性类xxProperties,读取配置文件信息
用一个循序渐进的示例来体验属性配置,先准备几个demo类:
@Data
public class Cat{
private String name;
private Integer age;
}
@Data
public class Mouse{
private String name;
private Integer age;
}
//猫和老鼠卡通类
@Component
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//提供个构造方法给两个属性赋值,不然默认null,下面会空指针
public CartoonCatAndMouse(){
cat = new Cat();
cat.setName("tom");
cat.setAge(3);
mouse= new Mouse();
mouse.setName("jerry");
mouse.setAge(3);
}
public void play(){
System.out.println(cat.getAge()+"岁的"+cat.getName()+"与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}
}
//启动类中调用下play方法
@SpringBootApplication
public class App{
public static void main(String[] args){
ConfigurableApplicationContext ctx = pringApplication.run(app.class);
CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);
bean.play();
}
}
此时,配置都硬编码了,显然不合理。
优化第一步:引入yaml配置并@ConfigurationProperties读取
cartoon:
cat:
name: tom
age: 3
mouse:
name: jerry
age: 4
@Component
@ConfigurationProperties(prefix = "cartoon")
//加个set,不然cat属性和mouse属性为null
@Data
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
public void play(){
System.out.println(cat.getAge()+"岁的"+cat.getName()+"与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}
}
此时,如果yaml配置中没有相关配置,则对应的对象为null,进而空指针。也就是说,一加@ConfigurationProperties(prefix = “cartoon”),我的类和yaml配置绑死了,没配置,类都受影响。
优化第二步:引入独立配置类CartoonProperties
@ConfigurationProperties(prefix = "cartoon")
@Data
//旧知识点,读取yaml的类的对象必须受Spring容器管控,否则,即使拿到yaml值也无法set给你
@Component
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
@Component
@Data
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//构造器注入了,用@Autowired也行
private CartoonProperties cartoonProperties;
public void play(CartoonProperties cartoonProperties){
this.cartoonProperties = cartoonProperties;
cat = new Cat();
cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName())
?cartoonProperties.getCat().getName():"tom"); //有则用,无则用默认值
//Mouse对象同样写法,略
System.out.println(cat.getAge()+"岁的"+cat.getName()+"与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}
}
此时,有个缺点,CartoonProperties类不管用不用,都被强制加载成一个Bean了,但去掉@Component,只留@ConfigurationProperties语法错误 ⇒ 用@EnableConfigurationProperties
优化第三步:@EnableConfigurationProperties,改掉强制加载Bean
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
@ConfigurationProperties和@EnableConfigurationProperties,前者是做属性绑定的,后者是开启属性绑定,并设定对应的目标是谁
@Component //这个Component也可以去掉,用的时候@Import(CartoonCatAndMouse.class)
@Data
//即当我加载CartoonCatAndMouse时,就用CartoonProperties.class,并把CartoonProperties类加载成Bean
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//重复代码,略....
这套模式的亮点有以下几个:
- 合理的加载配置文件,即你配置了就用你的,没配就用默认值来工作
- 对于属性类xxxProperties不用强制配置成Bean,使用@EnableConfigurationProperties
- 对于业务功能的Bean,通常使用@Import将一个类加载成Bean,来解耦强制加载Bean,以降低Spring容器管理Bean的工作量以及强度