一、問題
在Mybatis架構(gòu)的最上層就是接口層,它定義的是與數(shù)據(jù)庫(kù)交互的方式。還記不記得我們?cè)谇懊嬲鹿?jié)說的那兩種方式?不記得沒關(guān)系,我們回憶一下。
- Mybatis提供的API
使用Mybatis提供的API進(jìn)行操作,通過獲取SqlSession對(duì)象,然后根據(jù)Statement Id 和參數(shù)來操作數(shù)據(jù)庫(kù)。
String statement = "com.viewscenes.netsupervisor.dao.UserMapper.getUserList";
List<User> result = sqlsession.selectList(statement);
- mapper接口
定義Mapper接口,里面定義一系列業(yè)務(wù)數(shù)據(jù)操作方法。在Service層通過注入mapper屬性,調(diào)用其方法就可以執(zhí)行數(shù)據(jù)庫(kù)操作。就像下面這樣
public interface UserMapper {
List<User> getUserList();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userDao;
@Override
public List<User> getUserList() {
return userDao.getUserList();
}
}
那么,問題就來了。UserMapper 只是個(gè)接口,并沒有任何實(shí)現(xiàn)類。那么,我們?cè)谡{(diào)用它的時(shí)候,它是怎樣最終執(zhí)行到我們的SQL語句的呢?
二、掃描
1、配置信息
說到這,我們就要看配置文件中的另外一個(gè)Bean。通過指定基本包的路徑,Mybatis可以通過Spring掃描下面的類,將其注冊(cè)為BeanDefinition對(duì)象。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.viewscenes.netsupervisor.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
或者有的朋友項(xiàng)目里還有個(gè)annotationClass的屬性,即
<property name="annotationClass" value="org.springframework.stereotype.Repository" />
它的作用就是在掃描的包的時(shí)候,會(huì)過濾定義的annotationClass。如果有這個(gè)注解才會(huì)被掃描,通常會(huì)在類上以@Repository來標(biāo)識(shí)。不過它的作用也僅是為了過濾而已,我們也完全可以自定義這個(gè)注解。比如:
@MyDao
public interface UserMapper {}
<property name="annotationClass" value="com.viewscenes.netsupervisor.util.MyDao" />
當(dāng)然了,如果你確定基本包路徑下的所有類都要被注冊(cè),那就不必配置annotationClass。
2、掃描基本包
我們來到org.mybatis.spring.mapper.MapperScannerConfigurer
這個(gè)類,可以看到它實(shí)現(xiàn)了幾個(gè)接口。其中的重點(diǎn)是BeanDefinitionRegistryPostProcessor
。它可以 動(dòng)態(tài)的注冊(cè)Bean信息,方法為postProcessBeanDefinitionRegistry()
。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//創(chuàng)建ClassPath掃描器,設(shè)置屬性,然后調(diào)用掃描方法
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方法會(huì)調(diào)用到父類的scan方法,而在父類的scan方法中又調(diào)用到子類的doScan方法。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//調(diào)用Spring的scan方法。就是將基本包下的類注冊(cè)為BeanDefinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
}
super.doScan(basePackages)是Spring中的方法。我們?cè)赟pring系列文章中已經(jīng)詳細(xì)分析了,在這里就不細(xì)究。主要看它返回的是BeanDefinition的集合。
3、配置BeanDefinition
上面已經(jīng)掃描到了所有的Mapper接口,并將其注冊(cè)為BeanDefinition對(duì)象。接下來調(diào)用processBeanDefinitions()
要配置這些BeanDefinition對(duì)象。
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接口的名稱添加到構(gòu)造參數(shù)
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//設(shè)置BeanDefinition的class
definition.setBeanClass(this.mapperFactoryBean.getClass());
//添加屬性addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
//添加屬性sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
......
}
}
處理的過程很簡(jiǎn)單,就是往BeanDefinition對(duì)象中設(shè)置了一些屬性。我們重點(diǎn)關(guān)注兩個(gè)。
- 設(shè)置beanClass
設(shè)置BeanDefinition對(duì)象的BeanClass為MapperFactoryBean<?>
。這意味著什么呢?以UserMapper為例,意味著當(dāng)前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那么在IOC初始化的時(shí)候,實(shí)例化的對(duì)象就是MapperFactoryBean對(duì)象。
- 設(shè)置sqlSessionFactory屬性
為BeanDefinition對(duì)象添加屬性sqlSessionFactory,這就意味著,在為BeanDefinition對(duì)象設(shè)置PropertyValue的時(shí)候,會(huì)調(diào)用到setSqlSessionFactory()
。
三、創(chuàng)建SqlSession的代理
上面我們說在為BeanDefinition對(duì)象設(shè)置PropertyValue的時(shí)候,會(huì)調(diào)用它的setSqlSessionFactory,我們來看這個(gè)方法。
首先,這里說的BeanDefinition對(duì)象就是beanClass為MapperFactoryBean.class的MapperFactoryBean對(duì)象。定位到這個(gè)類,我們發(fā)現(xiàn)它繼承自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方法里,最終調(diào)用的是new SqlSessionTemplate()。所以sqlSession的對(duì)象其實(shí)是一個(gè)SqlSessionTemplate的實(shí)例。我們來看它的構(gòu)造函數(shù)。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
//設(shè)置sqlSessionFactory
this.sqlSessionFactory = sqlSessionFactory;
//設(shè)置執(zhí)行器的類型
this.executorType = executorType;
//異常相關(guān)處理類
this.exceptionTranslator = exceptionTranslator;
//sqlSession的代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
}
對(duì)JDK動(dòng)態(tài)代理熟悉的朋友,一定會(huì)先看到newProxyInstance
。它是給sqlSession接口創(chuàng)建了一個(gè)代理類,這個(gè)代理類的處理器程序就是SqlSessionInterceptor()
。不用多說,SqlSessionInterceptor肯定實(shí)現(xiàn)了InvocationHandler接口。
這就意味著,當(dāng)調(diào)用到sqlSession的時(shí)候,實(shí)際執(zhí)行的它的代理類,代理類又會(huì)調(diào)用到處理器程序的invoke()方法。
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args){
//內(nèi)容先略過不看
}
}
最終在setSqlSessionFactory
這個(gè)方法里,sqlSession獲取到的是SqlSessionTemplate實(shí)例。而在SqlSessionTemplate對(duì)象中,主要包含sqlSessionFactory和sqlSessionProxy,而sqlSessionProxy實(shí)際上是SqlSession接口的代理對(duì)象。
四、創(chuàng)建Mapper接口的代理
上面我們說到MapperFactoryBean繼承自SqlSessionDaoSupport,還有一點(diǎn)沒說的是,它同時(shí)實(shí)現(xiàn)了FactoryBean接口。
這就說明,MapperFactoryBean不是一個(gè)純粹的人。啊不對(duì),不是一個(gè)純粹的Bean,而是一個(gè)工廠Bean。如果要聲明一個(gè)Bean為工廠Bean,它要實(shí)現(xiàn)FactoryBean接口,這個(gè)接口就三個(gè)方法。
public interface FactoryBean<T> {
//返回對(duì)象的實(shí)例
T getObject() throws Exception;
//返回對(duì)象實(shí)例的類型
Class<?> getObjectType();
//是否為單例
boolean isSingleton();
}
MapperFactoryBean既然是一個(gè)工廠Bean,那么它返回就不是這個(gè)對(duì)象的本身,而是這個(gè)對(duì)象getObjectType方法返回的實(shí)例。為什么會(huì)這樣呢?
在Spring中執(zhí)行g(shù)etBean的時(shí)候,在創(chuàng)建完Bean對(duì)象且完成依賴注入之后,用調(diào)用到
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
。
這個(gè)方法會(huì)判斷當(dāng)前Bean是否為FactoryBean,如果不是就不再執(zhí)行,如果是最終就會(huì)調(diào)用到它的getObject()方法。
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
//getObjectFromFactoryBean最終調(diào)用的是getObject
Object object = getObjectFromFactoryBean(factory, beanName, !synthetic);
return object;
}
說了這么多,就是怕有的朋友對(duì)工廠Bean不了解,看這塊內(nèi)容的時(shí)候會(huì)比較迷惑。那么,getObject究竟會(huì)返回什么對(duì)象呢?
1、getObject()
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
public T getObject() throws Exception {
//mapperInterface是mapper接口的對(duì)象
return getSqlSession().getMapper(this.mapperInterface);
}
}
getSqlSession()我們已經(jīng)分析完了,它返回的就是SqlSessionTemplate對(duì)象的實(shí)例。所以,我們主要看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);
}
}
我們看到,它實(shí)現(xiàn)比較簡(jiǎn)單。不過,有個(gè)問題是knownMappers
是從哪兒來的呢?它為什么可以根據(jù)type接口就能獲取到MapperProxyFactory
實(shí)例呢?
是否還記得,在掃描注解式SQL聲明的時(shí)候,它調(diào)用到addMapper方法,其實(shí)就是這個(gè)類。
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的實(shí)例,下面來看它內(nèi)部到底創(chuàng)建了什么對(duì)象并返回的。
3、newInstance
在創(chuàng)建過程中,實(shí)際返回的是一個(gè)代理類,即mapper接口的代理類。
public class MapperProxyFactory<T> {
public T newInstance(SqlSession sqlSession) {
//mapperProxy就是一個(gè)調(diào)用程序處理器,顯然它要實(shí)現(xiàn)InvocationHandler接口
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//JDK動(dòng)態(tài)代理,生成的就是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);
}
}
看到這里,我們都已經(jīng)明白了。getObject方法返回的就是mapper接口的代理類。換言之,每一個(gè)mapper接口對(duì)應(yīng)的都是自身的接口代理。那么,在實(shí)際調(diào)用到mapper方法的時(shí)候,就會(huì)調(diào)用到調(diào)用程序處理器的MapperProxy.invoke(Object proxy, Method method, Object[] args)
。
五、總結(jié)
本章節(jié)重要闡述了Mapper接口的代理創(chuàng)建過程。我們簡(jiǎn)單梳理下流程:
掃描mapper接口基本包,將為注冊(cè)為BeanDefinition對(duì)象。
設(shè)置BeanDefinition的對(duì)象的beanClass和sqlSessionFactory屬性。
設(shè)置sqlSessionFactory屬性的時(shí)候,調(diào)用SqlSessionTemplate的構(gòu)造方法,創(chuàng)建SqlSession接口的代理類。
獲取BeanDefinition對(duì)象的時(shí)候,調(diào)用其工廠方法getObject,返回mapper接口的代理類。
最后我們?cè)赟ervice層,通過@Autowired UserMapper userDao
注入屬性的時(shí)候,返回的就是代理類。執(zhí)行userDao的方法的時(shí)候,實(shí)際調(diào)用的是代理類的invoke方法。
最后的最后,我們看一下這個(gè)代理類長(zhǎng)什么樣子。