SpringBoot-SpringBoot自动配置底层源码解析

1. @EnableAutoConfiguration源码解析
2. SpringBoot常用条件注解源码解析
3. SpringBoot之Mybatis自动配置源码解析
4. SpringBoot之AOP自动配置源码解析
5. SpringBoot Jar包启动过程源码解析
DeferredImportSelector接口
DeferredImportSelector和ImportSelector的区别在于:
        1. 在解析ImportSelector时,所导入的配置类会被直接解析,而DeferredImportSelector导入的配置类会延迟进行解析(延迟在其他配置类都解析完之后)
        2. DeferredImportSelector支持分组,可以实现getImportGroup方法以及定义Group对象,就相当于指定了DeferredImportSelector所导入进来的配置类所属的组,比如SpringBoot就把所有自动配置类单独做了分组 AutoConfigurationGroup
常用条件注解
SpringBoot中的常用条件注解有:
1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
4. ConditionalOnClass:是否存在某个类
5. ConditionalOnMissingClass:是否缺失某个类
6. ConditionalOnExpression:指定的表达式返回的是true还是false
7. ConditionalOnJava:判断Java版本
8. ConditionalOnWebApplication:当前应用是不是一个Web应用
9. ConditionalOnNotWebApplication:当前应用不是一个Web应用
10. ConditionalOnProperty:Environment中是否存在某个属性
        当然我们也可以利用@Conditional来自定义条件注解。
        条件注解是可以写在类上和方法上的,如果某个条件注解写在了自动配置类上,那该自动配置类会不 会生效就要看当前条件能不能符合,或者条件注解写在某个@Bean修饰的方法上,那这个Bean生不生效就看当前条件符不符合。
具体原理是:
        1.Spring在解析某个自动配置类时,会先检查该自动配置类上是否有条件注解,如果有,则进一步判断该条件注解
所指定的条件当前能不能满足,如果满足了则继续解析该配置类,如果不满足则不进行解析了,也就是配置类所定义的Bean都得不到解析,也就是相当于没有这些Bean了。
        2.同理,Spring在解析某个@Bean的方法时,也会先判断方法上是否有条件注解,然后进行解析,如果不满足条件,则该Bean不会生效
我们可以发现,SpringBoot的自动配置,实际上就是SpringBoot的源码中预先写好了一些配置类,预 先定义好了一些Bean,我们在用SpringBoot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean到底生不生效,就看具体所指定的条件了。
自定义条件注解
SpringBoot中众多的条件注解,都是基于Spring中的@Conditional来实现的,所以我们先来用一下
@Conditional注解。
先来看下@Conditional注解的定义:
@Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface Conditional {

 /**
* All {@link Condition} classes that must {@linkplain Condition#matches match} 
* in order for the component to be registered. 
*/ 
 Class<? extends Condition>[] value();

}
根据定义我们在用@Conditional注解时,需要指定一个或多个Condition的实现类,所以我们先来提供一个实现类:
public class ZhouyuCondition implements Condition {

 @Override
 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false; 
 }

 }
很明显,我们可以在matches方法中来定义条件逻辑:
        1.ConditionContext:表示条件上下文,可以通过ConditionContext获取到当前的类加载器、BeanFactory、 Environment环境变量对象
        2.AnnotatedTypeMetadata:表示当前正在进行条件判断的Bean所对应的类信息,或方法信息(比如@Bean定 义的一个Bean),可以通过AnnotatedTypeMetadata获取到当前类或方法相关的信息,从而就可以拿到条件注 解的信息,当然如果一个Bean上使用了多个条件注解,那么在解析过程中都可以获取到,同时也能获取Bean上定义的其他注解信息
@ConditionalOnClass的底层工作原理
先来看一个案例:
@Configuration
 @ConditionalOnClass(name = "com.zhouyu.Jetty")
 @ConditionalOnMissingClass(value = "com.zhouyu.Tomcat")
 public class ZhouyuConfiguration {


 }
