由示例引出本文的主角
首先新建两个Pojo,分别是People和Company
/**
* @description: People
* @Author MRyan
* @Date 2020/12/5 14:20
* @Version 1.0
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class People {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 所在公司
*/
private Company city;
}
/**
* @description: Company
* @Author MRyan
* @Date 2020/12/5 14:20
* @Version 1.0
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Company {
/**
* 公司名称
*/
private String companyName;
/**
* 公司所在城市
*/
private String companyCity;
}
有了这两个Pojo我们可以开始搞事情了
定义一个ConditionText类,将Company作为bean注入IOC容器中并返回对象,并同样创建People作为bean依赖Company。
/**
* @description: 《SpringBoot源码分析》ConditionText
* @Author MRyan
* @Date 2020/12/5 14:21
* @Version 1.0
*/
@Configuration
public class ConditionText {
@Bean
public Company loadCompany() {
Company company = new Company();
company.setCompanyName("Google");
return company;
}
@Bean
public People people(Company company) {
company.setCompanyCity("USA");
return new People("MRyan", 21, company);
}
}
然后我们在测试类中注入People并输出people信息
@Slf4j
@SpringBootTest
class DemoApplicationTests {
@Autowired(required = false)
private People people;
@Test
void contextLoads() {
System.out.println("people = " + people);
}
}
发现正常输出没有毛病,也符合实际开发的需求。
那么问题来了,如果上面的Company没有注入成功,会出现什么事情
(将Company注释掉模拟没有注入成功的场景)
/**
* @description: 《SpringBoot源码分析》ConditionText
* @Author MRyan
* @Date 2020/12/5 14:21
* @Version 1.0
*/
@Configuration
public class ConditionText {
/* @Bean
public Company loadCompany() {
Company company = new Company();
company.setCompanyName("Google");
return company;
}*/
@Bean
public People people(Company company) {
company.setCompanyCity("USA");
return new People("MRyan", 21, company);
}
}
启动直接空指针爆红了,这显然不是我们想要的结果,我们是要当Company已经注入成功那么实例化People,如果没有注入成功那么不实例化People。
那么我们该怎么做呢?
本文的重点来了:
@ConditionalOnBean注解的作用
将上述测试代码修改如下:
/**
* @description: 《SpringBoot源码分析》ConditionText
* @Author MRyan
* @Date 2020/12/5 14:21
* @Version 1.0
*/
@Configuration
public class ConditionText {
/* @Bean
public Company loadCompany() {
Company company = new Company();
company.setCompanyName("Google");
return company;
}*/
/***
*这里加了ConditionalOnBean注解,就代表如果Company存在才实例化people
*/
@ConditionalOnBean(name = "Company")
@Bean
public People people(Company company) {
company.setCompanyCity("USA");
return new People("MRyan", 21, company);
}
}
运行测试,发现这次没爆红,而且People如我们所愿没有实例化
ConditionalOnBean的作用是什么,它是怎么实现的呢?
注解ConditionalOnBean是什么
源码如下:
/**
* {@link Conditional @Conditional} that only matches when beans meeting all the specified
* requirements are already contained in the {@link BeanFactory}. All the requirements
* must be met for the condition to match, but they do not have to be met by the same
* bean.
* <p>
* When placed on a {@code @Bean} method, the bean class defaults to the return type of
* the factory method:
*
* <pre class="code">
* @Configuration
* public class MyAutoConfiguration {
*
* @ConditionalOnBean
* @Bean
* public MyService myService() {
* ...
* }
*
* }</pre>
* <p>
* In the sample above the condition will match if a bean of type {@code MyService} is
* already contained in the {@link BeanFactory}.
* <p>
* The condition can only match the bean definitions that have been processed by the
* application context so far and, as such, it is strongly recommended to use this
* condition on auto-configuration classes only. If a candidate bean may be created by
* another auto-configuration, make sure that the one using this condition runs after.
*
* @author Phillip Webb
* @since 1.0.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
//当给定的在bean存在时,则实例化当前Bean
public @interface ConditionalOnBean {
/**
* The class types of beans that should be checked. The condition matches when beans
* of all classes specified are contained in the {@link BeanFactory}.
* @return the class types of beans to check
*/
//需要作为条件的类的Class对象数组
Class<?>[] value() default {};
/**
* The class type names of beans that should be checked. The condition matches when
* beans of all classes specified are contained in the {@link BeanFactory}.
* @return the class type names of beans to check
*/
//需要作为条件的类的Name,Class.getName()
String[] type() default {};
/**
* The annotation type decorating a bean that should be checked. The condition matches
* when all of the annotations specified are defined on beans in the
* {@link BeanFactory}.
* @return the class-level annotation types to check
*/
//(用指定注解修饰的bean)条件所需的注解类
Class<? extends Annotation>[] annotation() default {};
/**
* The names of beans to check. The condition matches when all of the bean names
* specified are contained in the {@link BeanFactory}.
* @return the names of beans to check
*/
//spring容器中bean的名字
String[] name() default {};
/**
* Strategy to decide if the application context hierarchy (parent contexts) should be
* considered.
* @return the search strategy
*/
//搜索容器层级,当前容器,父容器
SearchStrategy search() default SearchStrategy.ALL;
/**
* Additional classes that may contain the specified bean types within their generic
* parameters. For example, an annotation declaring {@code value=Name.class} and
* {@code parameterizedContainer=NameRegistration.class} would detect both
* {@code Name} and {@code NameRegistration<Name>}.
* @return the container types
* @since 2.1.0
*/
//可能在其泛型参数中包含指定bean类型的其他类
Class<?>[] parameterizedContainer() default {};
}
其中我们看@Conditional(OnBeanCondition.class)是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件的才给容器注册Bean(有关于这个注解会另起一篇文章分析)
而@ConditionalOnBean作用是当给定的在bean存在时,则实例化当前Bean
需要注意配合上@Autowired(required = false)使用 required=false 的意思就是允许当前的Bean对象为null。
其实类似@ConditionalOnBean有很多注解
例如:
@ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
原理大致相同。