zoukankan      html  css  js  c++  java
  • SpringBoot学习(三)--@Conditional按条件注册

    一、SpringBoot怎么判断自动配置需要注册?

    SpringBoot在扫描到META-INFO下的spring.factories下的AutoConfiguration类的时候是怎么去判断是否需要注册这个类的呢?这里就有一个很重要的注解@Conditional。在SpringBoot中存在大量的条件注解@ConditionOnXXX,这些注解在自动配置类的上面都能见到,比如:
    在这里插入图片描述
    在这里插入图片描述
    它们是怎么工作的呢?

    先看一下SpringBoot中的常用Condition注解:

    条件化注解 配置生效条件
    @ConditionalOnBean 配置了某个特定Bean
    @ConditionalOnMissingBean 没有配置特定的Bean
    @ConditionalOnClass Classpath里有指定的类
    @ConditionalOnMissingClass Classpath里没有指定的类
    @ConditionalOnExpression 给定的SpEL表达式的计算结果为true
    @ConditionalOnJava Java的版本匹配特定值或者一个范围值
    @ConditionalOnJndi 参数中给定的JNDI位置必须存在一个,如果没有给定参数,则要有JNDI InitialContext
    @ConditionalOnProperty 指定的配置属性要有一个明确的值
    @ConditionalOnResource Classpath里有指定的资源
    @ConditionalOnWebApplication 是一个Web应用程序
    @ConditionalOnNotWebApplication 不是Web应用程序

    二、SpringBoot中@Conditional简单使用

    SpringBoot中利用以上注解设置的简单示例:
    自定义的自动配置类:MyAutoConfigurationPerson

    @Configuration
    @EnableConfigurationProperties(MyPersonProperties.class)
    @ConditionalOnClass(PersonService.class)
    //该属性为true时,配置文件中缺少对应的value或name的对应的属性值,也会注入成功
    @ConditionalOnProperty(prefix = "test.person", matchIfMissing = true, name = {"name","address"})
    public class MyAutoConfigurationPerson {
    
        private MyPersonProperties myPersonProperties;
    
        public MyAutoConfigurationPerson (MyPersonProperties myPersonProperties) {
            this.myPersonProperties = myPersonProperties;
        }
    
        @Bean
        @ConditionalOnMissingBean(OrderService.class)
        public PersonService personService () {
            PersonService service = new PersonService();
            System.err.println("service在自动配置类被初始化");
            System.err.println(myPersonProperties.toString());
            return service;
        }
    
    }
    

    配置的属性类:

    @ConfigurationProperties(prefix = "test.person")
    public class MyPersonProperties {
        private String name="alen";
        private String address="SHANGHAI";
    /*省略getter、setter和toString*/
    }
    

    待注入的服务类两个

    public class PersonService {
      public PersonService () {
            System.err.println("PersonService被初始化。。。。");
        }
    }
    public class OrderService{   
       public OrderService () {
             System.out.println("OrderService被初始化。。。。");
        }
    }
    

    application.properties配置:

    test.person.address=BEIJING
    test.person.name=lucy
    

    测试的话,可以尝试下更改注解属性值、application.properties文件删除给定的配置、OrderService加上@Component注解、@ConditionalOnMissingBean改为@ConditionalOnBean等查看类加载情况。

    三、自定义@Conditional注解DEMO

    简单的自定义一个@Conditional注解,看他是如何工作的:
    自定义的注解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Conditional(TestConditional.class)
    public @interface ConditionOnTest {
        String environment () default "dev";
    }
    

    匹配用的Condition接口实现类

    public class TestConditional implements Condition {
        @Override
        public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnTest.class.getName());
            String environment =(String) attributes.get("environment");
            //如果environment.equals("test")返回true,则表示注册该类
            return environment.equals("test");
        }
    }
    

    测试类

    @Component
    @ConditionOnTest(environment = "test")
    public class PersonService {
        public PersonService () {
            System.err.println("PersonService被初始化。。。。");
        }
    }
    

    测试类上的注解值如果environment = “test”,将会在控制台“打印PersonService被初始化。。。。”。
    如果将此值修改,则在启动时将不会看到构造函数输出被打印。

    四、 @Conditional注解解析

    以自定义的@Conditional注解Demo为例查看@Conditional注解的作用逻辑。
    此类起作用肯定是TestConditional类在其中起了作用,进入debug模式,在matchs方法打上断点,简单查看下它的工作流程:
    1.从Debug可以看出,是从解析配置类的包扫描,扫描到该组件。
    在这里插入图片描述
    然后再去判断该组件是否需要跳过注册,可以看到最终调用到了org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata)方法
    大致逻辑为先判断是否存在@Conditional注解,如果存在,则获取conditionClasses 并实例化Condition 实现类在调用matchs方法进行判断。

    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
         //判断元数据是否存在Conditional注解
    		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    			return false;
    		}
    省略.....
           //获取Condition类,demo此处为TestConditional类
    		List<Condition> conditions = new ArrayList<>();
    		for (String[] conditionClasses : getConditionClasses(metadata)) {
    			for (String conditionClass : conditionClasses) {
    				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
    				conditions.add(condition);
    			}
    		}
            //排序
    		AnnotationAwareOrderComparator.sort(conditions);
    
    		for (Condition condition : conditions) {
    			ConfigurationPhase requiredPhase = null;
    			if (condition instanceof ConfigurationCondition) {
    				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
    			}
    			//此处调用了matchs方法判断
    			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    				return true;
    			}
    		}
    
    		return false;
    	}
    

    如果返回false,后续将不会放入候选的集合中,则Spring将不会注册该类。
    第一个Demo中的配置也是如此,可以看下@ConditionalOnClass(PersonService.class)注解,此注解底层依旧是@Conditional注解

    @Conditional(OnClassCondition.class)
    public @interface ConditionalOnClass {
    

    所以可以直接在OnClassCondition类的getOutcome方法上打上断点去查看。

    五、SpringBoot启动自动注册配置类解析

    SpringBoot的大致处理逻辑也是一样,简单看一下SpringBoot启动时去怎么选择哪些自动配置项的。

    1、SpringBoot中的Condition

    1.先看一下Condition接口在SpringBoot中的应用,Condition接口在SpringBoot中的基础类为SpringBootCondition类,在其上注释为:

    /**
     * Base of all {@link Condition} implementations used with Spring Boot. Provides sensible
     * logging to help the user diagnose what classes are loaded.
     * */
      public abstract class SpringBootCondition implements Condition {
      /*。。。。*/

    SpringBootCondition有很多子类,此处只展示本处演示所在意的类的类图,其他再说。。。(此处只关注AutoConfigurationImportFilter类,因为下面会用到
    在这里插入图片描述
    来简单看下SpringCondition类:
    在此类的matchs方法中并没有太多逻辑,只是一个模板方法有子类去实现.

    	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    		String classOrMethodName = getClassOrMethodName(metadata);
    		/*省略。。。。*/
    			//此处为abstract方法,由子类去实现各自的逻辑(模板方法)
    			ConditionOutcome outcome = getMatchOutcome(context, metadata);
    			//日志
    			logOutcome(classOrMethodName, outcome);
    			//记录
    			recordEvaluation(context, classOrMethodName, outcome);
    			return outcome.isMatch();
    	   /*省略。。。。*/
    

    FilteringSpringBootCondition类中也并没有实现getMatchOutcome()方法,那现在去OnWebApplicationCondition中具体看一下怎么实现的:

    /**
    * 根据元数据的配置来过滤自动配置类
    * @Param autoConfigurationClasses 候选自动配置类集合
    * @Param autoConfigurationMetadata 候选自动配置类元数据
    * @Return outcomes  匹配完的结果集
    */
        @Override
    	protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
    			AutoConfigurationMetadata autoConfigurationMetadata) {
    			//新建一个匹配结果的数组
    		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    		for (int i = 0; i < outcomes.length; i++) {
    			String autoConfigurationClass = autoConfigurationClasses[i];
    			if (autoConfigurationClass != null) {
    				//具体判断逻辑
    				//autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"),
    				//以this.properties.getProperty(className + "." + key)获取配置的参数,如果为空则取值null
    				outcomes[i] = getOutcome(
    						autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
    			}
    		}
    		return outcomes;
    	}
    

    具体的判断逻辑,如果没有匹配的属性值,则直接返回空,否则再去与属性值进行比较,进行判断。

    private ConditionOutcome getOutcome(String type) {
    		if (type == null) {
    			return null;
    		}
    		ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
    		//判断@ConditionalOnWebApplication上的type属性是否与源信息的type相等
    		if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
    			//判断是否存在该类,如果不存在则不匹配
    			if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
    				return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
    			}
    		}
    		/*省略。。。。*/
    		return null;
    	}
    

    2、SpringBoot注册自动配置类

    在第一篇中我们看到处理这些候选的自动配置类是在
    org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
    ————————》
    org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
    ————————》
    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry()此方法处,具体在:
    该行代码处configurations = getConfigurationClassFilter().filter(configurations);
    该行代码分两部分查看:

    2.1 getConfigurationClassFilter获取过滤器

    private ConfigurationClassFilter getConfigurationClassFilter() {
    		if (this.configurationClassFilter == null) {
    		//获取filters,此处进去我们可以看到其实例化了AutoConfigurationImportFilter类的三个子类
    			List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
    			//执行Aware方法,注入属性
    			for (AutoConfigurationImportFilter filter : filters) {
    				invokeAwareMethods(filter);
    			}
             //包装到ConfigurationClassFilter,并扫描到metadata赋值给autoConfigurationMetadata
    			this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
    		}
    		return this.configurationClassFilter;
    	}
    

    2.2 filter(configurations)执行过滤判断

    configurations由之前可知此处已经是所有相关Jar包中spring.factories中经过过滤和去重的配置信息

    List<String> filter(List<String> configurations) {
    			long startTime = System.nanoTime();
    			//候选的自动配置类
    			String[] candidates = StringUtils.toStringArray(configurations);
    			boolean skipped = false;
    			//三个过滤器进行过滤
    			for (AutoConfigurationImportFilter filter : this.filters) {
    			//调用各自的match方法---》在父类SpringBootApplication中实现----》实际调用方法为getOutcomes,由各子类实现
    				boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
    				for (int i = 0; i < match.length; i++) {
    					//判断该位置的元素是否通过匹配,不匹配设置为null,跳过设置为true,不直接返回候选结果
    					if (!match[i]) {
    						candidates[i] = null;
    						skipped = true;
    					}
    				}
    			}
    			if (!skipped) {
    				return configurations;
    			}
    			List<String> result = new ArrayList<>(candidates.length);
    			for (String candidate : candidates) {
    				if (candidate != null) {
    					result.add(candidate);
    				}
    			}
    		/*省略。。。。*/
    			return result;
    		}
    

    可以看到匹配的未找到的情况:
    在这里插入图片描述
    此处最终返回匹配完成的结果并做一些处理,最终调用到:
    org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports方法处进行注册:

    2.3 最终注册

    getImports()逻辑执行完毕,获取的集合为Iterable<Group.Entry>,Group.Entry中包含了获取的符合条件的候选配置类。再执行processImports方法直接注册。

    public void processGroupImports() {
    			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                    //grouping.getImports()为返回的需要导入的自动配置类的集合
    				grouping.getImports().forEach(entry -> {
    					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
    					try {
    					//此处直接调用processImports注册该类
    					//this.importStack.registerImport(
    					//			currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
    						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
    								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
    								exclusionFilter, false);
    					}
    					catch (BeanDefinitionStoreException ex) {
    						throw ex;
    					}
    					catch (Throwable ex) {
    						throw new BeanDefinitionStoreException(
    								"Failed to process import candidates for configuration class [" +
    										configurationClass.getMetadata().getClassName() + "]", ex);
    					}
    				});
    			}
    		}
    

    到此,SpringBoot自动配置类注册完成。

  • 相关阅读:
    获取Oracle、SqlServer、Access中表名、字段和主键(转)
    Oracle事务控制总结
    Oracle数据类型
    Oracle中的数据字典技术及常用数据字典总结
    asp.net中的<%%>形式的详细用法总结
    一道sql面试题的解答
    求ax(2)+bx+c=0的解
    求发票的下一个号码
    软件设计师2008年12月下午试题4(C语言 动态规划)
    软件设计师1991下午试题1(流程图解析)
  • 原文地址:https://www.cnblogs.com/demo-alen/p/13547207.html
Copyright © 2011-2022 走看看