28--靜態代理模式和JDK、CGLIB動態代理
阿新 • • 發佈:2018-12-16
在分析AOP的原始碼之前,我們需要了解一下代理模式,代理模式可分為靜態代理和動態代理兩種。
1.靜態代理
代理模式是常用設計模式的一種,我們在軟體設計時常用的代理一般是指靜態代理,也就是在程式碼中顯式指定的代理。
靜態代理由 業務實現類、業務代理類 兩部分組成。業務實現類 負責實現主要的業務方法,業務代理類負責對呼叫的業務方法作攔截、過濾、預處理,主要是在方法中首先進行預處理動作,然後呼叫業務實現類的方法,還可以規定呼叫後的操作。我們在需要呼叫業務時,不是直接通過業務實現類來呼叫的,而是通過業務代理類的同名方法來呼叫被代理類處理過的業務方法。
- 代理類和被代理類
package com.lyc. cn.v2.day04.aop.proxy;
/**
* 賬戶介面
*/
public interface Count {
// 查詢賬戶
void queryCount();
// 修改賬戶
void updateCount();
}
package com.lyc.cn.v2.day04.aop.proxy;
/**
* @author: LiYanChao
* @create: 2018-10-21 12:07
*/
public class CountImpl implements Count {
@Override
public void queryCount() {
System. out.println("==查詢賬戶");
}
@Override
public void updateCount() {
System.out.println("==更新賬戶");
}
}
package com.lyc.cn.v2.day04.aop.proxy;
/**
* 代理類
* @author: LiYanChao
* @create: 2018-10-21 12:08
*/
public class CountProxy implements Count {
private CountImpl countImpl;
/**
* 覆蓋預設構造器
*/
public CountProxy(CountImpl countImpl) {
this.countImpl = countImpl;
}
@Override
public void queryCount() {
System.out.println("==查詢賬戶開始");
// 呼叫真正的查詢賬戶方法
countImpl.queryCount();
System.out.println("==查詢賬戶結束");
}
@Override
public void updateCount() {
System.out.println("==更新賬戶開始");
// 呼叫真正的修改賬戶操作
countImpl.updateCount();
System.out.println("==更新賬戶結束");
}
}
- 測試及結果
@Test
public void test3() {
// 靜態代理
CountImpl countImpl = new CountImpl();
CountProxy countProxy = new CountProxy(countImpl);
countProxy.updateCount();
System.out.println("\n*******\n");
countProxy.queryCount();
}
==更新賬戶開始
==更新賬戶
==更新賬戶結束
*******
==查詢賬戶開始
==查詢賬戶
==查詢賬戶結束
- 總結 一個代理類只能對一個業務介面的實現類進行包裝,如果有多個業務介面的話就要定義很多實現類和代理類才行。而且,如果代理類對業務方法的預處理、呼叫後操作都是一樣的(比如:呼叫前輸出提示、呼叫後自動關閉連線),則多個代理類就會有很多重複程式碼。這時我們可以定義這樣一個代理類,它能代理所有實現類的方法呼叫:根據傳進來的業務實現類和方法名進行具體呼叫。——那就是動態代理。
2.JKD動態代理
JDK動態代理所用到的代理類在程式呼叫到代理類物件時才由JVM真正建立,JVM根據傳進來的 業務實現類物件 以及 方法名 ,動態地建立了一個代理類的class檔案並被位元組碼引擎執行,然後通過該代理類物件進行方法呼叫。我們需要做的,只需指定代理類的預處理、呼叫後操作即可。JDK的動態代理需要實現InvocationHandler介面,並重寫invoke方法。
- 代理類和被代理類
package com.lyc.cn.v2.day04.aop.jdk;
public interface Animal {
void say();
}
package com.lyc.cn.v2.day04.aop.jdk;
public class Dog implements Animal {
@Override
public void say() {
System.out.println("I am a dog...");
}
}
package com.lyc.cn.v2.day04.aop.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
// 目標物件
private Object target;
/**
* 構造方法
* @param target 目標物件
*/
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
/**
* @param proxy JDK動態生成的最終代理物件
* @param method 呼叫真實物件的某個方法的Method物件
* @param args 呼叫真實物件某個方法時接受的引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("==代理方法開始執行");
Object invoke = method.invoke(target, args);
System.out.println("==代理方法結束執行");
return invoke;
}
/**
* 獲取目標物件的代理物件
* @return 代理物件
*/
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
}
}
- 測試及結果
@Test
public void test4() {
// JDK動態代理
MyInvocationHandler handler = new MyInvocationHandler(new Dog());
Animal proxy = (Animal) handler.getProxy();
proxy.say();
}
==代理方法開始執行
I am a dog...
==代理方法結束執行
- 總結 JDK動態代理的代理物件在建立時,需要使用業務實現類所實現的介面作為引數(因為在後面代理方法時需要根據介面內的方法名進行呼叫)。如果業務實現類是沒有實現介面而是直接定義業務方法的話,就無法使用JDK動態代理了。並且,如果業務實現類中新增了介面中沒有的方法,這些方法是無法被代理的(因為無法被呼叫)。如果沒有介面定義又想使用動態代理的話,那麼可以使用CGLIB動態代理。
3.CGLIB動態代理
CGLIB是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。 在使用的時候需要引入cglib和asm的jar包
compile group: 'asm', name: 'asm', version: '3.3.1'
compile group: 'cglib', name: 'cglib', version: '2.2.2'
- 代理類和被代理類
package com.lyc.cn.v2.day04.aop.cglib;
public class Cat {
public void say() {
System.out.println("I am a cat");
}
}
package com.lyc.cn.v2.day04.aop.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
// 這裡的目標型別為Object,則可以接受任意一種引數作為被代理類,實現了動態代理
public Object getInstance(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 返回代理物件
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("==代理方法開始執行");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("==代理方法結束執行");
return result;
}
}
- 測試類及結果
@Test
public void test5() {
// CGLIB動態代理
Cat cat = (Cat) new CglibProxy().getInstance(Cat.class);
cat.say();
}
==代理方法開始執行
I am a cat
==代理方法結束執行
- 總結 1、靜態代理是通過在程式碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,使用者通過代理類呼叫被包裝過的業務方法; 2、JDK動態代理是通過介面中的方法名,在動態生成的代理類中呼叫業務實現類的同名方法; 3、CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;