JDK動態代理、責任鏈在mybatis中的應用


以前一直覺得寫博客是給別人看的,所以很少分享自己寫的東西。這段時間突然意識到博客是給自己看的。

歡迎各位喜歡java的朋友騷擾。

最近在學習mybatis,看了下源代碼。翻到了Interceptor的實現,恰好前不久看過JDK的動態代理和責任鏈,因此來記錄一下。

一:JDK的動態代理

概念性質的東西就不談了,畢竟網上很多。JDK的動態代理要求接口和接口的實現類

public interface Target {
	public void execute();
}

 

/**
 * Target的實現類
 * @author wpr
 *
 */
public class TargetImpl implements Target {
	@Override
	public void execute() {
		System.out.println("execute");
	}
}

   a.JDK原生的動態代理寫法

 要求實現InvocationHandler接口,在invoke方法內實現攔截的邏輯(不懂得去看JDK的動態代理)

public class TargetProxy implements InvocationHandler{
	Target target;
	public TargetProxy(Target target) {
		this.target = target;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("攔截前");
		Object o= method.invoke(target, args);
		System.out.println("攔截后");
		return o;
	}
}

 測試的類:

	@Test
	public void test3(){
		Target target = new TargetImpl();
		target = (Target) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(),new TargetProxy(target));
		target.execute();
	}

 以上就是JDK動態代理的實現,但是存在問題,Proxy.newProxyInstance(..)完全可以交給TargetProxy來處理,於是第二版出現

public class TargetProxy implements InvocationHandler{
    //...........上面的代碼省略了...............
	public static Object bind(Target target){
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), new TargetProxy(target));
	}
}

 測試類:

	@Test
	public void test2(){
		Target target = new TargetImpl();
		target = (Target) TargetProxy.bind(target);
		target.execute();
	}

 但還是存在問題,業務代碼如果是execute()的話,所有的邏輯都寫死在invoke()方法里面了,不符合設計模式的要求。結合面向切面的編程,做如下說明,target.execute()視為業務代碼,在invoke()方法前進行插入切面(例如記錄日志、開啟事務等),設計Interceptor接口

public interface Interceptor {
	public void intercept();
}

 intercept()方法負責處理各種前期准備,下面是Interceptor的兩個實現類

public class LogInterceptor implements Interceptor{
	@Override
	public void intercept(){
		System.out.println("日志記錄開始");
	}
}

 

public class TransactionInterceptor implements Interceptor {
	@Override
	public void intercept() {
		System.out.println("事務開啟");
	}
}

 代理對象進一步改變,為了形象的說明是攔截器棧,所以我用了Stack,但是感覺使用List(ArrayList更合理一點)

public class TargetProxy implements InvocationHandler{
	private Target target;
	private Stack<Interceptor> interceptorStack;
	
	public TargetProxy(Target target, Stack<Interceptor> interceptorStack) {
		this.target = target;
		this.interceptorStack = interceptorStack;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		for(Interceptor interceptor:interceptorStack){
			interceptor.intercept();
		}
		return method.invoke(target, args);
	}
}

 在每次執行業務代碼execute(...)之前都會攔截,測試代碼如下:

	@org.junit.Test
	public void test() {
		Stack<Interceptor> interceptorStack =new Stack<>();
		interceptorStack.add(new LogInterceptor());
		interceptorStack.add(new TransactionInterceptor());
		
		Target target = new TargetImpl();
		target = (Target) Proxy.newProxyInstance(target.getClass().getClassLoader(),
						target.getClass().getInterfaces(),new TargetProxy(target, interceptorStack));
		target.execute();
	}

 接下來更近一步,根據代碼的設計准則,將不變的和變化的分離開。我們設計一個Invocation的類,先看下它的實現:

(其實這個地方還可以這樣理解:為了在Interceptor中得到被攔截對象的信息,需要定義一種數據結構來表示被攔截的方法,就是Invocation。這樣就實現了攔截器Interceptor和具體的對象之間的解耦)

public class Invocation {
	private Object target;
	private Method method;
	private Object[] args;

