• 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 {};
      

      }

  • 相关阅读:
    spring开发_Spring+Hibernate_HibernateDaoSupport
    java开发_STMP邮箱客户端_发送邮件
    struts2开发_userlogin_模拟用户登录
    spring开发_邮箱注册_激活_获取验证码
    MFC笔记(1)
    MFC笔记(2)菜单
    wpf控件开发基础(5) 依赖属性实践
    wpf控件开发基础(3) 属性系统(2)
    wpf控件开发基础(2) 属性系统(1)
    Caliburn笔记依赖注入容器(wpf框架)
  • 原文地址:https://www.cnblogs.com/windliu/p/9988754.html
Copyright © 2020-2023  润新知