Mybatis之mapper接口方法調用分析


一、問題

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM