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

1、問題

在Mybatis架構的最上層就是接口層,它定義的是與數據庫交互的方式。還記不記得咱們在前面章節說的那兩種方式?不記得不要緊,咱們回憶一下。spring

  • Mybatis提供的API

使用Mybatis提供的API進行操做,經過獲取SqlSession對象,而後根據Statement Id 和參數來操做數據庫。sql

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語句的呢bash

2、掃描

一、配置信息

說到這,咱們就要看配置文件中的另一個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的集合。

三、配置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()

3、建立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接口的代理對象。

sqlSession對象實例

4、建立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;
}
複製代碼

說了這麼多,就是怕有的朋友對工廠Bean不瞭解,看這塊內容的時候會比較迷惑。那麼,getObject究竟會返回什麼對象呢?

一、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()。

二、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的實例,下面來看它內部到底建立了什麼對象並返回的。

三、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)

5、總結

本章節重要闡述了Mapper接口的代理建立過程。咱們簡單梳理下流程:

  • 掃描mapper接口基本包,將爲註冊爲BeanDefinition對象。

  • 設置BeanDefinition的對象的beanClass和sqlSessionFactory屬性。

  • 設置sqlSessionFactory屬性的時候,調用SqlSessionTemplate的構造方法,建立SqlSession接口的代理類。

  • 獲取BeanDefinition對象的時候,調用其工廠方法getObject,返回mapper接口的代理類。

最後咱們在Service層,經過@Autowired UserMapper userDao注入屬性的時候,返回的就是代理類。執行userDao的方法的時候,實際調用的是代理類的invoke方法。 最後的最後,咱們看一下這個代理類長什麼樣子。

mapper接口的代理類實例
相關文章
相關標籤/搜索