java三種代理方式總結
阿新 • • 發佈:2019-01-24
最近在學習java代理,總結輸出如下。
java代理分為三種實現方式JDK靜態代理,JDK動態代理和CGLIB代理,三種代理的特點及比較如下表
代理方式 | 實現 | 優點 | 缺點 | 其他 |
---|---|---|---|---|
JDK靜態代理 | 代理類與委託類實現同一介面,並且在代理類中需要硬編碼介面 | 實現簡單,容易理解 | 代理類需要硬編碼介面,在實際應用中可能會導致重複編碼,浪費儲存空間並且效率很低 | 實現簡單 |
JDK動態代理 | 代理類與委託類實現同一介面,主要是通過代理類實現InvocationHandler並重寫invoke方法來進行動態代理的,在invoke方法中將對方法進行增強處理 | 不需要硬編碼介面,程式碼複用率高 | 只能夠代理實現了介面的委託類 | 底層使用反射機制進行方法的呼叫 |
CGLIB動態代理 | 代理類將委託類作為自己的父類併為其中的非final委託方法建立兩個方法,一個是與委託方法簽名相同的方法,它在方法中會通過super呼叫委託方法;另一個是代理類獨有的方法。在代理方法中,它會判斷是否存在實現了MethodInterceptor介面的物件,若存在則將呼叫intercept方法對委託方法進行代理 | 可以在執行時對類或者是介面進行增強操作,且委託類無需實現介面 | 不能對final類以及final方法進行代理 | 底層將方法全部存入一個數組中,通過陣列索引直接進行方法呼叫 |
- JDK靜態代理實現
如上表,靜態代理需要代理類與目標類實現同一介面。
統一介面類Person,Man類實現Person介面;靜態代理類StaticManProxy對Man類進行增強;即代理。
Person.java
public interface Person{
String eat(String food);
String sleep(String where);
}
Man.java
public class Man implements Person{
public String sleep(String name){
System.out.println(name+"睡覺了" );
return "";
}
public String eat(String name){
System.out.println(name+"吃飯了");
return "";
}
}
靜態代理類需要與目標類的介面,即StaticManProxy同樣需要實現Person
StaticManProxy.java
public class StaticManProxy implements Person{
private Person target;
public StaticManProxy(Person target){
this.target=target;
}
public String eat(String name) {
System.out.println("靜態代理 do something~");
target.eat(name);
return null;
}
public String sleep(String name) {
return null;
}
}
測試程式碼
public class ProxyTest {
public static void main(String[] args){
//1、靜態代理
Person person=new Man();
StaticManProxy manProxy=new StaticManProxy(person);
manProxy.eat("靜態代理");
}
}
結果
靜態代理 do something~
靜態代理吃飯了
如上StaticManProxy 即為Man的代理類,同時為其他實現Person介面的代理類;
- JDK動態代理實現
JDK動態代理通過反射實現,直接使用JDK的java.lang.reflect.Proxy.newProxyInstance方法生成代理;
實現程式碼如下
public class ProxyFactory {
// 代理目標
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
target.getClass().getInterfaces(),
/**
* InvocationHandler介面只定義了一個invoke方法,因此對於這樣的介面,
* 我們不用單獨去定義一個類來實現該介面, 而是直接使用一個匿名內部類來實現該介面,new
* InvocationHandler() {}就是針對InvocationHandler介面的匿名實現類
*/
/**
* 在invoke方法編碼指定返回的代理物件乾的工作 proxy : 把代理物件自己傳遞進來 method:
* 把代理物件當前呼叫的方法傳遞進來 args: 把方法引數傳遞進來
*
* 當呼叫代理物件的方法時,
* 實際上執行的都是invoke方法裡面的程式碼,
* 因此我們可以在invoke方法中使用method.getName()就可以知道當前呼叫的是代理物件的哪個方法
*/
new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("動態代理do something");
return method.invoke(target, args);
}
});
}
public Object getProxyByLambda() {
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
target.getClass().getInterfaces(), (proxy, method, args) -> {
System.out.println("Lambda動態代理do something");
return method.invoke(target, args);
});
}
}
測試程式碼段:
ProxyFactory proxy=new ProxyFactory(man);
//2、匿名函式 模式
Person p1=(Person)proxy.getProxy();
System.out.println(p1.eat("匿名函式"));
//3、Lambda 模式
Person p=(Person)proxy.getProxyByLambda();
System.out.println(p.eat("Lambda模式"));
結果
動態代理do something
匿名函式吃飯了
Lambda動態代理do something
Lambda模式吃飯了
如上,JDK動態代理通過Proxy.newProxyInstance直接生成代理物件,如需通過代理實現事物處理或者日誌新增,則可通過以上代理類直接在代理Factory中增加對應需要處理的邏輯即可,程式碼呼叫時只需要將目標類傳入則實現了對應的增強功能。針對所有介面實現類通用。
- CGLIB動態代理
Spring中繼承了CGLIB代理相關的程式碼,所以實現CGLIB代理只需要匯入spring-core的jar包即可。
CGLIB代理的目標類不需要實現任何介面;
public class CgLibMan{
public String eat(String name){
System.out.println(name+"吃飯了");
return "";
}
public String sleep(String name){
System.out.println(name+"睡覺了");
return "";
}
}
CGLIB代理實現類需要實現MethodInterceptor介面。如下:
public class CgLibProxyFactory implements MethodInterceptor{
//維護目標物件
private Object target;
public CgLibProxyFactory(Object target){
this.target=target;
}
//給目標物件建立一個代理物件
public Object getCgLibProxy(){
//1.工具類
Enhancer en = new Enhancer();
//2.設定父類
en.setSuperclass(target.getClass());
//3.設定回撥函式
en.setCallback(this);
//4.建立子類(代理物件)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("開始事務...");
//通過反射 執行目標物件的方法
Object returnValue = method.invoke(target, args);
proxy.invokeSuper(o, args);
System.out.println("提交事務...");
return returnValue;
}
}
測試程式碼段:
//4、Cglib實現代理
//目標物件
CgLibMan ldhCg=new CgLibMan();
//代理物件
CgLibMan proxyCg=(CgLibMan) new CgLibProxyFactory(ldhCg).getCgLibProxy();
proxyCg.eat("Cglib");