面试阿里p7被问到的问题(当时我只知道第一个):

  1. @Conditional是做什么的?
  2. @Conditional多个条件是什么逻辑关系?
  3. 条件判别在什么时分履行?
  4. ConfigurationCondition和Condition有什么区别?什么时分运用ConfigurationCondition?
  5. 多个Condition履行的次序是什么样的?能够装备优先级么?
  6. 能够介绍一下@Conditional常见的一些用法么?

@Conditional注解

@Conditional注解是从spring4.0才有的,能够用在任何类型或许办法上面,经过@Conditional注解能够装备一些条件判别,当一切条件都满意的时分,被@Conditional标示的方针才会被spring容器处理。

比方能够经过@Conditional来操控bean是否需求注册,操控被@Configuration标示的装备类是需求需求被解析等。

作用就像这段代码,相当于在spring容器解析方针前面加了一个条件判别:

if(@Conditional中装备的多个条件是否都匹配){
//spring持续处理被@Conditional注解标示的方针
}

@Conditional源码:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceConditional{
Class<?extendsCondition>[]value();
}

这个注解只要一个value参数,Condition类型的数组,Condition是一个接口,表明一个条件判别,内部有个办法回来true或false,当一切Condition都建立的时分,@Conditional的成果才建立。

下面咱们来看一下Condition接口。

Condition接口

用来表明条件判别的接口,源码如下:

@FunctionalInterface
publicinterfaceCondition{
/**
*判别条件是否匹配
*context:条件判别上下文
*/
booleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata);
}

是一个函数式接口,内部只要一个matches办法,用来判别条件是否建立的,2个参数:

  • context:条件上下文,ConditionContext接口类型的,能够用来获取容器中的个人信息
  • metadata:用来获取被@Conditional标示的方针上的一切注解信息

ConditionContext接口

这个接口中提供了一些常用的办法,能够用来获取spring容器中的各种信息,看一下源码:

publicinterfaceConditionContext{
/**
*回来bean界说注册器,能够经过注册器获取bean界说的各种装备信息
*/
BeanDefinitionRegistrygetRegistry();
/**
*回来ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器方针
*/
@Nullable
ConfigurableListableBeanFactorygetBeanFactory();
/**
*回来当时spring容器的环境装备信息方针
*/
EnvironmentgetEnvironment();
/**
*回来资源加载器
*/
ResourceLoadergetResourceLoader();
/**
*回来类加载器
*/
@Nullable
ClassLoadergetClassLoader();
}

比较关键性的问题:条件判别在什么时分履行?

Spring对装备类的处理主要分为2个阶段:

装备类解析阶段

会得到一批装备类的信息,和一些需求注册的bean

bean注册阶段

将装备类解析阶段得到的装备类和需求注册的bean注册到spring容器中

看一下什么是装备类

类中有下面任意注解之一的就归于装备类:

  1. 类上有@Compontent注解
  2. 类上有@Configuration注解
  3. 类上有@CompontentScan注解
  4. 类上有@Import注解
  5. 类上有@ImportResource注解
  6. 类中有@Bean标示的办法

判别一个类是不是一个装备类,是否的是下面这个办法,有爱好的能够看一下:

org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate

spring中处理这2个进程会循环进行,直到完结一切装备类的解析及一切bean的注册。

Spring对装备类处理进程

源码位置:

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

整个进程大致的进程如下:

  1. 一般咱们会经过new AnnotationConfigApplicationContext()传入多个装备类来发动spring容器
  2. spring对传入的多个装备类进行解析
  3. 装备类解析阶段:这个进程就是处理装备类上面6中注解的进程,此进程中又会发现许多新的装备类,比方@Import导入的一批新的类刚好也契合装备类,而被@CompontentScan扫描到的一些类刚好也是装备类;此刻会对这些新发生的装备类进行同样的进程解析
  4. bean注册阶段:装备类解析后,会得到一批装备类和一批需求注册的bean,此刻spring容器会将这批装备类作为bean注册到spring容器,同样也会将这批需求注册的bean注册到spring容器
  5. 经过上面第3个阶段之后,spring容器中会注册许多新的bean,这些新的bean中可能又有许多新的装备类
  6. Spring从容器中将一切bean拿出来,遍历一下,会过滤得到一批未处理的新的装备类,持续交给第3步进行处理
  7. step3到step6,这个进程会经历许屡次,直到完结一切装备类的解析和bean的注册

