注意: 本文只是简单阐述,自己动手来感受springboot对核心对象创建的控制,更详细的原理请关注后续文章
1. 前言
在springBoot中,相比于spring来说,它有一个核心的特点就是能够实现自动装配,自动装配是如何实现的?比如说当用户导入了一个redis的启动器,为什么springboot就会自动创建RedisTemplate对象,如果不导入为什么就不会创建呢?也就是类似于我添加了某些依赖就会自动创建某些对象,不添加就不会创建。
2.模拟实现简易版自动装配
- 需求:当我添加了Redis的依赖就自动创建Person实体类对象,如果没有添加Redis依赖就不创建。(模拟springboot添加依赖就会自动创建某些对象)
- 涉及注解:
- @Configuration 注解:放在某个配置类上,告诉spring该类为一个配置类,作用类似于xml配置文件。
- @Bean 注解 : 放在某个方法之上,将方法的返回值注入到spring容器中,而被注入到spring容器中的对象名字默认是方法名,一般配合@Configuration注解一起使用。
- (核心) @Conditional 注解:传入参数为一个实现了Condition接口的类的.class,而实现Condition接口的话,实现类中就必须重写Condition接口中的matches()方法,该方法返回值为boolean,如果为true则表示@Conditional注解中的条件为true,此时该Bean会被创建,如果为false,则该Bean不会被创建。
概括来说:就是根据条件选择性的决定某个Bean是否被创建。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
- 编写一个类去实现/继承Condition接口,并且重写matches方法
public class DecideConfig implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean flag = true;
try {
Class<?> name = Class.forName("org.springframework.data.redis.core.RedisTemplate");
} catch (ClassNotFoundException e) {
e.printStackTrace();
flag = false;
}
return flag;
}
}
- 创建一个PersonConfig配置类,里面根据条件来决定是否要创建Person对象
@Configuration
public class RedisConfig {
@Bean
@Conditional(DecideConfig.class)
public Person person(){
return new Person();
}
}
- 创建测试类来测试
@SpringBootTest
class SpringBoot01ApplicationTests {
@Autowired
private Person person;
@Test
public void testPerson(){
System.out.println(person);
}
}
- 当项目依赖加上下面这个redis的启动器的时候 , 运行testPerson()方法
<!-redis依赖--->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 当项目依赖去除redis的启动器的时候 , 运行testPerson()方法
3. 分析缺点
- 上述方法只能判断是否有Redis的依赖加入进来,也就是matches方法中写死了,Class.ForName()参数写了固定的,我需要实现动态的判断,也就是我想判断任意依赖是否添加,来决定是否创建Person对象
4. 完善简易版自动装配
- 涉及注解:
- @ConditionalOnClass: 这个注解是spring提供的注解,传入一个name[ ]数组,数组中的每一个元素都是一个类的名字ClassName,如果数组中的类全部存在则该Bean注入成功,否则注入失败。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
-
使用@ConditionalOnClass的话我们就不需要自己手动写一个类去实现Condition接口,和重写matches方法了,spring已经帮我们写好了实现。 -
创建一个PersonConfig配置类,里面根据条件来决定是否要创建Person对象
@Configuration
public class RedisConfig {
@Bean
@ConditionalOnClass(name = {"org.springframework.web.servlet.DispatcherServlet",
"org.springframework.data.redis.core.RedisTemplate"}})
public Person person(){
return new Person();
}
}
- 测试:
- 当我添加了
spring-boot-starter-web 和spring-boot-starter-data-redis 的时候 - 当我去除了redis依赖只剩下web依赖的时候
总结:
- 为什么springboot会在你添加了某些依赖就自动创建某些对象,而你不加这些依赖就不给你创建这些对象?
- 暂时可以理解为:因为springboot内部创建bean也就是一些依赖需要用到的核心对象的时候,并不是无脑使用@Bean来创建的,而是还搭配用到很多@ConditionalXXX的注解,要满足传入注解中的条件的时候,这些核心对象才会被创建,这些条件可能是只有加了一些依赖或者叫启动器的时候才会满足,不加的话就不满足,自然就不会创建对象。这样就做到了按需创建对象,用户需要什么对象springboot就帮你创建好,并放到spring容器中,用户直接使用就可以了。
- 一些spring已经写好的@ConditionalXXX注解:
- @ConditionalOnBean:仅在当前上下文中存在某个对象时,才会初始化一个Bean
- @ConditionalOnMissingBean:仅在当前上下文中不存在某个对象时,才会初始化一个Bean
- @ConditionalOnExpression:当表达式为true时,才会初始化一个Bean
- @ConditionalOnMissingClass:某个class类位于路径上不存在时,才会实例化一个Bean
- @ConditionalOnClass :某个class位于类路径上,才会实例化一个Bean
|