以前一直覺得寫博客是給別人看的,所以很少分享自己寫的東西。這段時間突然意識到博客是給自己看的。
歡迎各位喜歡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是實現對特定方法攔截的,不在今天的記錄范圍內。之前的工作相當於完成了這個部分的工作。
