前言
劳歌一曲解行舟,红叶青山水急流。日暮酒醒人已远,满天风雨下西楼。— 许浑 《谢亭送别》
一、SSA
关于mybatis实现jdk动态代理,在接入spring的时候代码是如何处理的(下篇)
二、GTTP
1.看下基本类,模拟的mybatis扩展spring接口实现查询
- 这里创建的CustomSqlsession customSqlsession = new CustomSqlsession();其实就是上一篇的创建动态代理的获取传入接口的代理对象的类
- mapperInterface就是传入的接口,这里模拟的传入的就是IndexDao;
- 这里提供了setMapperInterface是个set方法,意味着后面mybatis根据spring提供的接口规则将自己的代理对象注入spring的时候,spring通过这个set方法给mapperInterface设置值。
- CustomFactoryBean这个类里也可以提供有参构造方法,spring根据有参构造填充mapperInterface这个属性。
package com.cusmybatis.example3.factorybean;
import com.cusmybatis.example3.sqlSession.CustomSqlsession;
import org.springframework.beans.factory.FactoryBean;
/**
* @author xiansheng lv
* @date 2021/6/15 11:20
* 目的让mybati代理的这个对象注入spring.
* 即此处 生成代理对象必须让这个CustomFactoryBean 的bean生效
* 1, xml可以配置注册这个CustomFactoryBean类并且给他的属性指定需要代理的对象,
* 但是这个做法很局限,代理的对象多个无法手动一一写出来
* 2,注解的形式将需要被代理的对象注入CustomFactoryBean里
* 这会导致CustomFactoryBean一并走spring实例化对象的周期,也就是出现代理两次,直接出错
* 3,尝试添加这个代理对象的BeanDefintion 可行.
*
*/
public class CustomFactoryBean implements FactoryBean {
/*这个类是动态给的*/
Class mapperInterface;
@Override
public Object getObject() {
CustomSqlsession customSqlsession = new CustomSqlsession();
/*代理类获取对象*/
/*mabatis动态代理出来的类*/
Object mapper = customSqlsession.getMapper(mapperInterface);
/*此处实现FactoryBean返回的mapper对象会存到spring容器当中*/
return mapper;
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
/*提供一个set方法,属性注入的时候使用这个方法给
* 相较于构造方法而写的这个
* */
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
- 在spring实例化周期的过程中,这里实现了ImportBeanDefinitionRegistrar接口,来注入mybatis代理的对象
- 很明显你要注入mybatis代理对象必须按照spring提供的规矩来。也就是上面的(implements FactoryBean)实现FactoryBean接口,用BeanDefinitionBuilder构建mybatis的beanDefinition模型,将构建好的模型注册(registry.registerBeanDefinition)进去。
- 有一个大家很熟悉的后置处理器(implements BeanFactoryPostProcessor ),顾名思义,实现这个后置处理器可以根据实现类提供的beanFactory拿到你想要的beanDefinition,但这个是后置处理器,是已经走完注册BeanDefinition流程的接口,所以扩展这个接口只能获取不能在这里注册BeanDefinition;
- beanDefinition也提供了相应的属性设置的方法可以是.getPropertyValues().add(“mapperInterface”, IndexDao.class)来设置
package com.cusmybatis.example3.bd;
import com.cusmybatis.example3.dao.IndexDao;
import com.cusmybatis.example3.factorybean.CustomFactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xiansheng lv
* @date 2021/6/15 14:00
* spring里这个注册beanDefinition的类生效不能是Component注解
* 而是在配置类加上Import注解指定
*/
public class CustomImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
/*
*真实的情况是根据这里的annotations注解扫描获取到所有的dao
* 循环遍历 给它做代理注册到spring里
* 这里只获取了一个IndexDao注册进去
* */
/*构建传入class的beanDefinition信息*/
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
/*这里通过属性注入的方式 也可以是
* beanDefinition.getConstructorArgumentValues().addArgumentValues();
* 对应的CustomFactoryBean里也需要提供对应的有参构造
* */
beanDefinition.getPropertyValues().add("mapperInterface", IndexDao.class);
/*注册beanDefinition*/
registry.registerBeanDefinition("indexDao", beanDefinition);
}
}
下面是模拟@MapperScan注解的自定义注解@CustomScan用到的所有的类。
可以看到成功从容器中获取到IndexDao,并用它的代理对象执行了数据库查询。
2.查看mybatis扩展spring源码
可以看到,用Mybatis扩展spring的注解MapperScan来查询数据库依然可以,且代理对象变成了org.apache.ibatis.binding.MapperProxy@7e5c856f。接下来就看下源代码中的处理过程是否和上面模拟的一样。
- 可以看到@MapperScan(“com.cusmybatis.example3.dao”)中 @MapperScan注解上同样是@Import注解 导入了一个MapperScannerRegistrar.class类,这个类和笔者的@CustomScan注解导入的CustomImportBeanDefinitionRegistrar类一样实现了ImportBeanDefinitionRegistrar接口重写了registerBeanDefinitions方法。
- 可以看到这个MapperScannerRegistrar类的BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);这行逻辑代码第一行就构建了BeanDefinitionBuilder ,当然这里的MapperScannerConfigurer顾名思义扫描了所有的指定包下的dao;除了第一行下面的逻辑都是 做 builder.addPropertyValue(),这个和笔者的
这里的作用类似,是给BeanDefinition配置属性,这个属性是属于BeanDefinition的。最后一行 registry.registerBeanDefinition(beanName, builder.getBeanDefinition())将构建好的就是BeanDefinition注册到spring。接下来就重点看下MapperScannerConfigurer扫描干的事。
- 可以看到在MapperScannerConfigurer类的扫描过程中,在指定的com.cusmybatis.example3.dao包下扫描出了IndexDao。理论上扫描出来后应该是给它的BeanDefinition填充属性,这个属性是代理类需要的属性。
- 上图可以看到,扫描出来的beanDefinitions只有一个也就是MyBatis mapper 有一个IndexDao。但是在for (BeanDefinitionHolder holder : beanDefinitions) { 会循环遍历它,遍历的操作里有
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);
两行代码,查看属性知道beanClassName的值就是com.cusmybatis.example3.dao.IndexDao,也就是definition将来要实例化出来的对象的Constructor的参数是com.cusmybatis.example3.dao.IndexDao,实例化出来的对象是mapperFactoryBeanClass,这个是mybaits的提供的类实现了implements FactoryBean接口
作用就和上图的意思一样。当然理解上面的过程前提是对spring的beanDefinition有一定的理解。
总结
总结就是:
- mybaits自个儿单独使用是构建一个代理对象,需要查询数据库时将dao接口传入得到一个代理对象,这个代理对象的数据源事务等信息已经配置好了,可以直接调用dao接口里的方法查询得到数据,很方便。
- 但是要接入spring,spring提供了扩展implements ImportBeanDefinitionRegistrar这个接口即可注册你自己的beanDefinition,重写方法registerBeanDefinitions,构建BeanDefinitionBuilder时传入的对象必须是实现了org.springframework.beans.factory.FactoryBean接口的,对mybatis而言传入的就是MapperFactoryBean。
- 而这个对象有个有参构造,这个参就是用户传入发dao接口,MapperFactoryBean#getObject()方法返回对应的代理对象。
- 构建出BeanDefinitionBuilder对象前必须扫描包,遍历有多少个MyBatis mapper (扫描出来的dao是通过ScannedGenericBeanDefinition#ScannedGenericBeanDefinition变成的ScannedGenericBeanDefinition–GenericBeanDefinition的子类具体是spring源码这里有兴趣可以看下具体的实现)。
- 然后遍历每个beanDefinition,填充下未来根据beanDefinition代理出的bean对象的属性(BeanClassName,isAbstract,是否懒加载,是否单例,构造方法,构造方法的参数等),最后注册进去registry.registerBeanDefinition(beanName,beanDefinition)即可。