文章目录
  1. 1. 编码实现手动Bean注入
    1. 1.1. 1、定义ImportBeanDefinitionRegistrar实现类
      1. 1.1.1. 方式1:基于包路径的扫描
      2. 1.1.2. 方式2:直接注册BeanDefinition
      3. 1.1.3. 对比方式1方式2
    2. 1.2. 2、定义ClassPathBeanDefinitionScanner实现类
      1. 1.2.1. registerFilters分析
    3. 1.3. 3、自定义注解
    4. 1.4. 4、配置ImportBeanDefinitionRegistrar实现类
    5. 1.5. 4、测试
  2. 2. 总结
  3. 3. 下期预告:

本文开始,我们将系统地对Spring框架的扩展点进行学习,通过案例分析与图例结合,step by step地对Spring看似神秘的扩展点的机理与应用进行研究。

首先通过一张图对Spring框架各种扩展点的调用顺序(Bean生命周期)进行先入为主的概览。

0.png

可以看到图片的一开始便是Spring对Bean定义(BeanDefinition)进行解析和注册,Bean的注册主要就是通过ImportBeanDefinitionRegistrar实现的。

Spring框架主要就是通过ImportBeanDefinitionRegistrar实现对bean的动态注册。源码如下:

public interface ImportBeanDefinitionRegistrar {
    // 通过解析给定的注解元信息,向Spring容器中注册Bean定义
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {

        registerBeanDefinitions(importingClassMetadata, registry);
    }
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }
}

实现ImportBeanDefinitionRegistrar接口的类的都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化的,同时它也可以被aop、validator等机制处理。

编码实现手动Bean注入

日常的业务开发中,我们很少会通过ImportBeanDefinitionRegistrar来对bean进行注入。

而是通过xml文件声明或者注解如:@Component、@Service、@Bean等方式对bean进行注入和声明。

那么什么场景下才需要通过ImportBeanDefinitionRegistrar注册并注入bean呢?

在中间件开发场景下,就会用到手动bean注入。原因在于中间件/框架的开发者并不知道调用方/框架使用者是通过什么方式对bean进行注入的。

当然我们也可以让使用者们显式的对框架中的bean进行定义,但是这样就显著的增加了工作量和出错率,因此对于框架开发而言,常常通过ImportBeanDefinitionRegistrar实现bean的隐式注入和声明,减少调用方整合框架的复杂度。

我们通过一个模拟场景来介绍一下如何通过编码实现bean的手动隐式注入。、

1、定义ImportBeanDefinitionRegistrar实现类

首先定义一个实现ImportBeanDefinitionRegistrar接口的类,并编写bean注册逻辑。

public class MyBeanDefinationRegistry implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanFactoryAware {

    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        MyClassPathBeanDefinitionScanner scanner = new MyClassPathBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.registerFilters();
        scanner.doScan("com.spring.framework");

        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(TestBean.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
        registry.registerBeanDefinition("testBean", genericBeanDefinition);
    }
}

重点关注 BeanDefinitionRegistry 方法,这里提供了两种bean扫描方式。

方式1:基于包路径的扫描

MyClassPathBeanDefinitionScanner scanner = 
    new MyClassPathBeanDefinitionScanner(registry, false);
scanner.setResourceLoader(resourceLoader);
scanner.registerFilters();
scanner.doScan("com.spring.framework");
  1. 自定义一个ClassPathBeanDefinitionScanner实例,并将bean定义注册器BeanDefinitionRegistry引用传递进去,这是一种委托机制;
  2. 设置ResourceLoader,ResourceLoader的引用通过ResourceLoaderAware获得,并指向当前类的成员变量;
  3. 调用registerFilters方法(该方法为自定义方法,本质是调用了addIncludeFilter),让Spring去扫描带有特定标志的类进行管理与加载;(具体的代码稍后进行分析);
  4. 调用doScan,传递需要扫描的包路径,这个路径就是框架开发者自定义的包路径,该路径下存放的就是框架本身的bean,这个路径是完全由框架的开发者决定的,而且我们一般可以认为,该路径一旦定义就不会更改。并且该路径也不适合暴露给框架的调用者

方式2:直接注册BeanDefinition

GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(TestBean.class);
genericBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
registry.registerBeanDefinition("testBean", genericBeanDefinition);

方式2比较简单,但是相对的也比方式1繁琐。

TestBean 是模拟的一个框架的内部bean组件,实际开发中可以根据需要填充必要的属性和方法,这里只是作为演示。

public class TestBean {
}

通过声明GenericBeanDefinition,并未其添加需要注册的Bean的class,scope(单例or多例),beanName等属性,具体的属性可以看下图:

1.png

最后通过 registry.registerBeanDefinition 将设置好属性的GenericBeanDefinition注册,并设置beanName;

对比方式1方式2

通过代码我们可以直观的看到,方式1比方式2更加方便,可以实现批量bean的扫描与注入;

而方式2则需要逐个bean进行注入,但是相对的,方式2也更加灵活,能够实现 细粒度 的beanDefinition声明和定义。

2、定义ClassPathBeanDefinitionScanner实现类

通过定义ClassPathBeanDefinitionScanner的实现类,告诉Spring需要对哪些类进行管理(addIncludeFilter)以及不需要关注哪些类(addExcludeFilter)。