从上面进程中能够了解到:

  1. 能够在装备类上面加上@Conditional注解,来操控是否需求解析这个装备类,装备类假如不被解析,那么这个装备上面6种注解的解析都会被越过
  2. 能够在被注册的bean上面加上@Conditional注解,来操控这个bean是否需求注册到spring容器中
  3. 假如装备类不会被注册到容器,那么这个装备类解析所发生的一切新的装备类及所发生的一切新的bean都不会被注册到容器

一个装备类被spring处理有2个阶段:装备类解析阶段、bean注册阶段(将装备类作为bean被注册到spring容器)。

假如将Condition接口的完成类作为装备类上@Conditional中,那么这个条件会对两个阶段都有用,此刻经过Condition是无法精密的操控某个阶段的,假如想操控某个阶段,比方能够让他解析,可是不能让他注册,此刻就就需求用到别的一个接口了:ConfigurationCondition

ConfigurationCondition接口

看一下这个接口的源码:

publicinterfaceConfigurationConditionextendsCondition{
/**
*条件判别的阶段,是在解析装备类的时分过滤还是在创立bean的时分过滤
*/
ConfigurationPhasegetConfigurationPhase();
/**
*表明阶段的枚举:2个值
*/
enumConfigurationPhase{
/**
*装备类解析阶段,假如条件为false,装备类将不会被解析
*/
PARSE_CONFIGURATION,
/**
*bean注册阶段,假如为false,bean将不会被注册
*/
REGISTER_BEAN
}
}

ConfigurationCondition接口相对于Condition接口多了一个getConfigurationPhase办法,用来指定条件判别的阶段,是在解析装备类的时分过滤还是在创立bean的时分过滤。

@Conditional运用的3进程

  1. 自界说一个类,完成Condition或ConfigurationCondition接口,完成matches办法
  2. 在方针方针上运用@Conditional注解,并指定value的指为自界说的Condition类型
  3. 发动spring容器加载资源,此刻@Conditional就会起作用了

事例1:阻挠装备类的处理

在装备类上面运用@Conditional,这个注解的value指定的Condition当有一个为false的时分,spring就会越过处理这个装备类。

自界说一个Condition类:

packagecom.javacode2018.lesson001.demo25.test3;
importorg.springframework.context.annotation.Condition;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.core.type.AnnotatedTypeMetadata;
publicclassMyCondition1implementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
returnfalse;
}
}

matches办法内部咱们能够随意发挥,此处为了演示作用就直接回来false。

来个装备类,在装备类上面运用上面这个条件,此刻会让装备类失效,如下:

packagecom.javacode2018.lesson001.demo25.test3;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Conditional;
importorg.springframework.context.annotation.Configuration;
@Conditional(MyCondition1.class)//@1
@Configuration
publicclassMainConfig3{
@Bean
publicStringname(){//@1
return"路人甲Java";
}
}

@1:运用了自界说的条件类

@2:经过@Bean标示这name这个办法,假如这个装备类成功解析,会将name办法的回来值作为bean注册到spring容器

来个测验类,发动spring容器加载MainConfig3装备类,如下:

packagecom.javacode2018.lesson001.demo25;
importcom.javacode2018.lesson001.demo25.test3.MainConfig3;
importorg.junit.Test;
importorg.springframework.context.annotation.AnnotationConfigApplicationContext;
importjava.util.Map;
publicclassConditionTest{
@Test
publicvoidtest3(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig3.class);
Map<String,String>serviceMap=context.getBeansOfType(String.class);
serviceMap.forEach((beanName,bean)->{
System.out.println(String.format("%s->%s",beanName,bean));
});
}
}

test3中,从容器中获取String类型的bean,运转test3没有任何输出。

咱们能够将MainConfig3上面的@Conditional去掉,再次运转输出:

name->路人甲Java

事例2:阻挠bean的注册

来个装备类,如下:

packagecom.javacode2018.lesson001.demo25.test4;
importcom.javacode2018.lesson001.demo25.test3.MyCondition1;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Conditional;
importorg.springframework.context.annotation.Configuration;
@Configuration
publicclassMainConfig4{
@Conditional(MyCondition1.class)//@1
@Bean
publicStringname(){
return"路人甲Java";
}
@Bean
publicStringaddress(){
return"上海市";
}
}

