1. 程式人生 > >jdk動態代理:由淺入深理解mybatis底層

jdk動態代理:由淺入深理解mybatis底層

什麼是代理

代理模式,目的就是為其他物件提供一個代理以控制對某個物件的訪問,代理類為被代理者處理過濾訊息,說白了就是對被代理者的方法進行增強。
看到這裡,有沒有感覺很熟悉?AOP,我們熟知的面向切面程式設計,不也是對方法增強,對切點進行處理過濾麼。
其實AOP這種設計思想,他的精髓便是,在預編譯和執行階段使用動態代理實現的。

初體驗

下面是我自己寫的小例子。

//代理的介面
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:13
 **/
public interface person {
    void doSomething();
    void fun1();
    void fun2();
}

//被代理者/委託人
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:13
 **/
public class Student implements person{
    @Override
    public void doSomething() {
        System.out.println("dosomeThing");
    }

    @Override
    public void fun1() {
        System.out.println("fun1");
    }

    @Override
    public void fun2() {
        System.out.println("fun2");

    }
}
//實現InvocationHandler介面,加入切面邏輯
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:15
 **/
public class StudentProxyHandler implements InvocationHandler {

    Student target;

    public StudentProxyHandler(Student target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("pre");
        Object obj = method.invoke(target,args);
        System.out.println("aft");
        return obj;
    }
}
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:19
 **/
public class Main {
    public static void main(String[] args) {
        Student p=new Student();
        person person = (person) Proxy.newProxyInstance(p.getClass().getClassLoader(), p.getClass().getInterfaces(), new StudentProxyHandler(p));
        person.doSomething();
        System.out.println("______________________");
        person.fun1();
        System.out.println("______________________");
        person.fun2();
    }
}

執行程式碼,我們可以得到:

pre
dosomeThing
after
______________________
pre
dosomeThing
after
______________________
pre
dosomeThing
after

我們來通過列印的結果來實實在在的體會jdk的優點及aop的特性:
如果有種業務場景,需要有多個方法有重複的程式碼塊,或者相同的實現規則,如果不用aop我們可能要每個方法寫一份規則,或者自定義個方法,每個方法呼叫來實現。首先這就已經使程式碼變得侵入性,也違反了java的封裝重構原則。
而使用jdk動態代理,無論這個委託人有多少方法,他都會執行切面邏輯裡的規則,這樣很便於後期的程式碼維護,即便是後續加入新的方法,也無須考慮其他的,因為在切面裡都已經做好了,這樣程式碼的侵入性便降了很多。其實這和AOP的原理是一樣的。

jdk動態代理與mybatis

看到這裡,我便覺得mybatis和jdk動態代理必定有著很緊密的聯絡。
但我們通過上面的例子,我們知道,想要用jdk動態代理,必須要有個介面的實現類,否則代理介面便沒什麼意義。而mybatis只是介面+xml的形式,他是怎麼被代理的呢?
還有一個疑問,我們知道Spring的註解如果放在service的介面層,而不是放在實現類,他會找不到這個bean,那為什麼mapper層可以加註解呢?

首先我們通過查詢資料知道,mybatis確實是有jdk動態代理實現的。那我們帶著這個疑問,去看mybatis的原始碼。

為了便於檢視原始碼,我使用sqlsession的方式獲取mapper。

LRoleMapper lRoleMapper = sqlSessionFactory.openSession().getMapper(LRoleMapper.class);
lRoleMapper.selectAll();

點進getMapper的實現類 SqlSessionManager
發現這裡有sql的各個方法

Connection getConnection(){}
void commit() {}
rollback(){}
int insert(String statement){}
update(String statement){}
...

那我們在往下尋找,我看到了這個方法

 private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                try {
                    return method.invoke(sqlSession, args);
                } catch (Throwable var12) {
                    throw ExceptionUtil.unwrapThrowable(var12);
                }
            } else {
                SqlSession autoSqlSession = SqlSessionManager.this.openSession();

                Object var7;
                try {
                    Object result = method.invoke(autoSqlSession, args);
                    autoSqlSession.commit();
                    var7 = result;
                } catch (Throwable var13) {
                    autoSqlSession.rollback();
                    throw ExceptionUtil.unwrapThrowable(var13);
                } finally {
                    autoSqlSession.close();
                }

                return var7;
            }
        }
    }

看到這裡,是不是眼前一亮,那我們再看這個類的構造方法

 private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
    }

那看到這裡我們明白了,這個類代理的是SqlSessionFactory這個類,而它的切面邏輯,就是執行方法前,如果當前sqlsession存在sqlsession,就正常執行這個方法,如果不存在,就建立一個session,建立失敗再回滾資料。
那這裡的opensession,我直接貼原始碼了,無非就是從配置檔案讀取jdbc,連線驗證後,獲取會話之類,大家感興趣可以一層一層往下扒。

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

我們繼續回到getMapper方法,我們一層一層往下扒,最後我們可以看到這個類

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

再繼續扒newInstance方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

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

        return mapperMethod;
    }
}

那到這裡我們可以得出,mybatis中使用了大量的jdk動態代理。
這個getMapper呢,到這裡也就結束了,可以看到,SqlSession這個物件,代理的介面就是我們的mapper層dao,而這裡的切面邏輯,如果當前宣告的類是Object,直接執行方法,如果不是,執行另外的excute。這裡我沒有細看,初步理解是區分註解sql與mapper.xml 的sql。因為獲取mapper的sql是通過反射得到的Class類。有興趣的同學可以繼續扒,我精力有限就先到這兒了。
到此為止,第一個問題迎刃而解,總結來說,其實我們注入的mapper,是jdk動態代理產生的物件
那麼為什麼Mapper層加註解,spring也能獲取到呢。
其實這個問題已經和jdk動態代理沒什麼關係了,在這裡大概解釋一下。
mybatis並不是spring的產品,而作為第三方的外掛,我們都知道spring被稱作膠水框架,而第三方就需要將自己的產品讓spring管理。
換句話說,他們和spring自身的bean生命週期並不是同步的。

spring--------------------
class->掃描->新建例項->交給容器

mybatis ---------------------spring---
class -> 掃描-> 新建物件 -> 交給spring

同理,mybatis那就需要自己建立物件,把他交給spring。而我們平時的那些註解Mapperscan之類的,其實只是mybatis在標誌這些介面,使用反射,獲取這些類,實現一個ImportBeanDefinitionRegistrar介面,把自己產生的物件交給Spring。