Spring 学习笔记Spring Boot 核心技术

Spring 之 Condition 条件注解 实践和源码学习

2018-11-18  本文已影响20人  jwfy

Condition 是在spring4.0 增加的条件注解,通过这个可以功能可以实现选择性的注入Bean操作,接下来先学习下Condition是如何使用的,然后分析spring源码了解其中的实现原理。

更多可看==>Spring&SpringBoot 实践和源码学习

Demo

注意:以下三个代码块分属不同的文件,便于说明具体问题

@Bean("contectService")
@Conditional(LoadConditional.class)
// 条件控制,如果对应的match操作返回true,则会注入该bean
// 否则会跳过处理该bean
public ContextService contextService() {
    return new ContextService();
}

public class LoadConditional implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 查看在Bootstrap的ENV的值是否等于test
        // 后面会介绍这个context上下文,其中包含了整个的bean工厂内容
        return Bootstrap.ENV.equals("test");
    }
}

@SpringBootApplication
@ComponentScan("com.demo.boot")
@EnableSwagger2
public class Bootstrap {

    public static String ENV = "test";

    public static void main(String[] args) {
        SpringApplication.run(Bootstrap.class);
    }

}

这样就简单的通过@Conditional实现选择性的注入Bean操作,实际开发中,主要是为了区分测试环境和线上环境,其实在spring已经添加了一部分常用的条件注解,如下表格

Conditions 描述
@ConditionalOnBean 在存在某个bean的时候
@ConditionalOnMissingBean 不存在某个bean的时候
@ConditionalOnClass 当前classpath可以找到某个类型的类时
@ConditionalOnMissingClass 当前classpath不可以找到某个类型的类时
@ConditionalOnResource 当前classpath是否存在某个资源文件
@ConditionalOnProperty 当前jvm是否包含某个系统属性为某个值
@ConditionalOnWebApplication 当前spring context是否是web应用程序

源码学习

在之前的一篇笔记SpringBoot 之 EnableAutoConfiguration 实践和源码学习 中写道@Configuration实现bean的注入,那么条件注解肯定就类似于一道阀门在实现bean的注入前通过条件筛选去完成选择性的bean注入,接着上面那篇学习笔记,来到ConfigurationClassPostProcessor 类,关于这个类不再过多介绍

随着Spring的BPP的invoke来到processConfigBeanDefinitions 方法

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    //  ......忽略前面一大推代码
    // 遍历当前装载到Spring 容器的所有类,获取其中包含了@Configuration的bean到candidates中
    // 依次遍历解析Configuration的类
    do {
        parser.parse(candidates);
        parser.validate();
        // 解析完成,获取所有的类,暂时还不包含@Bean的信息

        Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // Read the model and create bean definitions based on its content
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        // 配置类读取信息,进行条件筛选
        // 也就是我们本篇学习笔记的Condition注解原理所在地
        
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
             // ....
}

来到ConfigurationClassBeanDefinitionReader 类中,肯定会进行类的遍历操作,获取其中的@Bean,这个方法非常的关键,包含了很多功能

ConfigurationClassBeanDefinitionReader 类

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
    // 传递的configClass就是添加了@Configuration 的包装类   // trackedConditionEvaluator 记录着类是否会拦截的处理类
}
image image

再来到其实现的细节方法中

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
        TrackedConditionEvaluator trackedConditionEvaluator) {

    if (trackedConditionEvaluator.shouldSkip(configClass)) {
           // 如果该configClass被过滤掉,也就是不应该实例化
           // 先直接进入到该方法中看看实现细节
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
            // 从context上下文中移除该config的bean信息
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
        // 直接返回
    }
     // 。。。。。 待续
}
private class TrackedConditionEvaluator {
    private final Map<ConfigurationClass, Boolean> skipped = new HashMap<ConfigurationClass, Boolean>();
    // 保存着所有config类的筛选状态
    public boolean shouldSkip(ConfigurationClass configClass) {
        Boolean skip = this.skipped.get(configClass);
        if (skip == null) {
              // 新处理的类信息
            if (configClass.isImported()) {
                  // 如果该配置类包含@Import导入的类
                boolean allSkipped = true;
                for (ConfigurationClass importedBy : configClass.getImportedBy()) {
                      // 以此遍历迭代执行
                    if (!shouldSkip(importedBy)) {
                          // 迭代每一个被Import导入的类,如果返回false,也就是不应该被忽略的类
                          // 则直接break,是否会导致 后面的需要跳过的类被忽略?
                        allSkipped = false;
                        break;
                    }
                }
                if (allSkipped) {
                    // 所有通过@Import的类全部跳过处理
                    skip = true;
                }
            }
            if (skip == null) {
                skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
                // 获取元信息(也就是类似注解信息)判断是否可以跳过。重点关注!
            }
            this.skipped.put(configClass, skip);
        }
        return skip;
    }
}