上面2个办法上面运用了@Bean注解来界说了2个bean,name办法上面运用了@Conditional注解,这个条件会在name这个bean注册到容器之前会进行判别,当条件为true的时分,name这个bean才会被注册到容器。

ConditionTest中新增个测验用例来加载上面这个装备类,从容器中获取String类型一切bean输出,代码如下:

@Test
publicvoidtest4(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig4.class);
Map<String,String>serviceMap=context.getBeansOfType(String.class);
serviceMap.forEach((beanName,bean)->{
System.out.println(String.format("%s->%s",beanName,bean));
});
}

运转输出:

address->上海市

能够看到容器中只要一个address被注册了,而name这个bean没有被注册。

事例3:bean不存在的时分才注册

需求

IService接口有两个完成类Service1和Service1,这两个类会放在2个装备类中经过@Bean的办法来注册到容器,此刻咱们想加个约束,只允许有一个IService类型的bean被注册到容器。

能够在@Bean标示的2个办法上面加上条件约束,当容器中不存在IService类型的bean时,才将这个办法界说的bean注册到容器,下面来看代码完成。

代码完成

条件判别类:OnMissingBeanCondition

packagecom.javacode2018.lesson001.demo25.test1;
importorg.springframework.beans.factory.config.ConfigurableListableBeanFactory;
importorg.springframework.context.annotation.Condition;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.context.annotation.ConfigurationCondition;
importorg.springframework.core.type.AnnotatedTypeMetadata;
importjava.util.Map;
publicclassOnMissingBeanConditionimplementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
//获取bean工厂
ConfigurableListableBeanFactorybeanFactory=context.getBeanFactory();
//从容器中获取IService类型bean
Map<String,IService>serviceMap=beanFactory.getBeansOfType(IService.class);
//判别serviceMap是否为空
returnserviceMap.isEmpty();
}
}

上面matches办法中会看容器中是否存在IService类型的bean,不存在的时分回来true

IService接口

packagecom.javacode2018.lesson001.demo25.test1;
publicinterfaceIService{
}

接口有2个完成类

Service1
packagecom.javacode2018.lesson001.demo25.test1;
publicclassService1implementsIService{
}
Service2
packagecom.javacode2018.lesson001.demo25.test1;
publicclassService2implementsIService{
}

来一个装备类负责注册Service1到容器

packagecom.javacode2018.lesson001.demo25.test1;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Conditional;
importorg.springframework.context.annotation.Configuration;
@Configuration
publicclassBeanConfig1{
@Conditional(OnMissingBeanCondition.class)//@1
@Bean
publicIServiceservice1(){
returnnewService1();
}
}

@1:办法之前运用了条件判别

再来一个装备类负责注册Service2到容器

packagecom.javacode2018.lesson001.demo25.test1;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Conditional;
importorg.springframework.context.annotation.Configuration;
@Configuration
publicclassBeanConfig2{
@Conditional(OnMissingBeanCondition.class)//@1
@Bean
publicIServiceservice2(){
returnnewService2();
}
}

@1:办法之前运用了条件判别

来一个总的装备类,导入别的2个装备类

packagecom.javacode2018.lesson001.demo25.test1;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.context.annotation.Import;
@Configuration
@Import({BeanConfig1.class,BeanConfig2.class})//@1
publicclassMainConfig1{
}

@1:经过@Import将其他2个装备类导入

来个测验用例

ConditionTest新增一个办法,办法中从容器中获取IService类型的bean,然后输出:

@Test
publicvoidtest1(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig1.class);
Map<String,IService>serviceMap=context.getBeansOfType(IService.class);
serviceMap.forEach((beanName,bean)->{
System.out.println(String.format("%s->%s",beanName,bean));
});
}

运转输出:

service1->com.javacode2018.lesson001.demo25.test1.Service1@2cd76f31

能够看出容器中只要一个IService类型的bean。

能够将@Bean标示的2个办法上面的@Conditional去掉,再运转会输出:

service1->com.javacode2018.lesson001.demo25.test1.Service1@49438269
service2->com.javacode2018.lesson001.demo25.test1.Service2@ba2f4ec

