美文网首页Spring 学习笔记Spring Boot 核心技术
Spring 之 Condition 条件注解 实践和源码学习

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

作者: jwfy | 来源:发表于2018-11-18 15:07 被阅读20次

    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作为入口处理

    相关文章

      网友评论

        本文标题:Spring 之 Condition 条件注解 实践和源码学习

        本文链接:https://www.haomeiwen.com/subject/dyrgfqtx.html