如果让自己着手开发一个框架整合到Spring当中,你会怎么做?
与其说我们怎么做,不如说Spring提供了哪些可扩展的地方允许你对接?
那我们就从最熟悉的MaBatis入手,看Spring如何整合MyBatis?
如果说自己写一个mybatis整合到Spring当中,怎么做?
1. 暴露一个注解,通过spring让mybatis去扫描所有Mapper。
2. 将所有Mapper交给Spring去管理,也就是添加到 容器。
3. 同时创建SqlSessionFactory 对象,包括mybatis一切所需对象。
4. 获取Mapper对象,执行mapper方法。
第一个问题:
mybatis是怎么通过spring去扫描Mapper的?
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
官网最新是2.0.4版本,我用2.0.2版本,2.0以后mybatis实现了一个Spring中一个重要的接口。
BeanDefinitionRegistryPostProcessor
Spring整合MyBatis主动权在于Spring,做事的规则还是交给Spring来定,ORM那一套怎么做Spring管不到,但是对象创建、对象管理,什么时候创建,怎么管理都是Spring说了算。
Spring整合MyBatis唯一的入口就在 @MapperScan 这个注解,他到底做了什么文章?
MapperScannerRegistrar 实现了Spring当中一个重要的扩展接口:
同时也实现了 ImportBeanDefinitionRegistrar 中registerBeanDefinitions 方法:
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
this.registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList();
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
registerBeanDefinitions方法
上面代码大家有兴趣可以看,主线最重要,registerBeanDefinitions 他的作用主要是将 MapperScannerConfigurer new成BeanDefinition , 并且注册到Spring上下文环境(AnnotationConfigApplicationContext)当中。
描述: 在这里我简单提一下,AnnotationConfigApplicationContext 继承了GenericApplicationContext , 在GenericApplicationContext 这个类当中维护了一个beanFactory:
在DefaultListableBeanFactory 当中维护了一个Map,这个Map就是存放所有BeanDefinition的容器:
这里留一个小问题: MapperScannerRegistrar 这个对象能调用registerBeanDefinitions 这个方法,它必然已经实例化完成,那么他是在什么时机,又是在什么地点实例化完成的?
第二个问题:
既然MyBatis将MapperScannerConfigurer添加到Spring 容器 当中,那么他的目的是什么,或者说他的作用是什么?
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {}
它实现了Spring中又一重要扩展接口:BeanDefinitionRegistryPostProcessor,同时实现了postProcessBeanDefinitionRegistry这个方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
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(this.lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
}
scanner.registerFilters();
//扫描包路径下的mapper
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
这个方法也就是完成了我们的第一步假设,创建了一个扫描器,扫描所有的Mapper,将Mapper new成BeanDefinition。
具体细节在这里不再解释了,大家可以自行断点。
第三个问题(重要):
Mapper是接口啊,他怎么会被实例化呢?
描述: 在扫描过程,也就是MyBatis ClassPathMapperScanner scan过程中,会设置所有Mapper BeanDefinition的属性,其中就包括设置类型。
所以我们要在MapperFactoryBean 这个类下功夫。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}
哇塞,MapperFactoryBean 实现了FactoryBean,那么这就说的通了,Spring不能去实例化Mapper,MyBatis就把所有的Mapper的BeanDefinition属性beanClass设置为MapperFactoryBean.class,让Spring去实例化这个FactoryBean 。让实例化Mapper的过程交给FactoryBean去做。
public static void main(String[] args) {
AnnotationConfigApplicationContext app
= new AnnotationConfigApplicationContext(AppConfig.class);
ProductMapper productMapper = app.getBean(ProductMapper.class);
}
描述: 整个过程是根据类型去getBean,发现一级缓存也就是SingletonObjects单例池中有一个实例MapperFactoryBean,而这个MapperFactoryBean恰好是一个FactoryBean,一个FactoryBean会走getObject 这个方法,具体实现是由MyBatis通过JDK动态代理完成。
本人自己简单实现了一下MyBatis连接Spring完成Mapper逻辑实现:
public class DoMybatisProcessor implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//将MapperProxyFactory new 成BeanDefinition
BeanDefinitionBuilder beanDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(MapperProxyFactory.class);
//设置MapperProxyFactory的mapperInterface属性值
beanDefinitionBuilder.getBeanDefinition().getPropertyValues().add("mapperInterface", UserMapper.class);
//将BeanDefinition注册到容器当中
beanDefinitionRegistry.registerBeanDefinition(((Class)beanDefinitionBuilder.getBeanDefinition().getPropertyValues().get("mapperInterface")).getSimpleName(),beanDefinitionBuilder.getBeanDefinition());
}
}
public class MapperProxyFactory implements FactoryBean {
Class mapperInterface;
@Override
public Object getObject() throws Exception {
return MapperCustomProxy.getMapper(mapperInterface);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public Class getMapperInterface() {
return mapperInterface;
}
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
public class MapperCustomProxy {
//基于动态代理的Mapper
public static Object getMapper(Class mapperInterface) {
return Proxy.newProxyInstance(MapperCustomProxy.class.getClassLoader(),
new Class[]{mapperInterface},new MyInvocationHandler());
}
}
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String sql=null;
Select annotation = method.getAnnotation(Select.class);
if (annotation!=null) {
sql = annotation.value()[0];
System.out.println("创建链接");
System.out.println("获取statement对象");
System.out.println("执行sql: "+sql);
System.out.println("处理并返回执行结果");
}
return null;
}
}
大家可以照我的自行去测试,笔者不在这里做详细解释了。
补充
针对MyBatis中实现的其中两个重要接口:
ImportBeanDefinitionRegistrar
BeanDefinitionRegistryPostProcessor
执行时机做一个简单描述。
invokeBeanFactoryPostProcessors方法主要处理BeanDefinitionRegistryPostProcessor以及BeanFactoryPostProcessor这两个后置处理器。这么说有点大,因为这两个后置处理器作用范围太广。
Spring最先处理的是ConfigurationClassPostProcessor这个类,它属于Spring开天辟地的五大类之一,而这个类就实现了BeanDefinitionRegistryPostProcessor这个接口。
ConfigurationClassPostProcessor最先执行BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry 方法。
问题:MapperScannerRegistrar实现的ImportBeanDefinitionRegistrar中registerBeanDefinitions方法又是何时执行的呢?
我们回到ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法:
点进去
当 MapperScannerRegistrar 中registerBeanDefinitions方法执行完成之后:
看图:
走到断点
让我们点进去,见证奇迹的时刻:
此时此刻Spring整合MyBatis主要步骤及源码解析已经完成,大家可以跟着我的断点自己走一遍,相信肯定有意想不到的收获。
总结:只有自己尝试才会感叹Spring是如此的强大,并不是说Spring用多少代码量来证明,而是在他独有的IOC功能基础上还能让其它框架来无缝扩展。