此刻没有条件约束,2个Service都会注册到容器。

事例4:依据环境选择装备类

往常咱们做项目的时分,有开发环境、测验环境、线上环境,每个环境中有些信息是不一样的,比方数据库的装备信息,下面咱们来模拟不同环境中运用不同的装备类来注册不同的bean。

自界说一个条件的注解

packagecom.javacode2018.lesson001.demo25.test2;
importorg.springframework.context.annotation.Conditional;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
@Conditional(EnvCondition.class)//@1
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceEnvConditional{
//环境(测验环境、开发环境、出产环境)
enumEnv{//@2
TEST,DEV,PROD
}
//环境
Envvalue()defaultEnv.DEV;//@3
}

@1:留意这个注解比较特别,这个注解上面运用到了@Conditional注解,这个当地运用到了一个自界说Conditione类:EnvCondition

@2:枚举,表明环境,界说了3个环境

@3:这个参数用指定环境

上面这个注解一会咱们会用在不同环境的装备类上面

下面来3个装备类

让3个装备类分别在不同环境中收效,会在这些装备类上面运用上面自界说的@EnvConditional注解来做条件限定。

每个装备类中经过@Bean来界说一个名称为name的bean,一会经过输出这个bean来判别哪个装备类收效了。

下面来看3个装备类的代码

测验环境装备类

packagecom.javacode2018.lesson001.demo25.test2;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
@Configuration
@EnvConditional(EnvConditional.Env.TEST)//@1
publicclassTestBeanConfig{
@Bean
publicStringname(){
return"我是测验环境!";
}
}

@1指定的测验环境

开发环境装备类

packagecom.javacode2018.lesson001.demo25.test2;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
@Configuration
@EnvConditional(EnvConditional.Env.DEV)//@1
publicclassDevBeanConfig{
@Bean
publicStringname(){
return"我是开发环境!";
}
}

@1:指定的开发环境

出产环境装备类

packagecom.javacode2018.lesson001.demo25.test2;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
@Configuration
@EnvConditional(EnvConditional.Env.PROD)//@1
publicclassProdBeanConfig{
@Bean
publicStringname(){
return"我是出产环境!";
}
}

@1:指定的出产环境

下面来看一下条件类:EnvCondition

条件类会解析装备类上面@EnvConditional注解,得到环境信息。

然后和现在的环境对比,决议回来true还是false,如下:

packagecom.javacode2018.lesson001.demo25.test2;
importorg.springframework.context.annotation.Condition;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.core.type.AnnotatedTypeMetadata;
publicclassEnvConditionimplementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
//当时需求运用的环境
EnvConditional.EnvcurEnv=EnvConditional.Env.DEV;//@1
//获取运用条件的类上的EnvCondition注解中对应的环境
EnvConditional.Envenv=(EnvConditional.Env)metadata.getAllAnnotationAttributes(EnvConditional.class.getName()).get("value").get(0);
returnenv.equals(curEnv);
}
}

@1:这个用来指定当时运用的环境,此处假定当时运用的是开发环境,这个咱们以后能够任意发挥,比方将这些放到装备文件中,此处方便演示作用。

来个测验用例

@Test
publicvoidtest2(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig2.class);
System.out.println(context.getBean("name"));
}

运转输出

我是开发环境!

能够看到开发环境收效了。

修正一下EnvCondition的代码,切换到出产环境:

EnvConditional.EnvcurEnv=EnvConditional.Env.PROD;

再次运转test2办法输出:

我是出产环境!

出产环境装备类收效了。

事例5:Condition指定优先级

多个Condition按次序履行

@Condtional中value指定多个Condtion的时分,默认情况下会按次序履行,还是经过代码来看一下作用。

下面代码中界说了3个Condition,每个Condition的matches办法中会输出当时类名,然后在装备类上面同时运用这3个Condition:

packagecom.javacode2018.lesson001.demo25.test5;
importorg.springframework.context.annotation.Condition;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.context.annotation.Conditional;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.core.type.AnnotatedTypeMetadata;
classCondition1implementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
System.out.println(this.getClass().getName());
returntrue;
}
}
classCondition2implementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
System.out.println(this.getClass().getName());
returntrue;
}
}
classCondition3implementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
System.out.println(this.getClass().getName());
returntrue;
}
}
@Configuration
@Conditional({Condition1.class,Condition2.class,Condition3.class})
publicclassMainConfig5{
}

