myBatis源码学了一段时间, 突然对接口的Mapper类怎么变成FactoryBean注入到项目中很好奇, 找时间研究了下, 做个总结.
在以前的分析代码中, 手动调用Mapper访问数据的过程如下;
IUserMapper mapper = session.GetMapper(IUserMapper.class);
UserBean bean = mapper.selectByPrimaryKey(1);
System.out.println(bean.getName());
即由SqlSession对象(Spring中为SqlSessionTemplate),直接调用getMapper方法获取对应的Mapper.然后访问数据库, 但是真正在项目中,Mapper的使用方式如下:
@Service
public class xxxService {
@Resource
private IUserMapper mapper ;
public Map<String, Object> getXXXmethod(String xx) {
UserBean bean = mapper.selectByPrimaryKey(1);
}
}
即Mapper是直接被注入到项目中的,
问题: 这个注入是什么时候实现的?
调试程序发现此时的Mapper实际类型是MapperProxy类型:
原来所有Mapper接口都被转变为MapperProxyBean放入Ioc, 回头查看下Mybatis文档.
http://www.mybatis.org/spring/zh/mappers.html#MapperFactoryBean, 有如下说明:
MyBatis-Spring 提供了一个动态代理的实现:MapperFactoryBean。这个类 可以让你直接注入数据映射器接口到你的 service 层 bean 中。当使用映射器时,你仅仅如调 用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码,因为 MyBatis-Spring 将会为你创建代理。
单个数据映射器接口可以按照如下做法加入到 Spring 中:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
MapperFactoryBean 创建的代理类实现了 UserMapper 接口,并且注入到应用程序中
问题来了,如果每个Mapper接口都如上配置的话, XML文件还不暴涨, 所以Spring 又提供了类MapperScannerConfigurer,
,你可以使用一个 MapperScannerConfigurer , 它 将 会 查 找 类 路 径 下 的 映 射 器 并 自 动 将 它 们 创 建 成 MapperFactoryBean。
要创建 MapperScannerConfigurer,可以在 Spring 的配置中添加如下代码:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>
下面看类MapperScannerConfigurer是怎么实现将接口Mapper变为MapperFactoryBean然后注入到Ioc中的.
首先看MapperProxy源码:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果是Object类的方法如:toSting等, 直接调用原方法,
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//被代理为SqlSession.executor来执行数据库查询
return mapperMethod.execute(sqlSession, args);
}
}
这不就是个典型的代理类嘛, 具体不细说,请参照本人mybaits源码分析相关博客:
https://blog.csdn.net/yangsnow_rain_wind/article/category/7552504
下面分析类MapperScannerConfigurer
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor,
InitializingBean, ApplicationContextAware, BeanNameAware {
//1.
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
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.registerFilters();
//此处调用ClassPathMapperScanner 扫描类
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
}
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,
该接口有个postProcessBeanDefinitionRegistry方法, 可以在standard initialization之后 修改definition, 这些Bean definitions 已经被加载到仓库,但是还没来得及初始化,
Mapper的加载由doScan方法来实现
//org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//1
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
//这里记录原始接口的名称
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
//2.
//这里类的名称被改写为MapperFactoryBean,
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
}
return beanDefinitions;
}
1处. 调用的是父类的方法 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
该方法中,用来扫描指定package下所有的类,并包装为BeanDefinitionHolder,
即返回指定包路径下的所有Mapper接口, 并封装为BeanDefinitionHolder
//mapper扫描路径
<property name="basePackage" value="com.xx.xxx.model.dao"/>
2处 :关键点,BeanDefinition的类名字由原来的Mapper被更改为MapperFactoryBean
,
举个栗子: 假如本Bean的名称依然是”userMapper”, 但其定义的类型已经被指定为MapperFactoryBean
, 这在该bean被IOC初始化的时候,则会按照新的类型被初始化为一个代理类MapperFactoryBean
,
MapperFactoryBean
继承了SqlSessionDaoSupport
类,SqlSessionDaoSupport
类继承DaoSupport
抽象类,DaoSupport抽象类实现了InitializingBean
接口,因此实例个MapperFactoryBean的时候,都会调用InitializingBean
接口的afterPropertiesSet方法
源码如下:
//org.springframework.dao.support.DaoSupport#afterPropertiesSet
public final void afterPropertiesSet() {
checkDaoConfig();
initDao();
}
//org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig
protected void checkDaoConfig() {
super.checkDaoConfig();
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
//至此, 世界一下子清净了, Mapper接口定义的Bean被注册进Ioc中
configuration.addMapper(this.mapperInterface);
}
}
Mapper被代理为MapperFactoryBean,注册进ioc中, 至此基本回到开始的原点:
IUserMapper mapper = session.GetMapper(IUserMapper.class);
mapper是一个代理好的对象, mapper的所有调用都被代理给sqlsessionTemplate
来实现,
完……………………..