前言
上一篇博客介绍了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的如何实现的
-
@MapperScan
继承了Import注解,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,将MapperScannerConfigurer后置处理器注册到BeanDefinitionMap中。执行时机详见3.2.1。
-
MapperScannerConfigurer
这是一个Spring的扩展,实现了BeanDefinitionRegistryPostProcessor接口,主要的作用就是构建一个扫描器执行扫描。
-
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当中。