Mybatis源碼分析(四)mapper接口方法是怎樣被調(diào)用到的

一、問題

在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ì)象。

sqlSession對(duì)象實(shí)例

四、創(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)什么樣子。

mapper接口的代理類實(shí)例

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記,整理的知識(shí)點(diǎn),也是為了防止忘記,尊重勞動(dòng)成果,轉(zhuǎn)載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 12,319評(píng)論 6 86
  • 單獨(dú)使用mybatis是有很多限制的(比如無法實(shí)現(xiàn)跨越多個(gè)session的事務(wù)),而且很多業(yè)務(wù)系統(tǒng)本來就是使用sp...
    七寸知架構(gòu)閱讀 3,488評(píng)論 0 53
  • 1 Mybatis入門 1.1 單獨(dú)使用jdbc編程問題總結(jié) 1.1.1 jdbc程序 上邊使...
    哇哈哈E閱讀 3,325評(píng)論 0 38
  • 輕微的郁悶與迷茫,從泰國(guó)回來以后卻陷入了更深的空虛之中,許是外面浮華世界的簇?fù)恚屛颐允Я吮驹摪卜€(wěn)的現(xiàn)實(shí),深深迷戀...
    小爸爸的小崽子閱讀 197評(píng)論 0 1
  • 1、現(xiàn)在pod使用的鏡像是:gem source -a https://gems.ruby-china.org(官...
    艾嘟嘟的嘟嘟閱讀 305評(píng)論 0 0