我们在ZhouyuConfiguration这个类上使用了两个条件注解:
        1.@ConditionalOnClass(name = "com.zhouyu.Jetty"):条件是项目依赖中存在 "com.zhouyu.Jetty"这个类,则 表示符合条件
        2.@ConditionalOnMissingClass(value = "com.zhouyu.Tomcat"):条件是项目依赖中不存 "com.zhouyu.Tomcat"这个类,则表示符合条件

 

这两个注解对应的都是 @Conditional(OnClassCondition.class) ,那在OnClassCondition类中是如 何对这两个注解进行区分的呢?
Spring在解析到ZhouyuConfiguration这个配置时,发现该类上用到了条件注解就会进行条件解析, 相关源码如下:
// 这是Spring中的源码,不是SpringBoot中的
 for (Condition condition : conditions) {
 ConfigurationPhase requiredPhase = null;
 if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition)
condition).getConfigurationPhase();

 }

 // 重点在这
if ((requiredPhase == null || requiredPhase == phase) &&
!condition.matches(this.context, metadata)) {

return true; 
 }
}
        conditions中保存了两个OnClassCondition对象,这段代码会依次调用OnClassCondition对象的 matches方法进行条件匹配,一旦某一个条件不匹配就不会进行下一个条件的判断了,这里return的 是true,但是这段代码所在的方法叫做shouldSkip,所以true表示忽略。
我们继续看OnClassCondition的matches()方法的实现。
        OnClassCondition类继承了FilteringSpringBootCondition,FilteringSpringBootCondition类又继 承了SpringBootCondition,而SpringBootCondition实现了Condition接口,matches()方法也是在SpringBootCondition这个类中实现的:
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
{

 // 获取当前解析的类名或方法名
 String classOrMethodName = getClassOrMethodName(metadata);
 try {
// 进行具体的条件匹配,ConditionOutcome表示匹配结果 
ConditionOutcome outcome = getMatchOutcome(context, metadata); 

// 日志记录匹配结果 
logOutcome(classOrMethodName, outcome); 
recordEvaluation(context, classOrMethodName, outcome); 

// 返回true或false 
return outcome.isMatch(); 
 }
catch (NoClassDefFoundError ex) {
// ... 
 }
catch (RuntimeException ex) {
 // ... 
 }
}
所以具体的条件匹配逻辑在getMatchOutcome方法中,而SpringBootCondition类中的
getMatchOutcome方法是一个抽象方法,具体的实现逻辑就在子类OnClassCondition中:
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {

ClassLoader classLoader = context.getClassLoader(); 
ConditionMessage matchMessage = ConditionMessage.empty(); 

// 拿到ConditionalOnClass注解中的value值,也就是要判断是否存在的类名
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class); 
 if (onClasses != null) {
在getMatchOutcome方法中的逻辑为:
// 判断onClasses中不存在的类 
List<String> missing = filter(onClasses, ClassNameFilter.MISSING,
classLoader);
// 如果有缺失的类,那就表示不匹配 
if (!missing.isEmpty()) { 
return
ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)

.didNotFind("required class", "required classes").items(Style.QUOTE, missing));

} 
// 否则就表示匹配 
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes") 
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT,
classLoader));

 }

 // 和上面类似,只不过是判断onMissingClasses是不是全部缺失,如果是则表示匹配
List<String> onMissingClasses = getCandidates(metadata,
ConditionalOnMissingClass.class);

 if (onMissingClasses != null) {
List<String> present = filter(onMissingClasses,
ClassNameFilter.PRESENT, classLoader);

if (!present.isEmpty()) { 
return
ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)

.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));

}
matchMessage =
matchMessage.andCondition(ConditionalOnMissingClass.class)

.didNotFind("unwanted class", "unwanted classes") 
.items(Style.QUOTE, filter(onMissingClasses,
ClassNameFilter.MISSING, classLoader));

 }
