Spring扩展之BeanDefinitionRegistryPostProcessor

  • Post author:
  • Post category:其他




前言

上一篇博客介绍了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注解

在这里插入图片描述

这个注解的主要作用是将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 添加构造函数需要的参数
	// 上面这一行代码可以使用下面的代码替代
    // MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
		// 第一个参数 是set方法去掉set 后首字母小写的名字。 setMapperInterface 去掉set M改成小写 得 mapperInterface
    // propertyValues.add("mapperInterface",aClass);
    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内置的扫描器在完成了扫描之后会解析配置类上的Import标签。将实现了ImportBeanDefinitionRegistrar的类实例化并添加到importBeanDefinitionRegistrars 这个map当中。之后会执行这个map中对象的registerBeanDefinitions方法。

在这里插入图片描述



4.2 MyScannerRegistrar

模仿MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar接口

在这里插入图片描述

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



4.3 MyPostRegistryPostProcessor

模仿MapperScannerConfigurer,实现了BeanDefinitionRegistryPostProcessor接口。

在这里插入图片描述

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



4.4 MyScanner

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

在这里插入图片描述



4.5 MyFilter

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

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

在这里插入图片描述



4.6 实验结果

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

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述



版权声明:本文为gongsenlin341原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。