動態代理的介紹
動態代理是一種在執行時動態地建立代理物件,動態地處理代理方法呼叫的機制。
實際上它是一種代理機制。代理可以看做是對呼叫目標的一個封裝,直接通過代理來實現對目的碼的呼叫
與靜態代理的比較
靜態代理
提前寫好代理類,每個業務類都要對應一個代理類,不靈活
- ISubject,該介面是被訪問者或者被訪問的物件
- SubjectImpl,被訪問者的具體實現類
- SubjectProxy,被訪問者或被訪問資源的代理實現類
- Client:代表訪問者的抽象角色,client將會訪問Isubject型別的物件或者資源,通過代理類進行訪問。
- 這裡的SubjectImpl和SubjectProxy都實現了ISubject的介面,SubjectProxy是將請求轉發給SubjectImpl,其內部有SubjectImpl的物件,並且可以新增一些限制
以上的圖解本質上就是代理模式的一個講解,適用於靜態代理和動態代理,區別在於代理物件和代理方法生成的時間和方式不同。
動態代理是執行時自動生成代理物件,一個缺點是生成代理物件和呼叫代理方法需要耗費時間
動態代理的實現方式
動態代理主要有兩種實現方式,一種是JDK動態代理,一種是CGLIB位元組碼機制,當然還有Javassist或ASM庫,這兩個在CGLIB那塊一併介紹
JDK動態代理
說到JDK動態代理,就不得不提起反射機制,JDK動態代理就是通過反射機制實現的。
反射機制
反射就是通過Class類和java.lang.reflect類庫在執行時獲取某個類的資訊。比如通過java.lang.reflect類庫中Field,Method以及Constructor類就可以獲取類的相關資訊
JDK動態代理的實現
下面是通過反射機制實現JDK動態代理的一個簡單例子
/**
* 動態代理的實現
*
* @author pjmike
* @create 2018-08-04 17:42
*/
public class DynamicProxy {
public static void main(String[] args) {
IHelloImpl hello = new IHelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
//獲取目標使用者的代理物件
IHello proxyHello = (IHello) Proxy.newProxyInstance(IHelloImpl.class.getClassLoader(), IHelloImpl.class.getInterfaces(), handler);
//呼叫代理方法
proxyHello.sayHello();
}
}
/**
* 被訪問者介面
*/
interface IHello{
void sayHello();
}
/**
* 被訪問者的具體實現類
*/
class IHelloImpl implements IHello {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
/**
*
* @param target 被代理的目標物件
*/
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 執行目標物件的方法
*
* @param proxy 代理物件
* @param method 代理方法
* @param args 方法引數
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke method");
System.out.println("Method name : "+method.getName());
Object result = method.invoke(target, args);
return result;
}
}
複製程式碼
從上面的例子中可以看出,代理物件的生成是通過Proxy.newProxyInstance()
來完成的
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
複製程式碼
newProxyInstance()
方法主要以下三個引數
- 類載入器(ClassLoader)用來載入動態代理類
- 一個要實現介面的陣列,從這點就可以看出,要想使用JDK動態代理,必須要有介面類
- InvocactionHandler介面的一個實現
動態代理可以將所有呼叫重定向到呼叫處理器,因此通常上會向呼叫處理器的構造器傳遞一個"實際"物件的引用,從而使得處理器在執行任務時,可以請求轉發。
利用CGLIB實現動態代理
cglib是一種基於ASM的位元組碼生成庫,用於生成和轉換Java位元組碼.
而ASM是一個輕量但高效能的位元組碼操作框架。cglib是基於ASM的上層應用,對於代理沒有實現介面的類,cglib非常實用。
CGLIB動態代理的簡單例子
本質上說,對於需要被代理的類,它只是動態生成一個子類以覆蓋非final的方法,同時繫結鉤子回撥自定義的攔截器。
新增CGLIB依賴
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
/dependency>
複製程式碼
package com.pjmike.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB動態代理實現
*
* @author pjmike
* @create 2018-08-06 16:55
*/
public class CglibProxy {
public static void main(String[] args) {
//Enhancer是CGLIB的核心工具類,是一個位元組碼增強器,它可以方便的對類進行擴充套件
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
//設定回撥所需的攔截器
enhancer.setCallback(new MyMethodInterceptor());
//通過enhancer.create()方法獲取代理物件
//對代理物件所有非final的方法呼叫都會轉發給MethodInterceptor.intercept方法,
//作用跟JDK動態代理的InvocationHandler類似
PersonService personService = (PersonService) enhancer.create();
System.out.println(personService.sayHello("pjmike"));
}
}
class PersonService {
public String sayHello(String name) {
return "Hello, " + name;
}
}
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(obj, args);
}
}
複製程式碼
至於上面提到的javassist也是需要直接操作位元組碼,跟ASM類似,所以這兩者使用門檻比較高,一般用於框架的底層實現。比如hibernate底層使用了javassist和cglib.
javassist
javassist是一個執行時編譯庫,可以動態的生成或修改類的位元組碼。它有兩種實現動態代理的方案: javassist提供的動態代理介面和javassist位元組碼。
因為javassist提供動態代理介面比較慢,所以這裡主要分析javassist位元組碼,先不考慮其動態代理介面。至於這幾種代理方案的效能比較問題,參考動態代理方案效能對比。
javassist主要由CtClass,CtMethod,CtField幾個類組成,與JDK反射中的Class,Method,Field相似。
簡單使用
新增依賴
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
複製程式碼
程式碼
package com.pjmike.proxy;
import javassist.*;
/**
* javassist位元組碼
*
* @author pjmike
* @create 2018-08-07 0:07
*/
public class JavassistByteCode {
public static void main(String[] args) throws IllegalAccessException, CannotCompileException, InstantiationException, NotFoundException {
ByteCodeAPI byteCodeApi = createJavassistBycodeDynamicProxy();
System.out.println(byteCodeApi.sayHello());
}
public static ByteCodeAPI createJavassistBycodeDynamicProxy() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException {
//獲取執行時類的上下文
ClassPool pool = ClassPool.getDefault();
//動態建立類
CtClass cc = pool.makeClass(ByteCodeAPI.class.getName()+"demo");
cc.addInterface(pool.get(ByteCodeAPI.class.getName()));
//建立屬性
CtField field = CtField.make("private String a;", cc);
cc.addField(field);
//建立方法
CtMethod method = CtMethod.make("public String sayHello() {return \"hello\";}", cc);
cc.addMethod(method);
//新增構造器
cc.addConstructor(CtNewConstructor.defaultConstructor(cc));
Class<?> pc = cc.toClass();
ByteCodeAPI byteCodeApi = (ByteCodeAPI) pc.newInstance();
return byteCodeApi;
}
}
interface ByteCodeAPI {
public String sayHello();
}
//結果:輸出 hello
複製程式碼
動態代理的實際應用
動態代理實際上有很多應用,比如spring aop的實現,rpc框架的實現,一些第三方工具庫的內部使用等等。這裡簡單介紹動態代理在spring aop和RPC框架中的應用
應用一: Spring AOP的動態代理實現
Spring AOP的動態代理實現主要有兩種方式,JDK動態代理和CGLIB位元組碼生成。
預設情況下,如果Spring AOP發現目標物件後實現了相應的interface,則採用JDK動態代理機制為其生成代理物件。如果沒有發現介面,則採用CGLIB的方式為目標物件生成動態的代理物件例項
應用二: RPC框架中的應用
RPC即遠端過程呼叫,它的實現中使用到了動態代理,關於RPC的具體原理參照你應該知道的RPC原理等文章
實際上RPC框架要解決的一個問題就是: 如何呼叫他人的遠端服務?像呼叫本地服務一樣呼叫遠端服務。
如何封裝資料,通過網路傳輸給遠端服務,使遠端介面透明,這就需要動態代理的幫助了。所以透明化遠端服務呼叫就是要利用動態代理,在代理層對資料進行封裝,網路傳輸等操作。
小結
實際開發中,我們很多時候都是利用現成的框架和開源庫,其中就包含動態代理的應用。只有真正瞭解了動態代理的知識,才能更好地理解其在框架中的設計,也有利於更好的去使用框架。