ConditionEvaluator 类

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
          // 如果没有元信息,或者注解类中不包含Conditional类,就直接返回false,表示不能跳过
          // 注解类Conditional的用途就在这里了!!!!
        return false;
    }

    if (phase == null) {
          // 默认传递的phase是ConfigurationPhase.REGISTER_BEAN
        if (metadata instanceof AnnotationMetadata &&
                ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }

    List<Condition> conditions = new ArrayList<Condition>();
    for (String[] conditionClasses : getConditionClasses(metadata)) {
          // 获取包含了@Conditional 的value值
          // 类比demo中就是@Conditional(LoadConditional.class)的LoadConditional类全称
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            // 完成LoadConditional类的实例化(必须是不带参数的构造器)
            conditions.add(condition);
        }
    }

    AnnotationAwareOrderComparator.sort(conditions);
    // 按照order进行排序

    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        if (requiredPhase == null || requiredPhase == phase) {
            if (!condition.matches(this.context, metadata)) {
                  // 这个matchee 方法就是自定义实现的方法,具体如上demo中的LoadConditional的重载方法
                  // 这是个循环的方法,意味着可以有多个条件注解,
                  // 而且一旦出现了一个被过滤了则直接认定为需要被跳过
                return true;
            }
        }
    }
    return false;
}

该方法就是获取configuration类的注解信息,然后调用相关条件过滤的matches方法获取匹配结果

image

这个图也正好说明了传递到matches方法的context包含的内容,例如Spring的Bean工厂,以及上下文环境等信息(一般这个上下文环境使用的比较多)

到这里整个过程感觉比较清晰,但是这是从类的角度触发,处理的也是@Configuration类,而没有@Bean的处理过程

那么继续来到loadBeanDefinitionsForConfigurationClass 类中

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
        TrackedConditionEvaluator trackedConditionEvaluator) {

    if (trackedConditionEvaluator.shouldSkip(configClass)) {
           // 上面已经说了
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
    }

    if (configClass.isImported()) {
           // 包含了@Import的情况
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
          // 这个地方是处理@Bean的地方,也就是我们需要关注的地方,先忽略下
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 处理使用了@ImportResource 导入的xml、groovy文件等处理入口
    // 肯定是按照读取xml的原理一样,声明一个reader然后解析resource的内容,然后填充到register中
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

回过头关注处理@Bean的细节

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    ConfigurationClass configClass = beanMethod.getConfigurationClass();
    // 获取方法的@Configuration信息
    MethodMetadata metadata = beanMethod.getMetadata();
    String methodName = metadata.getMethodName();

    if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
          // 这个就是上面说没有处理method的另一个处理入口
          // 返回true就意味着可以被跳过处理
        configClass.skippedBeanMethods.add(methodName);
        return;
    }
    if (configClass.skippedBeanMethods.contains(methodName)) {
        return;
    }

    // Consider name and any aliases
    // name值是一个String[] 的样式,主要是处理别名的情况
    AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
    List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
    String beanName = (!names.isEmpty() ? names.remove(0) : methodName);

    // Register aliases even when overridden
    for (String alias : names) {
        this.registry.registerAlias(beanName, alias);
    }

    // Has this effectively been overridden before (e.g. via XML)?
    // springboot本身的重名会被IDEA类似工具发现,但是无法判断xml是否也存在
    // 主要是去重操作
    if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
        return;
    }

    ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
    beanDef.setResource(configClass.getResource());
    beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

    if (metadata.isStatic()) {
        // static @Bean method
        beanDef.setBeanClassName(configClass.getMetadata().getClassName());
        beanDef.setFactoryMethodName(methodName);
    }
    else {
        // instance @Bean method
        beanDef.setFactoryBeanName(configClass.getBeanName());
        beanDef.setUniqueFactoryMethodName(methodName);
    }
    beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

    AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

    Autowire autowire = bean.getEnum("autowire");
    if (autowire.isAutowire()) {
        beanDef.setAutowireMode(autowire.value());
    }

    String initMethodName = bean.getString("initMethod");
    if (StringUtils.hasText(initMethodName)) {
        beanDef.setInitMethodName(initMethodName);
    }

    String destroyMethodName = bean.getString("destroyMethod");
    if (destroyMethodName != null) {
        beanDef.setDestroyMethodName(destroyMethodName);
    }
    
    // 设置init、destroy方法等
    // scope范围等数据

    // Consider scoping
    ScopedProxyMode proxyMode = ScopedProxyMode.NO;
    AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
    if (attributes != null) {
        beanDef.setScope(attributes.getString("value"));
        proxyMode = attributes.getEnum("proxyMode");
        if (proxyMode == ScopedProxyMode.DEFAULT) {
            proxyMode = ScopedProxyMode.NO;
        }
    }

    // Replace the original bean definition with the target one, if necessary
    BeanDefinition beanDefToRegister = beanDef;
    if (proxyMode != ScopedProxyMode.NO) {
        BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
                new BeanDefinitionHolder(beanDef, beanName), this.registry,
                proxyMode == ScopedProxyMode.TARGET_CLASS);
        beanDefToRegister = new ConfigurationClassBeanDefinition(
                (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
    }

    if (logger.isDebugEnabled()) {
        logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
                configClass.getMetadata().getClassName(), beanName));
    }

    this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

到这里整个的源码学习就完全结束了,整个的过程也还是按照spring原本的套路去实现的,从一个Spring BPP作为入口处理

上一篇下一篇

猜你喜欢

热点阅读