来个测验用例

@Test
publicvoidtest5(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig5.class);
}

运转输出:

com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3

上面有多行输出,是因为spring解析整个装备类的进程中,有好几个当地都会履行条件判别。

咱们只用重视前3行,能够看出输出的属性和@Conditional中value值的次序是一样的。

指定Condition的次序

自界说的Condition能够完成PriorityOrdered接口或许承继Ordered接口,或许运用@Order注解,经过这些来指定这些Condition的优先级。

排序规矩:先按PriorityOrdered排序,然后依照order的值进行排序;也就是:PriorityOrdered asc,order值 asc

下面这几个都能够指定order的值
接口:org.springframework.core.Ordered,有个getOrder办法用来回来int类型的值
接口:org.springframework.core.PriorityOrdered,承继了Ordered接口,所以也有getOrder办法
注解:org.springframework.core.annotation.Order,有个int类型的value参数指定Order的大小

看事例代码:

packagecom.javacode2018.lesson001.demo25.test6;
importorg.springframework.context.annotation.Condition;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.context.annotation.Conditional;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.core.Ordered;
importorg.springframework.core.PriorityOrdered;
importorg.springframework.core.annotation.Order;
importorg.springframework.core.type.AnnotatedTypeMetadata;
@Order(1)//@1
classCondition1implementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
System.out.println(this.getClass().getName());
returntrue;
}
}
classCondition2implementsCondition,Ordered{//@2
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
System.out.println(this.getClass().getName());
returntrue;
}
@Override
publicintgetOrder(){//@3
return0;
}
}
classCondition3implementsCondition,PriorityOrdered{//@4
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
System.out.println(this.getClass().getName());
returntrue;
}
@Override
publicintgetOrder(){
return1000;
}
}
@Configuration
@Conditional({Condition1.class,Condition2.class,Condition3.class})//@5
publicclassMainConfig6{
}

@1:Condition1经过@Order指定次序,值为1

@2:Condition2经过完成了Ordered接口来指定次序,@3:getOrder办法回来1

@4:Condition3完成了PriorityOrdered接口,完成这个接口需求重写getOrder办法,回来1000

@5:Condtion次序为1、2、3

依据排序的规矩,PriorityOrdered的会排在前面,然后会再依照order升序,最后能够次序是:

Condtion3->Condtion2->Condtion1

来个测验用例看看作用是不是咱们剖析的这样:

@Test
publicvoidtest6(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig6.class);
}

运转test6,部分输出如下:

com.javacode2018.lesson001.demo25.test6.Condition3
com.javacode2018.lesson001.demo25.test6.Condition2
com.javacode2018.lesson001.demo25.test6.Condition1

成果和咱们剖析的一致。

事例6:ConfigurationCondition运用

ConfigurationCondition运用的比较少,许多当地对这个基本上也不会去介绍,Condition接口基本上能够满意99%的需求了,可是springboot中却许多用到了ConfigurationCondition这个接口。

ConfigurationCondition经过解说比较难了解,来个事例感受一下:

来一个普通的类:Service

packagecom.javacode2018.lesson001.demo25.test7;
publicclassService{
}

来一个装备类,经过装备类注册上面这个Service

packagecom.javacode2018.lesson001.demo25.test7;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
@Configuration
publicclassBeanConfig1{
@Bean
publicServiceservice(){
returnnewService();
}
}

再来一个装备类:BeanConfig2

packagecom.javacode2018.lesson001.demo25.test7;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
@Configuration
publicclassBeanConfig2{
@Bean
publicStringname(){
return"路人甲Java";
}
}

来一个总的装备类

packagecom.javacode2018.lesson001.demo25.test7;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.context.annotation.Import;
@Configuration
@Import({BeanConfig1.class,BeanConfig2.class})
publicclassMainConfig7{
}

上面经过@Import引入了别的2个装备类

来个测验用例加载MainConfig7装备类

@Test
publicvoidtest7(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig7.class);
context.getBeansOfType(String.class).forEach((beanName,bean)->{
System.out.println(String.format("%s->%s",beanName,bean));
});
}

