0
点赞
收藏
分享

微信扫一扫

Spring 如何控制 bean 注入,全靠了这个注解

沈芏 2022-03-11 阅读 101

前言

在 spring 工程中经常能看到在服务配置中添加了某些服务配置后,这个服务就能用了,例如在yaml文件中配置了下面的配置后,oauth2在校验token时就不会去调动本地的服务,而是会调用token-info-uri中设置的uri来获取token的校验结果

security:
  oauth2:
    resource:
      token-info-uri:
    client:
      client-id: 
      client-secret:

这是如何做到的呢?答案就是@Conditional,通过这个注解,我们就可以动态控制 spring 服务中某一些 bean 是否实例化并放入 spring 容器中,从而控制是否提供对应功能。接下来我们就来看下这个魔力的注解。

使用方式

首先我们来需要知道这个注解怎么用,并且能够达到的效果。
该注解往往是定义在配置类中,放在创建bean的对象上,例如

/**
* 在dbService上定义有@Conditional注解,注解中的值就是需要判断的实体
**/
@Configuration
public class DBConfig {

    @Bean
    @Conditional(CustomCondition.class)
    public DBService dbService() {
        DBService dbService = new DBService();
        return dbServie;
    }
}

/**
* 实现Condition接口,该接口只有一个方法,该方法就是判断是否创建bean实例并放入spring容器的关键,如果该方法返回true,则创建,返回false就不创建。
**/
public class CustomCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return true;
    }
}

在上面的例子中,CustomCondition中的matches方法一定会返回true,表示dbService一定会创建出来,同样如果matches方法返回false的话,则dbService就不会被创建。如果dbService被创建出来,我们就可以通过编写代码,调用dbService服务和数据库正常交流了。

配置控制

讲到这里,有些小伙伴就要问了,文章的一开始不是说通过配置来控制么,这里也没法控制呀?
其实是因为在matches方法中我写的过于简单了,直接返回了true,我们可以通过读取配置中是否存在某个配置,选择返回true,还是false,这样就可以通过配置来动态管理了。
那么我们又该如果获取配置信息呢?

public class CustomCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String dbServiceFlag = environment.getProperty("jdq.DBSercive.enable");
        if (StringUtils.hasText(dbServiceFlag) && "true".equalsIgnoreCase(dbServiceFlag)) {
            return true;
        } else {
            return false;
        }
    }
}

在 conditionContext 中存储有服务上下文信息,可以通过上面的方式去获取服务配置内容。

jdq.DBSercive.enable=true

如果在配置文件上配置上面这个配置后,DBService服务便会生成,如果配置false或者没有配置,那么就不会生成DBService服务。

实现原理

在了解该注解实现方式后,我们就来看下他的实现原理。这个注解本质上就是控制 bean 对象是否导入到 spring 容器中。那么首先我们来看下spring 是如何处理 bean 对象的导入的

spring 工程在启动时首先会调用org.springframework.context.support.AbstractApplicationContext#refresh函数。
在这里插入图片描述
查看refresh可以发现invokeBeanFactoryPostProcessors这个函数,注释的大概意思就是调用已经注册到下上文中的factory processors。也就是BeanFactoryPostProcessor这个bean对象。可能有些同学有点懵了,BeanFactoryPostProcessor是啥玩意,听都没有听过,但是这个类可以说是非常的牛叉,他工作在 spring 启动时, 所有的 bean 定义已经加载好了,但是还没有初始化对应的对象(如果大家对类对象的加载和初始化不太懂的话,建议去学习一下JVM相关的内容,这里就不在解释了),在这个期间,我们可以使用这个类来修改 bean 的定义,就问强不强吧,无论你 bean 是如何定义, BeanFactoryPostProcessor都可以修改你的定义。并且在BeanFactoryPostProcessor在处理好后,就开始对应 bean 对象的初始化了,初始化后该初始化生成的对象就存在于 spring 容器中了。

