1. 程式人生 > 程式設計 >動態代理的楷模:原始碼分析Mybatis與Spring(一)

動態代理的楷模:原始碼分析Mybatis與Spring(一)

前言


Mybatis對於我們並不陌生,但他實際工作原理是怎樣的呢?希望看完本篇文章,大家能瞭解一下問題

  • 呼叫的Mapper是介面,實際執行的實現類是什麼?

spring-mybatis.png

原始碼分析


Mybatis有兩處都用了動態代理。一是Mapper介面代理,二是SqlSession代理。具體實現,下面逐一剖析

一. Mapper介面代理

  • 1、Mapper Bean 的IOC

經歷過MapperScannerConfigurer的初始化,包路徑下的Mapper介面都註冊成為的Spring的bean,其BeanName是介面名,BeanClass是MapperFactoryBeanMapperFactoryBean

是工廠Bean,實現了FactoryBean這個介面。

public interface FactoryBean<T> {
    //返回物件的例項
    T getObject() throws Exception;
    //返回物件例項的型別
    Class<?> getObjectType();
    //是否為單例
    boolean isSingleton();
}
複製程式碼

再看看Spring在獲取Bean時的呼叫鏈getBean() -> doGetBean() -> AbstractBeanFactory::getObjectForBeanInstance() -> FactoryBeanRegistrySupport::getObjectFromFactoryBean() -> FactoryBean::getObject()

因此,當IOC呼叫時,返回的例項不是MapperFactoryBean物件本身,而是getObject()返回的例項,型別是getObjectType()返回的型別。

  • 2、getObject()

接下來看看MapperFactoryBean的getObject()實現了什麼。

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
複製程式碼

MapperFactoryBean繼承了SqlSessionDaoSupport,getSqlSession()

返回的是SqlSessionTemplate。SqlSessionTemplate繼承了SqlSeesion介面

  • 3、getMapper()

public class SqlSessionTemplate implements SqlSeesion
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type,this);
  }
複製程式碼

getConfiguration()最終返回的是DefaultSqlSessionFactory的Configuration(Mybatis屬性的大管家)

public Configuration{
  public <T> T getMapper(Class<T> type,SqlSession sqlSession) {
    return mapperRegistry.getMapper(type,sqlSession);
  }
}
複製程式碼

這裡出現了MapperRegistry類,下面具體看看它的實現:

public <T> T getMapper(Class<T> type,SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    ...
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e,e);
    }
  }
複製程式碼
  • 4、newInstance()

到了這裡,終於看到了熟悉的動態代理身影。代理的是Mapper介面,處理類是MapperProxy。

public class MapperProxyFactory<T> {
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[] { mapperInterface },mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    // MapperProxy是
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession,mapperInterface,methodCache);
    return newInstance(mapperProxy);
  }
}
複製程式碼
  • 5、invoke()

IOC呼叫Mapper的方法,最終會由MapperProxy使用當前執行緒的sqlSession執行JDBC操作。

  @Override
  public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this,args);
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy,method,args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession,args);
  }
複製程式碼
  • 6、addMapper()

在getMapper()時細心的小夥伴會存在疑問,final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);是怎麼來的。這要從MapperRegistry類的addMapper()方法說起

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type,new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known,it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config,type);
        // 將註解中的sql轉換成sqlStatement
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
複製程式碼

addMapper()會將當前介面類作為key,MapperProxyFactory為value存到knownMappers中。MapperProxyFactory會在getMapper()執行時取出,生成代理類。

那addMapper()一定是在getMapper()前呼叫的,實際呼叫時機是什麼時候呢? 追根溯源,addMapper() <- Configuration::addMapper() <- MapperFactoryBean::checkDaoConfig() MapperFactoryBean的繼承類如下:

MappperFactoryBean.png
再看看DaoSUpport類:

@Override
	public final void afterPropertiesSet() throws IllegalArgumentException,BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();
    ...
	}
複製程式碼

結論:SpringBoot啟動時會掃描包路徑下所有Mapper介面註冊成Bean,每個Mapper介面Bean的初始化完成並屬性設定完成後,都會呼叫checkDaoConfig(),由此將Mapper載入到knownMappers供IOC時使用。當Spring GetBean時,最終會呼叫到getMapper(),返回當前Mapper介面的動態代理類作為實現類。