Mybatis學習(二)——動態代理源碼分析

Mybatis 之所以不用我們自己實現接口的實例,根本原因就在於它採用了反射機制以及動態代理來代理我們的接口。

一、什麼是動態代理

代理是一種模式,提供了對目標對象的間接訪問方式,即通過代理訪問目標對象。如此便於在目標實現的基礎上增加額外的功能操作,前攔截,後攔截等,以滿足自身的業務需求,同時代理模式便於擴展目標對象功能的特點也爲多人所用。

JDK 本身提供的動態代理只能代理接口。java 中的動態代理位於java.lang.reflect 包下,主要涉及兩個類:
1. Interface InvocationHandler
該接口中僅定義了一個方法
public object invoke(Object obj,Method method, Object[] args)。這個抽象方法在代理類中動態實現。

參數 功能
Object obj 被代理的對象
Method method 是被代理的方法
Object[] args 爲該方法的參數數組

2.Proxy
該類即爲動態代理類,其中主要包含以下內容:
protected Proxy(InvocationHandler h)
構造函數,用於給內部的h賦值。
static Class getProxyClass (ClassLoaderloader, Class[] interfaces)
獲得一個代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部接口的數組。
static Object newProxyInstance(ClassLoad load,Class[] interfaces, InvocationHandler h)
返回代理類的一個實例,返回後的代理類可以當作被代理類使用(可使用被代理類的在Subject接口中聲明過的方法)

實現步驟
1、通過實現 InvocationHandler 接口創建自己的調用處理器;
2、通過爲 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類;
3、通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型;
4、通過構造函數創建動態代理類實例,構造時調用處理器對象作爲參數被傳入。

public class Test{
interface Subject {
public void sayHi();
public void sayHello();
}

static class SubjectImpl implements Subject{

public void sayHi() {
System.out.println("hi");
}

public void sayHello() {
System.out.println("hello");
}
}
static class ProxyInvocationHandler implements InvocationHandler {
private Subject target;
public ProxyInvocationHandler(Subject target) {
this.target=target;
}

// @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("say:");
return method.invoke(target, args);
}
}

public static void main(String[] args) {
Subject subject=new SubjectImpl();
Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
subjectProxy.sayHi();
subjectProxy.sayHello();
/**
* 結果
* say:hi
* say:hello
*/
}
}

JDK 的代理類

/**   
*    
* JDK動態代理類   
*    
*   
*/    
public class JDKProxy implements InvocationHandler {    
    
    private Object targetObject;//需要代理的目標對象    
    
    public Object newProxy(Object targetObject) {//將目標對象傳入進行代理    
        this.targetObject = targetObject;     
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),    
                targetObject.getClass().getInterfaces(), this);//返回代理對象    
    }    
    
    public Object invoke(Object proxy, Method method, Object[] args)//invoke方法    
            throws Throwable {    
        before();
        Object ret = null;      // 設置方法的返回值    
        ret  = method.invoke(targetObject, args);       //invoke調用需要代理的方法
        after();
        return ret;    
    }    
    
    private void before() {//方法執行前   
        System.out.println("方法執行前 !");    
    }    
    private void after() {//方法執行後    
        System.out.println("方法執行後");    
    }    
}

newProxyInstance方法執行了以下幾種操作。

  • 生成一個實現了參數interfaces裏所有接口且繼承了Proxy的代理類的字節碼,然後用參數裏的classLoader加載這個代理類。
  • 使用代理類父類的構造函數 Proxy(InvocationHandler h)來創造一個代理類的實例,將我們自定義的InvocationHandler的子類傳入。
  • 返回這個代理類實例。
public final class $Proxy0 extends Proxy
implements Test.Subject
{
private static Method m4;
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
  
static
{
   try {
     m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]);
     m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
     m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]);
     m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
     m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