	public Invocation(Object target, Method method, Object[] args) {
		this.target = target;
		this.method = method;
		this.args = args;
	}
	/**
	 * 調用代理類的方法
	 * @return
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 */
	public Object process() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		return method.invoke(target, args);
	}
       //省略了getter和setter       
}

 Invocation類就是將被代理的目標類對立出出來,target表示目標類,method是攔截的方法,args是方法參數,於是新的TargetProxy變成了下面的樣子。僅僅是invoke

public class TargetProxy implements InvocationHandler{
	private Target target;
	private Interceptor interceptor;
	
	public TargetProxy(Target target,Interceptor interceptor) {
		this.target = target;
		this.interceptor= interceptor;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Invocation invocation = new Invocation(target, method, args);
		return interceptor.intercpt(invocation);
	}
}

 同時,要改變Interceptor的行為:

public interface Interceptor {
	
	public Object intercpt(Invocation invocation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}

 具體的實現如下,一定返回invocation.process();要不然攔截就會斷掉

public class LogInterceptor implements Interceptor{

	@Override
	public Object intercpt(Invocation invocation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		System.out.println("打印日志");
		return invocation.process();
	}

}

 但是問題又出現了,我們希望目標類只需要了解攔截它的類就可以,並不需要知道它的代理類,於是把target的攔截過程放在Interceptor接口中完成(實際操作交個TargetProxy)。最終我們的Interceptor接口變成了

public interface Interceptor {
	public Object intercept(Invocation invocation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
	public Object register(Object object);
}
public class LogInterceptor implements Interceptor{
	@Override
	public Object intercept(Invocation invocation) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		System.out.println("日志攔截前:");
		return invocation.process();
	}
	@Override
	public Object register(Object target) {
		return TargetProxy.bind(target, this);
	}
}

 

public class TargetProxy implements InvocationHandler{
	private Object target;
	private Interceptor interceptor;
	
	public TargetProxy(Object target, Interceptor interceptor) {
		this.target = target;
		this.interceptor = interceptor;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Invocation invocation = new Invocation(target, method, args);
		return interceptor.intercept(invocation);
	}
	
	public static Object bind(Object target,Interceptor interceptor){
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(),new TargetProxy(target,interceptor));
	}
}

 到此為止,目標類僅需要知道在執行前應該由誰去攔截它就可以了,測試代碼如下:

	@org.junit.Test
	public void test() {
		Target target = new TargetImpl();
		Interceptor interceptor = new LogInterceptor();
		target =(Target) interceptor.register(target);
		target.execute();
	}

 好處顯而易見,在使用時根本不必知道代理的存在,只要定義業務邏輯,和對業務邏輯的攔截(切面),然后把他們綁定在一起就可以了。

二:責任鏈

以上代碼實現了對一個業務的一次攔截,但如果對其進行多次攔截的話就需要用到責任鏈了(依然略過概念,自己google吧)

public class InterceptorChain {
	private Stack<Interceptor> interceptors;
	
	public InterceptorChain(Stack<Interceptor> interceptors) {
		this.interceptors = interceptors;
	}
	public Object registerAll(Object target){
		for(Interceptor interceptor:interceptors){
			target = TargetProxy.bind(target, interceptor);
		}
		return target;
	}
	public void addInterceptor(Interceptor interceptor){
		interceptors.add(interceptor);
	}
	public Stack<Interceptor> getInterceptor(){
		return (Stack<Interceptor>) Collections.unmodifiableCollection(interceptors);
	}
}

 registerAll(...)方法來完成對目標的全部代理,一層一層的包裹,測試類

@Test
	public void interceptorChainTest(){
		Stack<Interceptor> interceptors = new Stack<>();
		LogInterceptor logInterceptor = new LogInterceptor();
		TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
		interceptors.add(logInterceptor);
		interceptors.add(transactionInterceptor);
		InterceptorChain interceptorChain = new InterceptorChain(interceptors);
		
		Target target = new TargetImpl();
		target= (Target)interceptorChain.registerAll(target);
		target.execute();
	}

 以上內容都比較基礎和理論,但mybatis的Interceptor完全是我們這樣實現的

三:mybatis的攔截分析

其中大部分和之前的分析一致,Plugin就是TargetProxy,內部實現的代碼邏輯也完全相同,Signature是實現對特定方法攔截的,不在今天的記錄范圍內。之前的工作相當於完成了這個部分的工作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM