欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring扩展之BeanDefinitionRegistryPostProcessor

程序员文章站 2022-05-07 21:53:27
...

前言

上一篇博客介绍了Spring中的invokeBeanFactoryPostProcessors方法。其中涉及到了两个接口,一是BeanFactoryPostProcessor接口,二是BeanDefinitionRegistryPostProcessor接口。详细的内容可以参考上一篇博客《Spring IOC—invokeBeanFactoryPostProcessors源码分析》

而这两个接口是spring提供的扩展点,本文将介绍关于BeanDefinitionRegistryPostProcessor扩展点的应用。

1.概述

对于BeanDefinitionRegistryPostProcessor扩展点应用的学习,可以参考mybatis是如何扩展这个接口的。这种站在巨人的肩膀上学习效率更高。

mybatis基于这个扩展点,将mapper接口交给了Spring管理。众所周知Spring管理的是对象,那么是如何将mapper接口变成对象呢,又是通过怎样的方式将这个对象交给spring管理的呢?如果有疑问的读者不妨带着问题继续阅读。

1.1重点类介绍

1.1.1 MapperProxyFactory

mapper代理工厂,基于接口和mapperProxy来构建一个mapper代理对象。实现了将接口转变成一个对象。

1.1.2 MapperFactoryBean

MapperFactoryBean实现了FactoryBean。他是一个特殊的Bean对象,可以调用getObject()方法根据接口的不同,返回不同的接口代理对象,这个对象也就是通过MapperProxyFactory产生的。

1.1.3 MapperScannerRegistrar

用来将MapperScannerConfigurer注册到BeanDefinitionMap中。具体的执行时机后续会介绍。

1.1.4 MapperScannerConfigurer

实现了BeanDefinitionRegistryPostProcessor接口,这是基于spring的扩展。在Spring的内置扫描器扫描结束后触发这个类的逻辑,执行postProcessBeanDefinitionRegistry方法,构建一个mybatis的扫描器来执行扫描mapper接口。

1.1.5 ClassPathMapperScanner

mybatis实现的一个扫描器,继承了spring内置的扫描器ClassPathBeanDefinitionScanner。实现了将mapper.class到BeanDefinition的转换。

2.mybatis中如何将接口变成对象

先来看一段代码

Environment environment = new Environment("development", null, null);
Configuration configuration = new Configuration(environment);
configuration.addMapper(TestMapper.class);

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
mapper.query();

这段代码大家肯定很熟悉了,而重点就在于sqlSession.getMapper(TestMapper.class);这一句上。debug调试,一直执行下去,可以看到是使用了JDK的动态代理。基于传入的接口,生成一个代理对象。代理对象执行的方法都会去执行mapperProxy中的invoke方法。具体做了什么这里不讨论了。

//MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

第一个疑问就这样解决了,mybatis使用基于接口的动态代理生成代理对象。

3.mybatis如何将代理对象交给spring管理

不妨先来看看有哪几种方式可以将一个对象交给spring来管理

3.1将对象交给Spring管理的方式

3.1.1 api手动添加

ac.getBeanFactory().registerSingleton("beanName",new Object());

3.1.2 @Bean

@Bean
public Object object(){ 
   return new Object();
}

3.1.3 factoryBean

factoryBean 他是一个特殊Bean,必须实现接口FactoryBean,当前这个Bean还能产生一个Bean。将这个MyFactoryBean类交给spring管理,也就可以将一个对象A交给了Spring来进行管理了。

mybatis就是使用的这种方式。

class MyFactoryBean implements FactoryBean{

   @Override
   public Object getObject() throws Exception {
      return new A();
   }

   @Override
   public Class<?> getObjectType() {
      return A.class;
   }
}

3.2 mybatis对于spring扩展实现

需要做哪些事情呢?

首先需要扫描指定包下面的接口,将这些接口生成代理对象,再通过FactoryBean将这些代理对象交给Spring管理。

3.2.1 @MapperScan注解

Spring扩展之BeanDefinitionRegistryPostProcessor

这个注解的主要作用是将MapperScannerConfigurer放到BeanDefinitionMap当中。

那么他是如何工作的呢?再上一篇博客中提到了spring中的内置类ConfigurationClassPostProcessor。这个类的作用是扫描带spring注解的类添加到bdmap中,并且解析配置类上的@Import标签,若这个标签中的类实现了ImportBeanDefinitionRegistrar接口,会将这这个类实例化后放入到 importBeanDefinitionRegistrars 缓存当中。扫描完成后会执行这个集合当中的对象的registerBeanDefinitions方法。

mybatis中MapperScannerRegistrar这个方法的作用就是将MapperScannerConfigurer注册到BeanDefinitionMap当中。

//MapperScannerRegistrar
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }
 void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {

  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  // 。。。。忽略不重要的代码

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

3.2.2 MapperScannerConfigurer

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,即对spring进行扩展

还记得上一篇博客中这些后置处理器的执行顺序嘛?

根据3.2.1MapperScannerConfigurer是在spring内置的后置处理器执行完毕后就被注册到了bdMap中。而之后会去执行实现了BeanDefinitionRegistryPostProcessor接口和Ordered接口的后置处理器。然后会执行实现了BeanDefinitionRegistryPostProcessor的后置处理器。

所以会去执行MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法,如下

主要的逻辑就是初始化一个mybatis中实现的扫描器ClassPathMapperScanner,然后用这个扫描器执行扫描工作,即执行scan方法。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);//mybatis中的扫描器
  //下面的一系列set方法是对这个扫描器进行初始化
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  scanner.registerFilters();//注册一些扫描用的过滤器
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));//扫描basePackage包
}

ClassPathMapperScanner并没有重写父类的scan方法,所以这里调用的是父类的scan方法,如下。

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

   // 完成扫描
   doScan(basePackages);

   // Register annotation config processors, if necessary.
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
   }

   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