return ConditionOutcome.match(matchMessage);
}
在getMatchOutcome方法中的逻辑为:
        1.如果类或方法上有@ConditionalOnClass注解,则获取@ConditionalOnClass注解中的value属性,也就是要判断是否存在的类名
        2.利用ClassNameFilter.MISSING来判断这些类是否缺失,把缺失的类的类名存入missing集合
        3.如果missing不为空,则表示有类缺失,则表示不匹配,并利用ConditionMessage记录哪些类是缺失的,直接 return,表示条件不匹配
        4.否则,则表示条件匹配,继续执行代码
        5.如果类或方法上有ConditionalOnMissingClass注解,则获取ConditionalOnMissingClass注解中的value属 性,也就是要判断是否缺失的类名
        6.利用ClassNameFilter.PRESENT来判断这些类是否存在,把存在的类的类名存入present集合
        7.如果present不为空,则表示有类存在,则表示不匹配,并利用ConditionMessage记录哪些类是存在的,直接 return,表示条件不匹配
        8否则,则表示条件匹配,继续执行代码
        9.return,表示条件匹配
@Configuration
 @ConditionalOnClass(Tomcat.class)
 @ConditionalOnMissingClass(value = "com.zhouyu.Tomcat")
 public class ZhouyuConfiguration {


 }
1.如果@ConditionalOnClass条件匹配、@ConditionalOnMissingClass条件也匹配,那么getMatchOutcome 方法会执行两次
2.如果@ConditionalOnClass条件不匹配,那么getMatchOutcome方法会执行一次
3.如果@ConditionalOnClass条件匹配、@ConditionalOnMissingClass条件不匹配,那么getMatchOutcome方法也只会执行一次,因为在getMatchOutcome方法处理了这种情况
protected enum ClassNameFilter {

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); 
}

}; 

abstract boolean matches(String className, ClassLoader classLoader); 

static boolean isPresent(String className, ClassLoader classLoader) { 
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader(); 
} 
try { 
resolve(className, classLoader); 
return true; 
} 
catch (Throwable ex) { 
return false;
} 
} 

 }




protected static Class<?> resolve(String className, ClassLoader classLoader) throws
ClassNotFoundException {

主要就是用类加载器,来判断类是否存在。
@ConditionalOnBean和@ConditionalOnClass的底层实现应该是差不多的,一个是判断Bean存不
存在,一个是判断类存不存在,事实上也确实差不多。
首先@ConditionalOnBean和@ConditionalOnMissingBean对应的都是OnBeanCondition类,
OnBeanCondition类也是继承了SpringBootCondition,所以SpringBootCondition类中的
getMatchOutcome方法才是匹配逻辑:
 if (classLoader != null) {
return Class.forName(className, false, classLoader); 
 }
 return Class.forName(className);
 }

相关推荐

  1. SpringBoot-SpringBoot自动配置底层

    2024-01-25 07:40:01       32 阅读
  2. SpringBoot

    2024-01-25 07:40:01       42 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-25 07:40:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-25 07:40:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-25 07:40:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-25 07:40:01       20 阅读

热门阅读

  1. 使用django-admin来做erp,是否需要使用缓存数据库

    2024-01-25 07:40:01       36 阅读
  2. 数据结构练习3

    2024-01-25 07:40:01       29 阅读
  3. 江苏服务器租用要注意哪些方面?

    2024-01-25 07:40:01       34 阅读
  4. html 粒子效果文字特效

    2024-01-25 07:40:01       35 阅读
  5. Hadoop-MapReduce-源码跟读-客户端篇

    2024-01-25 07:40:01       28 阅读
  6. CentOS 安装 Ruby

    2024-01-25 07:40:01       41 阅读
  7. VSCode Python调试运行:json编写

    2024-01-25 07:40:01       30 阅读