zoukankan      html  css  js  c++  java
  • Spring boot ConditionalOnClass原理解析

    Spring boot如何自动加载

    对于Springboot的ConditionalOnClass注解一直非常好奇,原因是我们的jar包里面可能没有对应的class,而使用ConditionalOnClass标注的Configuration类又import了这个类,那么如果想加载Configuration类,就会报ClassNotFoundException,那么又如何取到这个类上的注解呢

    SpringFactoriesLoader获取"META-INF/spring.factories"路径下的所有文件,解析出需要自动加载的类
    判断的逻辑为配置中的org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
    xxx即为我们配置的需要自动加载的@Configuration标注的类

    解析出需要加载的所有Configuration类,无论该类是否被ConditionOnClass注解声明,都使用OnClassCondition类进行match,判断是否需要加载当前类

    做这个解析有点耗时,spring boot将筛选出了所有Configuration类数目的一半,单独放到另外一个线程中执行,这样相当于并发两个线程解析

    可以参照OnClassCondition类的getOutcomes方法

    具体执行解析操作的类为StandardOutcomesResolver,方法:resolveOutcomes()

    以Gson自动配置类org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration 为例

    package org.springframework.boot.autoconfigure.gson;
    
    import com.google.gson.Gson;
    
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * {@link EnableAutoConfiguration Auto-configuration} for Gson.
     *
     * @author David Liu
     * @since 1.2.0
     */
    @Configuration
    @ConditionalOnClass(Gson.class)
    public class GsonAutoConfiguration {
    
    	@Bean
    	@ConditionalOnMissingBean
    	public Gson gson() {
    		return new Gson();
    	}
    
    }
    

    假如当前classpath下并没有引入Gson类的jar包

    	private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses,
    			int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
    		ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
    		for (int i = start; i < end; i++) {
    			String autoConfigurationClass = autoConfigurationClasses[i];  //GsonAutoConfiguration类的字符串
    			Set<String> candidates = autoConfigurationMetadata
    					.getSet(autoConfigurationClass, "ConditionalOnClass");  //获取当前class上的ConditionOnClass注解配置
    			if (candidates != null) {
    				outcomes[i - start] = getOutcome(candidates);
    			}
    		}
    		return outcomes;
    	}
    

    AutoConfigurationMetadataLoader类将加载META-INF/spring-autoconfigure-metadata.properties下所有的配置,如果你使用了ConditionalOnClass注解,需要写到文件中,如

    	org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration.ConditionalOnClass=io.searchbox.client.JestClient
    

    这样Set candidates = autoConfigurationMetadata.getSet就能获取到需要判断ConditionalOnclass有哪些类了,实际上上述过程是非常快速的。
    下面的速度会明显变慢,原因是将根据上述执行结果,找出对应的Class是否在classpath*下可以找到

    	private ConditionOutcome getOutcome(Set<String> candidates) {
    			try {
    				List<String> missing = getMatches(candidates, MatchType.MISSING,
    						this.beanClassLoader);
    				if (!missing.isEmpty()) {
    					return ConditionOutcome.noMatch(
    							ConditionMessage.forCondition(ConditionalOnClass.class)
    									.didNotFind("required class", "required classes")
    									.items(Style.QUOTE, missing));
    				}
    			}
    			catch (Exception ex) {
    				// We'll get another chance later
    			}
    			return null;
    		}
    
    	}
    

    使用MatchType.MISSING来判断,如果不为空,则说明缺少这个类了。

    	private enum MatchType {
    
    		PRESENT {
    
    			@Override
    			public boolean matches(String className, ClassLoader classLoader) {
    				return isPresent(className, classLoader);
    			}
    
    		},
    
    		MISSING {
    
    			@Override
    			public boolean matches(String className, ClassLoader classLoader) {
    				return !isPresent(className, classLoader);
    			}
    
    		};
    
    		private static boolean isPresent(String className, ClassLoader classLoader) {
    			if (classLoader == null) {
    				classLoader = ClassUtils.getDefaultClassLoader();
    			}
    			try {
    				forName(className, classLoader);
    				return true;
    			}
    			catch (Throwable ex) {
    				return false;
    			}
    		}
    
    		private static Class<?> forName(String className, ClassLoader classLoader)
    				throws ClassNotFoundException {
    			if (classLoader != null) {
    				return classLoader.loadClass(className);
    			}
    			return Class.forName(className);
    		}
    
    		public abstract boolean matches(String className, ClassLoader classLoader);
    
    	}	
    

    最终回到了原始的方法,调用classLoader.loadClass(className)来判断类是否在classpath下,加载类相对于内存计算,比较耗时,这也是为什么需要再开一个线程和主线程一起工作的原因,使用Thread.join()来等待线程结束,并获取最终结果

    延伸

    既然加载ConfigurationBean时,用ClassNotFound就能发现对应的类没有在classpath下,又何必多此一举,绕这么大个弯来发现没有对应的class呢?

    • 原因是ConditionalOnClass还支持输入字符串类型的class name,在Configuration中可以面向接口编程的方式来生成bean

    • Spring boot还提供了类似ConditionalOnBean的注解,有可能一个class在classpath下,而不是spring里面的bean;

      @Target({ ElementType.TYPE, ElementType.METHOD })
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Conditional(OnClassCondition.class)
      public @interface ConditionalOnClass {

        /**
         * The classes that must be present. Since this annotation is parsed by loading class
         * bytecode, it is safe to specify classes here that may ultimately not be on the
         * classpath, only if this annotation is directly on the affected component and
         * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
         * use this annotation as a meta-annotation, only use the {@link #name} attribute.
         * @return the classes that must be present
         */
        Class<?>[] value() default {};
      
        /**
         * The classes names that must be present.
         * @return the class names that must be present.
         */
        String[] name() default {};
      

      }

  • 相关阅读:
    AtCoder Regular Contest 093
    AtCoder Regular Contest 094
    G. Gangsters in Central City
    HGOI 20190711 题解
    HGOI20190710 题解
    HGOI 20190709 题解
    HGOI 20190708 题解
    HGOI20190707 题解
    HGOI20190706 题解
    HGOI 20190705 题解
  • 原文地址:https://www.cnblogs.com/windliu/p/9988754.html
Copyright © 2011-2022 走看看