什么是代理
代理模式,目的就是為其他對象提供一個代理以控制對某個對象的訪問,代理類為被代理者處理過濾消息,說白了就是對被代理者的方法進行增強。
看到這里,有沒有感覺很熟悉?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
我們來通過打印的結果來實實在在的體會代理模式的優點及aop的特性:
如果有種業務場景,需要有多個方法有重復的代碼塊,或者相同的實現規則,如果不用aop我們可能要每個方法寫一份規則,或者自定義個方法,每個方法調用來實現。首先這就已經使代碼變得侵入性,也違反了java的封裝重構原則。
而使用java動態代理,無論這個委托人有多少方法,他都會執行切面邏輯里的規則,這樣很便於后期的代碼維護,即便是后續加入新的方法,也無須考慮其他的,因為在切面里都已經做好了,這樣代碼的侵入性便降了很多。其實這和AOP的原理是一樣的。
動態代理與mybatis
看到這里,我便覺得mybatis和j動態代理必定有着很緊密的聯系。
但我們通過上面的例子,我們知道,想要用動態代理,必須要有個接口的實現類,否則代理接口便沒什么意義。而mybatis只是接口+xml的形式,他是怎么被代理的呢?
還有一個疑問,我們知道Spring的注解如果放在service的接口層,而不是放在實現類,他會找不到這個bean,那為什么mapper層可以加注解呢?
首先我們通過查詢資料知道,mybatis確實是有動態代理實現的。那我們帶着這個疑問,去看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中使用了大量的java動態代理。
這個getMapper呢,到這里也就結束了,可以看到,SqlSession這個對象,代理的接口就是我們的mapper層dao,而這里的切面邏輯,如果當前聲明的類是Object,直接執行方法,如果不是,執行另外的excute。這里我沒有細看,初步理解是區分注解sql與mapper.xml 的sql。因為獲取mapper的sql是通過反射得到的Class類。有興趣的同學可以繼續扒,我精力有限就先到這兒了。
到此為止,第一個問題迎刃而解,總結來說,其實我們注入的mapper,是動態代理產生的對象
。
那么為什么Mapper層加注解,spring也能獲取到呢。
其實這個問題已經和java動態代理沒什么關系了,在這里大概解釋一下。
mybatis並不是spring的產品,而作為第三方的插件,我們都知道spring被稱作膠水框架,而第三方就需要將自己的產品讓spring管理。
換句話說,他們和spring自身的bean生命周期並不是同步的。
spring--------------------
class->掃描->新建實例->交給容器
mybatis ---------------------spring---
class -> 掃描-> 新建對象 -> 交給spring
同理,mybatis那就需要自己創建對象,把他交給spring。而我們平時的那些注解Mapperscan之類的,其實只是mybatis在標志這些接口,使用反射,獲取這些類,實現一個ImportBeanDefinitionRegistrar接口,把自己產生的對象交給Spring。