一、问题
在Mybatis架构的最上层就是接口层,它定义的是与数据库交互的方式。有以下两种方式:
- Mybatis提供的API
使用Mybatis提供的API进行操作,通过获取SqlSession对象,然后根据Statement Id 和参数来操作数据库。
String statement = "com.viewscenes.netsupervisor.dao.UserMapper.getUserList";
List<User> result = sqlsession.selectList(statement);
- mapper接口
定义Mapper接口,里面定义一系列业务数据操作方法。在Service层通过注入mapper属性,调用其方法就可以执行数据库操作。就像下面这样:
public interface UserMapper { List<User> getUserList(); } @Service public class UserServiceImpl implements UserService{ @Autowired UserMapper userDao; @Override public List<User> getUserList() { return userDao.getUserList(); } }
UserMapper 只是个接口,并没有任何实现类,在调用它的时候,最终是怎样执行到SQL语句的呢?
二、扫描
1、配置信息
说到这,就要看配置文件中的另外一个Bean。通过指定基本包的路径,Mybatis可以通过Spring扫描下面的类,将其注册为BeanDefinition对象。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.viewscenes.netsupervisor.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>
或者有的朋友项目里还有个annotationClass的属性,即
<property name="annotationClass" value="org.springframework.stereotype.Repository" />
它的作用就是在扫描的包的时候,会过滤定义的annotationClass。如果有这个注解才会被扫描,通常会在类上以@Repository来标识。不过它的作用也仅是为了过滤而已,我们也完全可以自定义这个注解。比如:
@MyDao public interface UserMapper {} <property name="annotationClass" value="com.viewscenes.netsupervisor.util.MyDao" />
当然了,如果确定基本包路径下的所有类都要被注册,那就不必配置annotationClass。
2、扫描基本包
来到org.mybatis.spring.mapper.MapperScannerConfigurer这个类,可以看到它实现了几个接口。其中的重点是BeanDefinitionRegistryPostProcessor。它可以 动态的注册Bean信息,方法为postProcessBeanDefinitionRegistry()。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { //创建ClassPath扫描器,设置属性,然后调用扫描方法 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAnnotationClass(this.annotationClass); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); //如果配置了annotationClass,就将其添加到includeFilters scanner.registerFilters(); scanner.scan(this.basePackage); }
ClassPathMapperScanner继承自Spring中的类ClassPathBeanDefinitionScanner,所以scan方法会调用到父类的scan方法,而在父类的scan方法中又调用到子类的doScan方法。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { public Set<BeanDefinitionHolder> doScan(String... basePackages) { //调用Spring的scan方法。就是将基本包下的类注册为BeanDefinition Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); processBeanDefinitions(beanDefinitions); return beanDefinitions; } }
super.doScan(basePackages)是Spring中的方法,主要看它返回的是BeanDefinition的集合。
3、配置BeanDefinition
上面已经扫描到了所有的Mapper接口,并将其注册为BeanDefinition对象。接下来调用processBeanDefinitions()要配置这些BeanDefinition对象。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>(); private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); //将mapper接口的名称添加到构造参数 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); //设置BeanDefinition的class definition.setBeanClass(this.mapperFactoryBean.getClass()); //添加属性addToConfig definition.getPropertyValues().add("addToConfig", this.addToConfig); //添加属性sqlSessionFactory definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); ...... } }
处理的过程很简单,就是往BeanDefinition对象中设置了一些属性。重点关注两个。
- 设置beanClass
设置BeanDefinition对象的BeanClass为MapperFactoryBean。这意味着什么呢?以UserMapper为例,意味着当前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那么在IOC初始化的时候,实例化的对象就是MapperFactoryBean对象。
- 设置sqlSessionFactory属性
为BeanDefinition对象添加属性sqlSessionFactory,这就意味着,在为BeanDefinition对象设置PropertyValue的时候,会调用到setSqlSessionFactory()。
三、创建SqlSession的代理
上面我们说在为BeanDefinition对象设置PropertyValue的时候,会调用它的setSqlSessionFactory,我们来看这个方法。首先,这里说的BeanDefinition对象就是beanClass为MapperFactoryBean.class的MapperFactoryBean对象。定位到这个类,我们发现它继承自org.mybatis.spring.support.SqlSessionDaoSupport。
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } }
在它的setSqlSessionFactory方法里,最终调用的是new SqlSessionTemplate()。所以sqlSession的对象其实是一个SqlSessionTemplate的实例。我们来看它的构造函数。
public class SqlSessionTemplate implements SqlSession, DisposableBean { public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { //设置sqlSessionFactory this.sqlSessionFactory = sqlSessionFactory; //设置执行器的类型 this.executorType = executorType; //异常相关处理类 this.exceptionTranslator = exceptionTranslator; //sqlSession的代理 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } }
对JDK动态代理熟悉的朋友,一定会先看到newProxyInstance。它是给sqlSession接口创建了一个代理类,这个代理类的处理器程序就是SqlSessionInterceptor()。不用多说,SqlSessionInterceptor肯定实现了InvocationHandler接口。 这就意味着,当调用到sqlSession的时候,实际执行的它的代理类,代理类又会调用到处理器程序的invoke()方法。
private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args){ //内容先略过不看 } }
最终在setSqlSessionFactory这个方法里,sqlSession获取到的是SqlSessionTemplate实例。而在SqlSessionTemplate对象中,主要包含sqlSessionFactory和sqlSessionProxy,而sqlSessionProxy实际上是SqlSession接口的代理对象。

四、创建Mapper接口的代理
上面我们说到MapperFactoryBean继承自SqlSessionDaoSupport,还有一点没说的是,它同时实现了FactoryBean接口。这就说明,MapperFactoryBean不是一个纯粹的人。啊不对,不是一个纯粹的Bean,而是一个工厂Bean。如果要声明一个Bean为工厂Bean,它要实现FactoryBean接口,这个接口就三个方法。
public interface FactoryBean<T> { //返回对象的实例 T getObject() throws Exception; //返回对象实例的类型 Class<?> getObjectType(); //是否为单例 boolean isSingleton(); }
MapperFactoryBean既然是一个工厂Bean,那么它返回就不是这个对象的本身,而是这个对象getObjectType方法返回的实例。为什么会这样呢? 在Spring中执行getBean的时候,在创建完Bean对象且完成依赖注入之后,用调用到 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);。 这个方法会判断当前Bean是否为FactoryBean,如果不是就不再执行,如果是最终就会调用到它的getObject()方法。
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; } //getObjectFromFactoryBean最终调用的是getObject Object object = getObjectFromFactoryBean(factory, beanName, !synthetic); return object; }
那么,getObject究竟会返回什么对象呢?
1、getObject()
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { public T getObject() throws Exception { //mapperInterface是mapper接口的对象 return getSqlSession().getMapper(this.mapperInterface); } }
getSqlSession()我们已经分析完了,它返回的就是SqlSessionTemplate对象的实例。所以,我们主要看getMapper()。
2、getMapper
public class MapperRegistry { public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = knownMappers.get(type); return mapperProxyFactory.newInstance(sqlSession); } }
我们看到,它实现比较简单。不过,有个问题是knownMappers是从哪儿来的呢?它为什么可以根据type接口就能获取到MapperProxyFactory实例呢?是否还记得,在扫描注解式SQL声明的时候,它调用到addMapper方法,其实就是这个类。
public class MapperRegistry { public <T> void addMapper(Class<T> type) { if (type.isInterface()) { try { /注入type接口的映射 knownMappers.put(type, new MapperProxyFactory<T>(type)); //扫描注解 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); } } } }
这也就解释了为什么knownMappers.get(type)就能获取到MapperProxyFactory的实例,下面来看它内部到底创建了什么对象并返回的。
3、newInstance
在创建过程中,实际返回的是一个代理类,即mapper接口的代理类。
public class MapperProxyFactory<T> { public T newInstance(SqlSession sqlSession) { //mapperProxy就是一个调用程序处理器,显然它要实现InvocationHandler接口 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); //JDK动态代理,生成的就是mapperInterface接口的代理类 //mapperInterface就是我们的mapper接口 //比如com.viewscenes.netsupervisor.dao.UserMapper return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } }
public class MapperProxy<T> implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //具体流程先略过.... return method.invoke(this, args); } }
看到这里,我们都已经明白了。getObject方法返回的就是mapper接口的代理类。换言之,每一个mapper接口对应的都是自身的接口代理。那么,在实际调用到mapper方法的时候,就会调用到调用程序处理器的MapperProxy.invoke(Object proxy, Method method, Object[] args)。
五、总结
本章节重要阐述了Mapper接口的代理创建过程。简单梳理下流程:
- 扫描mapper接口基本包,将为注册为BeanDefinition对象。
- 设置BeanDefinition的对象的beanClass和sqlSessionFactory属性。
- 设置sqlSessionFactory属性的时候,调用SqlSessionTemplate的构造方法,创建SqlSession接口的代理类。
- 获取BeanDefinition对象的时候,调用其工厂方法getObject,返回mapper接口的代理类。
最后我们在Service层,通过@Autowired UserMapper userDao注入属性的时候,返回的就是代理类。执行userDao的方法的时候,实际调用的是代理类的invoke方法。 最后的最后,我们看一下这个代理类长什么样子。
