一、問題
在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方法。 最后的最后,我們看一下這個代理類長什么樣子。
