由示例引出本文的主角

首先新建两个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);
    }

}

springboot源码解析与原理分析 pdf_Google


发现正常输出没有毛病,也符合实际开发的需求。

那么问题来了,如果上面的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);
    }

}

springboot源码解析与原理分析 pdf_Google_02


springboot源码解析与原理分析 pdf_实例化_03

启动直接空指针爆红了,这显然不是我们想要的结果,我们是要当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如我们所愿没有实例化

springboot源码解析与原理分析 pdf_spring boot_04


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有很多注解

springboot源码解析与原理分析 pdf_实例化_05

例如:

@ConditionalOnBean         //	当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean  //	当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass        //	当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass //	当给定的类名在类路径上不存在,则实例化当前Bean

原理大致相同。