動態代理是一種在運行時動態地建立代理對象,動態地處理代理方法調用的機制。html
實際上它是一種代理機制。代理能夠看作是對調用目標的一個封裝,直接經過代理來實現對目標代碼的調用java
提早寫好代理類,每一個業務類都要對應一個代理類,不靈活 git
以上的圖解本質上就是代理模式的一個講解,適用於靜態代理和動態代理,區別在於代理對象和代理方法生成的時間和方式不一樣。github
動態代理是運行時自動生成代理對象,一個缺點是生成代理對象和調用代理方法須要耗費時間spring
動態代理主要有兩種實現方式,一種是JDK動態代理,一種是CGLIB字節碼機制,固然還有Javassist或ASM庫,這兩個在CGLIB那塊一併介紹編程
說到JDK動態代理,就不得不提起反射機制,JDK動態代理就是經過反射機制實現的。api
反射就是經過Class類和java.lang.reflect類庫在運行時獲取某個類的信息。好比經過java.lang.reflect類庫中Field,Method以及Constructor類就能夠獲取類的相關信息數組
下面是經過反射機制實現JDK動態代理的一個簡單例子bash
/**
* 動態代理的實現
*
* @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()
方法主要如下三個參數
動態代理能夠將全部調用重定向到調用處理器,所以一般上會向調用處理器的構造器傳遞一個"實際"對象的引用,從而使得處理器在執行任務時,能夠請求轉發。
cglib是一種基於ASM的字節碼生成庫,用於生成和轉換Java字節碼.
而ASM是一個輕量但高性能的字節碼操做框架。cglib是基於ASM的上層應用,對於代理沒有實現接口的類,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主要由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的動態代理實現主要有兩種方式,JDK動態代理和CGLIB字節碼生成。
默認狀況下,若是Spring AOP發現目標對象後實現了相應的interface,則採用JDK動態代理機制爲其生成代理對象。若是沒有發現接口,則採用CGLIB的方式爲目標對象生成動態的代理對象實例
RPC即遠程過程調用,它的實現中使用到了動態代理,關於RPC的具體原理參照你應該知道的RPC原理等文章
實際上RPC框架要解決的一個問題就是: 如何調用他人的遠程服務?像調用本地服務同樣調用遠程服務。
如何封裝數據,經過網絡傳輸給遠程服務,使遠程接口透明,這就須要動態代理的幫助了。因此透明化遠程服務調用就是要利用動態代理,在代理層對數據進行封裝,網絡傳輸等操做。
實際開發中,咱們不少時候都是利用現成的框架和開源庫,其中就包含動態代理的應用。只有真正瞭解了動態代理的知識,才能更好地理解其在框架中的設計,也有利於更好的去使用框架。