重点在执行doScan方法,该方法由ClassPathMapperScanner重写了。

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//扫描包下的接口

  if (beanDefinitions.isEmpty()) {
    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
        + "' package. Please check your configuration.");
  } else {
    processBeanDefinitions(beanDefinitions);//将这些接口变成FactoryBean
  }

  return beanDefinitions;
}

首先会去调用父类的doScan方法来执行扫描,

遍历包名

<1>处执行findCandidateComponents方法,根据包名去扫描符合条件的类。其中会调用到scanCandidateComponents(basePackage)方法。这个方法会根据包名得到这个包下的所有的文件资源。然后会遍历这些资源将所有的接口添加到候选集合中返回。

<2>处遍历找到的候选bd

<3>处设置一些默认的属性值

<4>处看是否有注解,处理这些注解,例如@Lazy,@Primary、@DependsOn等

<5>检查beanName是否冲突,没有冲突的话构建成bdHolder,保存在一个set集合中并注册bd 。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {//遍历包名

      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//<1>寻找符合条件的候选BeanDefinition

      for (BeanDefinition candidate : candidates) {//<2>遍历找到的候选bd
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());//设置Scope
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);//<3>设置一些默认的属性值
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);//<4>看是否有注解,处理这些注解,例如@Lazy,@Primary、@DependsOn等
         }
         if (checkCandidate(beanName, candidate)) {//<5>检查beanName是否冲突,没有冲突的话构建成bdHolder,保存在一个set集合中并注册bd 。
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

方法结束后返回结果到ClassPathMapperScanner中的doScan继续执行

此时得到的bd并不是所要的,不能对其进行实例化,因为他所包含的类还是个接口,接口是不能被实例化的。需要将其转变成一个FactoryBean。

执行 processBeanDefinitions(beanDefinitions)

if (beanDefinitions.isEmpty()) {
    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
        + "' package. Please check your configuration.");
  } else {
    processBeanDefinitions(beanDefinitions);
  }

这个方法会设置构造函数的参数为这个接口,然后将beanClass修改为MapperFactoryBean。之后实例化的时候会调用这个FactoryBean的getObject()方法来构建一个代理Mapper对象。这就是如何将map接口转变成一个对象交给Spring管理的关键。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 添加构造函数需要的参数
    definition.setBeanClass(this.mapperFactoryBeanClass);//设置类型为mapperFactoryBeanClass。之后的实例化会调用MapperFactoryBean中的getObject()来构建出一个基于接口的代理对象交给spring管理。

   //省略非重要代码
  }
}

4.模仿mybatis实现一个自定义扫描器

通过对mybatis中对spring的扩展的学习,试着模仿一下实现使用自定义的注解,使得添加了该注解的类可以交给Spring来管理。

首先先来总结一下mybatis的如何实现的

  1. @MapperScan

    继承了Import注解,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,将MapperScannerConfigurer后置处理器注册到BeanDefinitionMap中。执行时机详见3.2.1。

  2. MapperScannerConfigurer

    这是一个Spring的扩展,实现了BeanDefinitionRegistryPostProcessor接口,主要的作用就是构建一个扫描器执行扫描。

  3. ClassPathMapperScanner

    mybatis实现的扫描器,继承了Spring中的ClassPathBeanDefinitionScanner。实现属于自己扫描的逻辑。

根据以上的总结可以模仿着来实现一套属于自己的扫描规则。

4.1 @MyBeanScan

模仿@MapperScan,继承了@Import标签

Spring扩展之BeanDefinitionRegistryPostProcessor

该注解用于添加在配置类上,Spring内置的扫描器在完成了扫描之后会解析配置类上的Import标签。将实现了ImportBeanDefinitionRegistrar的类实例化并添加到importBeanDefinitionRegistrars 这个map当中。之后会执行这个map中对象的registerBeanDefinitions方法。

Spring扩展之BeanDefinitionRegistryPostProcessor

4.2 MyScannerRegistrar

模仿MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar接口

Spring扩展之BeanDefinitionRegistryPostProcessor

主要逻辑就是将一个实现了BeanDefinitionRegistryPostProcessor接口的后置处理器注册到BeanDefinitionMap当中。

4.3 MyPostRegistryPostProcessor

模仿MapperScannerConfigurer,实现了BeanDefinitionRegistryPostProcessor接口。

Spring扩展之BeanDefinitionRegistryPostProcessor

Spring会执行这个类中的postProcessBeanDefinitionRegistry方法。这个方法主要逻辑是构建一个自定义的扫描器,配置一些过滤规则,然后执行扫描。这里扫描的是com.gongsenlin.custom包下的类。

4.4 MyScanner

自定义的扫描器,继承了Spring的扫描器。可以对里面的一些方法进行重写。

Spring扩展之BeanDefinitionRegistryPostProcessor

4.5 MyFilter

在扫描器执行扫描的时候,会根据过滤器来过滤掉一些不符合条件的类。

自定义的过滤器,说明一些过滤规则。这里实现的是,类上注解包含了@MyBean的返回true。就是说留下包含注解@MyBean的类,过滤掉不符合的。

Spring扩展之BeanDefinitionRegistryPostProcessor

4.6 实验结果

com.gongsenlin.custom包下现在有两个类,一个X和一个Y。Y上添加了MyBean注解。

Spring扩展之BeanDefinitionRegistryPostProcessor
Spring扩展之BeanDefinitionRegistryPostProcessor

编写测试类,启动Spring容器。

Spring扩展之BeanDefinitionRegistryPostProcessor

可以看到添加了MyBean注解的类被添加到了BeanDefinitionMap当中。

Spring扩展之BeanDefinitionRegistryPostProcessor