SpringbootConditionalOnClass原理解析
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;
le.gson.Gson;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.dition.ConditionalOnClass;
import org.springframework.dition.ConditionalOnMissingBean;
import t.annotation.Bean;
import t.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 = Set就能获取到需要判断ConditionalOnclass有哪些类了,实际上上述过程是⾮常快速的。
下⾯的速度会明显变慢,原因是将根据上述执⾏结果,出对应的Class是否在classpath*下可以到
private ConditionOutcome getOutcome(Set<String> candidates) {
try {
List<String> missing = getMatches(candidates, MatchType.MISSING,
this.beanClassLoader);
if (!missing.isEmpty()) {
Match(
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 = DefaultClassLoader();
}
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.
*/
spring framework jar包String[] name() default {};
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论