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 ,這裏邊最終完成了增刪改查方法的調用