上面从容器中获取String类型的bean,然后输出。

运转输出

name->路人甲Java

现在咱们有个需求

当容器中有Service这种类型的bean的时分,BeanConfig2才收效。

很简单吧,加个Condition就行了,内部判别容器中是否有Service类型的bean,持续

来个自界说的Condition

packagecom.javacode2018.lesson001.demo25.test7;
importorg.springframework.beans.factory.config.ConfigurableListableBeanFactory;
importorg.springframework.context.annotation.Condition;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.core.type.AnnotatedTypeMetadata;
publicclassMyCondition1implementsCondition{
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
//获取spring容器
ConfigurableListableBeanFactorybeanFactory=context.getBeanFactory();
//判别容器中是否存在Service类型的bean
booleanexistsService=!beanFactory.getBeansOfType(Service.class).isEmpty();
returnexistsService;
}
}

上面代码很简单,判别容器中是否有IService类型的bean。

BeanConfig2上运用Condition条件判别

@Configuration
@Conditional(MyCondition1.class)
publicclassBeanConfig2{
@Bean
publicStringname(){
return"路人甲Java";
}
}

再次运转test7输出

无任何输出

为什么?

在文章前面咱们说过,装备类的处理睬顺次经过2个阶段:装备类解析阶段和bean注册阶段,Condition接口类型的条件会对这两个阶段都有用,解析阶段的时分,容器中是还没有Service这个bean的,装备类中经过@Bean注解界说的bean在bean注册阶段才会被注册到spring容器,所以BeanConfig2在解析阶段去容器中是看不到Service这个bean的,所以就被拒绝了。

此刻咱们需求用到ConfigurationCondition了,让条件判别在bean注册阶段才起效。

自界说一个ConfigurationCondition类

packagecom.javacode2018.lesson001.demo25.test7;
importorg.springframework.beans.factory.config.ConfigurableListableBeanFactory;
importorg.springframework.context.annotation.ConditionContext;
importorg.springframework.context.annotation.ConfigurationCondition;
importorg.springframework.core.type.AnnotatedTypeMetadata;
publicclassMyConfigurationCondition1implementsConfigurationCondition{
@Override
publicConfigurationPhasegetConfigurationPhase(){
returnConfigurationPhase.REGISTER_BEAN;//@1
}
@Override
publicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){
//获取spring容器
ConfigurableListableBeanFactorybeanFactory=context.getBeanFactory();
//判别容器中是否存在Service类型的bean
booleanexistsService=!beanFactory.getBeansOfType(Service.class).isEmpty();
returnexistsService;
}
}

@1:指定条件在bean注册阶段,这个条件才有用

matches办法中的内容直接仿制过来,判别规矩不变。

修正BeanConfig2的类容

@Conditional(MyCondition1.class)
替换为
@Conditional(MyConfigurationCondition1.class)

再次运转test7输出

name->路人甲Java

此刻name这个bean被输出了。

能够再试试将BeanConfig1中service办法上面的@Bean去掉,此刻Service就不会被注册到容器,再运转一下test7,会发现没有输出了,此刻BeanConfig2会失效。

判别bean存不存在的问题,一般会运用ConfigurationCondition这个接口,阶段为:REGISTER_BEAN,这样能够保证条件判别是在bean注册阶段履行的。

对springboot比较了解的,它里面有许多@Conditionxxx这样的注解,能够去看一下这些注解,许多都完成了ConfigurationCondition接口。

Spring中这块的源码

@Conditional注解是被下面这个类处理的

org.springframework.context.annotation.ConfigurationClassPostProcessor

又是这个类,说了许屡次了,非常重要的一个类,我们下去了多撸一下这个类的源码,这样了解起来更顺畅一些。

事例源码

https://gitee.com/javacode2018/spring-series

路人甲java一切事例代码以后都会放到这个上面,我们watch一下,能够持续重视动态。

总结

  1. @Conditional注解能够标示在spring需求处理的方针上(装备类、@Bean办法),相当于加了个条件判别,经过判别的成果,让spring觉得是否要持续处理被这个注解标示的方针
  2. spring处理装备类大致有2个进程:解析装备类、注册bean,这两个进程中都能够运用@Conditional来进行操控spring是否需求处理这个进程
  3. Condition默认会对2个进程都有用
  4. \