在Mybatis架構的最上層就是接口層,它定義的是與數據庫交互的方式。還記不記得咱們在前面章節說的那兩種方式?不記得不要緊,咱們回憶一下。spring
使用Mybatis提供的API進行操做,經過獲取SqlSession對象,而後根據Statement Id 和參數來操做數據庫。sql
String statement = "com.viewscenes.netsupervisor.dao.UserMapper.getUserList";
List<User> result = sqlsession.selectList(statement);
複製代碼
定義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語句的呢?bash
說到這,咱們就要看配置文件中的另一個Bean。經過指定基本包的路徑,Mybatis能夠經過Spring掃描下面的類,將其註冊爲BeanDefinition對象。session
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.viewscenes.netsupervisor.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
複製代碼
或者有的朋友項目裏還有個annotationClass的屬性,即mybatis
<property name="annotationClass" value="org.springframework.stereotype.Repository" />
複製代碼
它的做用就是在掃描的包的時候,會過濾定義的annotationClass。若是有這個註解纔會被掃描,一般會在類上以@Repository來標識。不過它的做用也僅是爲了過濾而已,咱們也徹底能夠自定義這個註解。好比:架構
@MyDao
public interface UserMapper {}
<property name="annotationClass" value="com.viewscenes.netsupervisor.util.MyDao" />
複製代碼
固然了,若是你肯定基本包路徑下的全部類都要被註冊,那就沒必要配置annotationClass。app
咱們來到org.mybatis.spring.mapper.MapperScannerConfigurer
這個類,能夠看到它實現了幾個接口。其中的重點是BeanDefinitionRegistryPostProcessor
。它能夠 動態的註冊Bean信息,方法爲postProcessBeanDefinitionRegistry()
。ide
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中的方法。咱們在Spring系列文章中已經詳細分析了,在這裏就不細究。主要看它返回的是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對象中設置了一些屬性。咱們重點關注兩個。
設置BeanDefinition對象的BeanClass爲MapperFactoryBean<?>
。這意味着什麼呢?以UserMapper爲例,意味着當前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那麼在IOC初始化的時候,實例化的對象就是MapperFactoryBean對象。
爲BeanDefinition對象添加屬性sqlSessionFactory,這就意味着,在爲BeanDefinition對象設置PropertyValue的時候,會調用到setSqlSessionFactory()
。
上面咱們說在爲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接口的代理對象。
上面咱們說到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;
}
複製代碼
說了這麼多,就是怕有的朋友對工廠Bean不瞭解,看這塊內容的時候會比較迷惑。那麼,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()。
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的實例,下面來看它內部到底建立了什麼對象並返回的。
在建立過程當中,實際返回的是一個代理類,即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方法。 最後的最後,咱們看一下這個代理類長什麼樣子。