public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    /**
    * 比较重要的一个点就是registerFilters()这个方法,
    * 在里面我们可以定义让Spring去扫描带有特定标志的类选择进行管理或者是选择不管理;
    * 通过addIncludeFilter()方法和通过addExcludeFilter()方法;
    */
    protected void registerFilters() {
        /**
        *  TODO addIncludeFilter  满足任意includeFilters会被加载
        */
        addIncludeFilter(new AnnotationTypeFilter(SnoWalkerAutoInject.class));
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        return super.doScan(basePackages);
    }
}

可以看到,扫描器ClassPathBeanDefinitionScanner扫描类路径上的需要被管理的类,通过BeanFactory创建Bean给ApplicationComtext(Spring容器)管理;

registerFilters分析

registerFilters是自定义的方法,核心的逻辑就是通过addIncludeFilter添加了一个包扫描的规则:

这里是通过注解类型的Filter通知Spring容器对添加了SnoWalkerAutoInject自定义注解的bean进行管理。

我们可以看到,自定义的MyClassPathBeanDefinitionScanner重写了父类的doScan方法,本质上调用了父类的doScan,以实现对指定路径下的bean进行扫描。

最终实际上是在ApplicationContext中调用了doScan,实现了对bean定义的扫描及实例化,我们可以看一下源码实现:

/**
 * Create a new AnnotationConfigApplicationContext, scanning for components
 * in the given packages, registering bean definitions for those components,
 * and automatically refreshing the context.
 * @param basePackages the packages to scan for component classes
 */
public AnnotationConfigApplicationContext(String... basePackages) {
    this();
    scan(basePackages);
    refresh();
}

AnnotationConfigApplicationContext构造方法中,对package进行了扫描,并调用refresh方法对bean进行初始化和实例化。

3、自定义注解

自定义注解,并添加到需要装载到Spring容器中的框架类上:

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface SnoWalkerAutoInject {
}

定义几个模拟的框架类,用以模拟框架的逻辑。实际的开发中,我们可以按照需求的实际需要,开发框架代码,并标记自定义的注解。

@SnoWalkerAutoInject
public class FrameWorkConfigA {

    public FrameWorkConfigA() {
        System.out.println("自定义框架组件A-初始化逻辑");
    }
}

@SnoWalkerAutoInject
public class FrameWorkConfigB {

    public FrameWorkConfigB() {
        System.out.println("自定义框架组件B-初始化逻辑");
    }
}

@SnoWalkerAutoInject
public class FrameWorkConfigC {

    public FrameWorkConfigC() {
        System.out.println("自定义框架组件C-初始化逻辑");
    }
}

4、配置ImportBeanDefinitionRegistrar实现类

如何使用自定义的ImportBeanDefinitionRegistrar实现类对bean进行装载呢?

最终我们还是需要定义一个配置类,通过@Import注解配置ImportBeanDefinitionRegistrar实现。

@Configuration
@Import(MyBeanDefinationRegistry.class)
@ComponentScan("com.spring.framework")
public class MyConf {
}
  1. MyConf是自定义的配置类,标注了 @Configuration 注解。
  2. 通过@Import将实现了ImportBeanDefinitionRegistrar接口的MyBeanDefinationRegistry包含进来;
  3. 添加扫描包,以方便spring对该包下的类进行扫描并进行选择性的装载;

4、测试

编写测试类:

public class App {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.spring");

        final TestBean testBean = (TestBean) applicationContext.getBean("testBean");
        System.out.println(testBean.getClass());
    }
}
  1. 首先我们声明并初始化一个AnnotationConfigApplicationContext容器;
  2. 接着从容器中通过BeanName获取通过GenericBeanDefinition定义的TestBean实例,打印其Class类型;
  3. 观察日志输出,期望能够看到框架代码FrameWorkConfigA、FrameWorkConfigB、FrameWorkConfigC的构造方法日志打印,并看到TestgBean的Class类型打印。

运行测试类,观察控制台日志输出:

自定义框架组件A-初始化逻辑
自定义框架组件B-初始化逻辑
自定义框架组件C-初始化逻辑

class com.spring.TestBean

可以看到符合预期,这表明,通过ImportBeanDefinitionRegistrar自定义手动bean注入符合预期。

总结

本文我们全篇对ImportBeanDefinitionRegistrar在Spring容器装载bean的过程进行了综述,并通过一个模拟框架开发的案例,对如何通过ImportBeanDefinitionRegistrar实现bean的自定义注入进行了代码级别的讲解和分析。

如果在实际的开发案例中需要实现自定义的bean注入,减少调用方整合的复杂度,那么我们完全可以通过本文讲解的方式,利用ImportBeanDefinitionRegistrar扩展点实现。

下期预告:

下期我们将分析讲解BeanPostProcessor扩展点在Spring框架中的作用,并讲解BeanPostProcessor在实战开发中的使用。



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

文章目录
  1. 1. 编码实现手动Bean注入
    1. 1.1. 1、定义ImportBeanDefinitionRegistrar实现类
      1. 1.1.1. 方式1:基于包路径的扫描
      2. 1.1.2. 方式2:直接注册BeanDefinition
      3. 1.1.3. 对比方式1方式2
    2. 1.2. 2、定义ClassPathBeanDefinitionScanner实现类
      1. 1.2.1. registerFilters分析
    3. 1.3. 3、自定义注解
    4. 1.4. 4、配置ImportBeanDefinitionRegistrar实现类
    5. 1.5. 4、测试
  2. 2. 总结
  3. 3. 下期预告:
Fork me on GitHub