那经过分析,我们可以看下invokeBeanFactoryPostProcessors函数来探究里面做了哪些工作。
在这里插入图片描述
注释主要说:实例化并调用所有的已经注册的BeanFactoryPostProcessor对象,根据order来先后调用。那我们可以考虑调用BeanFactoryPostProcessor对象是来干啥呢?带着这个问题我们来看下invokeBeanFactoryPostProcessors函数,主要就是这个函数做的工作了。

一点进去,我的天,这么长,说实话我截图一下子都截不下来,但是不要慌,spring 的代码绝对不会写无用的代码,我们可以按照功能点对这个函数进行划分。我们先来看最先开始的一部分
在这里插入图片描述

换到sublime中还能截的长一点,通过注释我们可以看到这些代码其实就是做了一件事,就是获取BeanDefinitionRegistryPostProcessor的实现类,并且对其不同的实现类分为了三类

  • 实现了PriorityOrdered的类
  • 实现了Ordered的类
  • 剩下的其他归为一类

BeanDefinitionRegistryPostProcessor大家可能又会感觉到陌生,这个其实也是一个接口,并且这个实现了BeanFactoryPostProcessor接口,BeanDefinitionRegistryPostProcessor接口上只有一个方法在这里插入图片描述
看这个方法的注释,就可以知道这个方式提供了一种能力,可以允许更多的 bean 定义加载进来,通俗的将就是可以调用这个方法来注册 bean。又是一个非常强悍的接口。

这里我先对BeanDefinitionRegistryPostProcessorBeanDefinitionPostProcessor做一下总结

  • BeanDefinitionPostProcessor是在bean加载完后,在bean实例化前处理,可以对bean的定义进行修改
  • BeanDefinitionRegistryPostProcessor是在bean加载中,可以使用这个来加载bean
    所以我们可以知道BeanDefinitionRegistryPostProcessor的作用时间一定是在BeanDefinitionPostProcessor之前的,这也是我们先看到调用invokeBeanDefinitionRegistryPostProcessors方法的原因

这时候需要关注到这个类,他实现了PriorityOrderedBeanDefinitionRegistryPostProcessor, 所以在第一类实现了PriorityOrdered就会被找到,然后调用invokeBeanDefinitionRegistryPostProcessors方法来执行所有找到BeanDefinitionRegistryPostProcessor中的processConfigBeanDefinitions方法,包括ConfigurationClassPostProcessor类。
在这里插入图片描述
看类上的注释,大意就是 @Configuraion 注解类的引导处理。

这样目标就很明确了,这个类应该就是处理 @Configuraion 注解类实例化的地方了,我们来看下这个类,里面的调用堆栈有点多,这里就不再每一个函数展开了,流程如下
在这里插入图片描述
当走到loadBeanDefinitionsForBeanMethod方法时
在这里插入图片描述
可以发现存在这个函数,没错,这个函数就是@Conditional注解起作用的地方
在这里插入图片描述
如果返回false,则表示不需要跳过bean对象初始化,bean就会被初始化进入spring容器中,反之则不会初始化对应的bean,最后初始化的函数是loadBeanDefinitionsForBeanMethod方法中的DefaultListableBeanFactory#registerBeanDefinition方法来进行bean的初始化。

@Conditional 注解扩展

在 spring 中@ConditionalOnProperty,@ConditionalOnBean等这类的注解我们一定使用过。这些注解本质上也就是在@Conditional注解上的扩展,我们就拿@ConditionalOnProperty为例来看下他是如何实现的。
在这里插入图片描述
这个注解的作用和文章一开始写的例子相似,都是如果配置中存在某种配置,就实例化对应的bean,当然 @ConditionalOnProperty里的功能比我上面例子中的功能多很多(在注解中的值中进行控制)。具体逻辑也是写在OnPropertyCondition
在这里插入图片描述
其中SpringBootCondition是模板类,实现了matches方法
在这里插入图片描述
那我们就一起来看下OnPropertyCondition类中的getMatchOutcome方法实现。
在这里插入图片描述
其他例如@ConditionalOnBean等其他注解同理,大家有兴趣可以去看下。

举报

相关推荐

0 条评论