public $Proxy0(InvocationHandler paramInvocationHandler)
{
  super(paramInvocationHandler);
}
public final void sayHello()
{
  try
  {
   this.h.invoke(this, m4, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final boolean equals(Object paramObject)
{
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final void sayHi()
{
  try
  {
   this.h.invoke(this, m3, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final int hashCode()
{
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
public final String toString()
{
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
}
}

我們可以看到代理類內部實現比較簡單,在調用每個代理類每個方法的時候,都用反射去調newProxyInstance方法中傳來的h的invoke方法(也就是我們自定義的InvocationHandler的子類中重寫的invoke方法),用參數傳遞了代理類實例、接口方法、調用參數列表,這樣我們在重寫的invoke方法中就可以實現對所有方法的統一包裝了。

二、Mybatis 的動態代理源碼分析

1.Mapper 是怎麼添加進去的呢

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory,dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

等價於

String resource = "mybatis-config.xml"; // xml內容就不貼了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我們後面的 sqlSessionFactory採用以上方式上面生成sqlSessionFactory 的過程中有這麼一行 configuration.addMapper(OrderMapper.class); 添加了一個Mapper 接口
addMapper()方法的實現

Configuration類:  
public <T> void addMapper(Class<T> type) {    
	mapperRegistry.addMapper(type);  
}

從代碼中我們可以看出mapper 實際是被添加進mapperRegistry 中去了,我們繼續研究mapperRegistry 類。

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

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<T>(type));// 注意這裏
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

mapperRegistry裏面有一個私有的HashMap 就是knownMappers,它的key 爲當前的class 對象,value 爲一個MapperProxyFactory()實例

總結:Mapper 接口被添加進了MapperReqister 裏面的一個HashMap 中去了,並且以Mapper接口的class 對象作爲key,以一個攜帶mepper 接口作爲屬性的MapperProxyFactory 作爲value,MapperProxyFactory 從名字來看好像是一個工廠,用來創建mapper proxy 的工廠。

2.mapper 是怎樣獲取的呢
從源碼來看依然是調用configuration 的getMapper()方法

DefaultSqlsession 類:
public <T> T getMapper(Class<T> type) {
   return configuration.<T>getMapper(type, this);
}

configuration 類裏面存放了所有關於xml 文件對的配置信息,從參數上看它要我們傳入一個class 類型,這已經能看到後面一定要用到反射機制和動態代理

Configuration類:
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegister 類

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession); //重點看這裏
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

我們調用的session.getMapper(order.class)最終會到這裏,這個方法根據order的class對象,以它爲key在knowMappers 中找到了對應的value——MapperProxyFactory(BlogMapper) 對象,然後調用這個對象的newInstance()方法。根據這個名字,我們就能猜到這個方法是創建了一個對象
MapperProxyFactory()

public class MapperProxyFactory<T> { 
	//映射器代理工廠  
	private final Class<T> mapperInterface; 
	private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, 	MapperMethod>();  //刪除不必要代碼 
	
	@SuppressWarnings("unchecked")  
	protected T newInstance(MapperProxy<T> mapperProxy) {   
	//使用了JDK自帶的動態代理生成映射器代理類的對象    
		return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { 					mapperInterface }, mapperProxy);  
	} 
	public T newInstance(SqlSession sqlSession) {    
			final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, 	methodCache);    
		return newInstance(mapperProxy);  
	}
}

最終是通過Proxy.newProxyInstance產生了一個OrdersMapper1的代理對象。Mybatis 爲了完成 Mapper 接口的實現,運用了代理模式。具體是使用了JDK動態代理,這個Proxy.newProxyInstance方法生成代理類的三個要素是:

  • ClassLoader —— 指定當前接口的加載器即可
  • Order —— 當前被代理的接口
  • MapperProxy—— 代理類是什麼

代理模式中,代理類(MapperProxy)中才真正的完成了方法調用的邏輯。我們貼出MapperProxy的代碼

public class MapperProxy<T> implements InvocationHandler, Serializable {
//實現了InvocationHandler接口

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
    //代理以後,所有Mapper的方法調用時,都會調用這個invoke方法
      try {
        return method.invoke(this, args);// 注意1
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了緩存
    //執行CURD
    return mapperMethod.execute(sqlSession, args);// 注意2
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

Blog blog =mapper.getOrdersById(3); 實際上最後是會調用這個MapperProxy的invoke方法。這段代碼中,if 語句先判斷,我們想要調用的方法是否來自Object類,這裏的意思就是,如果我們調用toString()方法,那麼是不需要做代理增強的,直接還調用原來的method.invoke()就行了。只有調用getOrdersById之類的方法的時候,才執行增強的調用——即mapperMethod.execute(sqlSession, args);

而mapperMethod.execute(sqlSession, args);這句最終就會執行增刪改查了

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        }
       // 刪除部分代碼      
      }
  // 刪除部分代碼
    return result;
  }

我們分析了 Mapper接口是如何註冊的,Mapper接口是如何產生動態代理對象的,Maper接口方法最終是如何執行的。總結起來主要就是這幾個點:

  • Mapper 接口在初始SqlSessionFactory 註冊的
  • Mapper 接口註冊在了名爲 MapperRegistry 類的 HashMap中, key = Mapper class value = 創建當前Mapper的工廠
  • Mapper 註冊之後,可以從SqlSession中get
  • SqlSession.getMapper 運用了 JDK動態代理,產生了目標Mapper接口的代理對象
  • 動態代理的 代理類是 MapperProxy ,這裏邊最終完成了增刪改查方法的調用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章