代理模式是軟件開發(fā)中常見的設(shè)計模式冻晤,它的目的是讓調(diào)用者不用持有具體操作者的引用获雕,而是通過代理者去對具體操作者執(zhí)行具體的操作扳剿。
靜態(tài)代理的實現(xiàn)
操作接口:
public interface Operate {? ? voiddoSomething();}
操作者:
public class Operator implements Operate {? ? @Override? ? public voiddoSomething() {? ? ? ? System.out.println("I'm doing something");? ? }}
代理者:
public class OperationProxy implements Operate {? ? private Operator operator = null;? ? @Override? ? public voiddoSomething() {? ? ? ? beforeDoSomething();if(operator == null){? ? ? ? ? ? operator =? new Operator();? ? ? ? }? ? ? ? operator.doSomething();? ? ? ? afterDoSomething();? ? }? ? private voidbeforeDoSomething() {? ? ? ? System.out.println("before doing something");? ? }? ? private voidafterDoSomething() {? ? ? ? System.out.println("after doing something");? ? }}
調(diào)用者:
public class StaticProxyTest {? ? public static void main(String[] args) {? ? ? ? Operate operate = new OperationProxy();//使用OperationProxy代替Operator? ? ? ? operate.doSomething();? //代理者代替真實者做事情? ? }}
靜態(tài)代理的局限性
可以看到茬故,靜態(tài)代理讓調(diào)用者不用再直接持有操作者的引用脉顿,而是將一切操作交由代理者去完成。但是靜態(tài)代理也有它的局限性:
如果需要增加一個需要代理的方法,代理者的代碼也必須改動進而適配新的操作;
如果需要代理者代理另外一個操作者愧杯,同樣需要對代理者進行擴展并且更加麻煩。
可能有人想到可以用策略模式和工廠模式分別解決上面兩個問題鞋既,但是力九,有沒有更加巧妙的方法呢?首先邑闺,我們了解一下 Java 代碼的執(zhí)行過程跌前。
理解 Java 代碼執(zhí)行流程
要從根本上理解動態(tài)代理的實現(xiàn)原理,得先從 Java 代碼的執(zhí)行流程說起:
JVM 在運行 .class 文件之前陡舅,首先通過 ClassLoader 將 .class 文件以二進制的形式解析并生成實例以供調(diào)用抵乓,我們的代碼執(zhí)行邏輯是在 JVM 的運行期系統(tǒng)中進行工作的,那么靶衍,我們可不可以在自己的代碼里面按照 .class 的格式生成自己的 .class 文件灾炭,進而調(diào)用自定義的 ClassLoader 將其加載出來呢?答案是肯定的颅眶,這樣我們就可以動態(tài)地創(chuàng)建一個類了蜈出。
生成自己的 .class 文件
當然我們不用手動去一點一點拼裝 .class 文件,目前比較常用的字節(jié)碼生成工具有ASM和Javassist涛酗,根據(jù)這個思路铡原,生成 .class 文件的過程如下:
import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.CtNewMethod; public class Test {? ? public static void main(String[] args) throws Exception {? ? ? ? ClassPool pool = ClassPool.getDefault();? ? ? ? //創(chuàng)建 AutoGenerateClass 類? ? ? ? CtClass cc= pool.makeClass("com.guanpj.AutoGenerateClass");? ? ? ? //定義 show 方法? ? ? ? CtMethod method = CtNewMethod.make("public void show(){}", cc);? ? ? ? //插入方法代碼? ? ? ? method.insertBefore("System.out.println(\"I'm just test generate .class file by javassit.....\");");? ? ? ? cc.addMethod(method);? ? ? ? //保存生成的字節(jié)碼? ? ? ? cc.writeFile("D://temp");? ? }}
生成的 .class 文件如下:import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.CtNewMethod; public class Test { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); //創(chuàng)建 AutoGenerateClass 類 CtClass cc= pool.makeClass("com.guanpj.AutoGenerateClass"); //定義 show 方法 CtMethod method = CtNewMethod.make("public void show(){}", cc); //插入方法代碼 method.insertBefore("System.out.println(\"I'm just test generate .class file by javassit.....\");"); cc.addMethod(method); //保存生成的字節(jié)碼 cc.writeFile("D://temp"); }}
反編譯后查看內(nèi)容:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package com.guanpj;public class AutoGenerateClass {? ? public voidshow() {? ? ? ? System.out.println("I'm just test generate .class file by javassit.....");? ? }? ? publicAutoGenerateClass() {? ? }}
可以看到偷厦,javassit 生成的類中,除了 show() 方法之外還默認生成了一個無參的構(gòu)造方法燕刻。
自定義類加載器加載
為了能夠讓自定的類被加載出來只泼,我們自定義了一個類加載器來加載指定的 .class 文件:
public class CustomClassLoader extends ClassLoader {? ? publicCustomClassLoader() {? ? }? ? protected Class findClass(String className) {? ? ? ? String path ="D://temp//"+ className.replace(".","http://") +".class";? ? ? ? byte[] classData = getClassData(path);returndefineClass(className, classData, 0, classData.length);? ? }? ? private byte[] getClassData(String path) {? ? ? ? try {? ? ? ? ? ? InputStream ins = new FileInputStream(path);? ? ? ? ? ? ByteArrayOutputStream baos = new ByteArrayOutputStream();? ? ? ? ? ? int bufferSize = 4096;? ? ? ? ? ? byte[] buffer = new byte[bufferSize];? ? ? ? ? ? int bytesNumRead = 0;while((bytesNumRead = ins.read(buffer)) != -1) {? ? ? ? ? ? ? ? baos.write(buffer, 0, bytesNumRead);? ? ? ? ? ? }returnbaos.toByteArray();? ? ? ? } catch (IOException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }returnnull;? ? }}
接著,用 ClassLoader 加載剛才生成的 .class 文件:
public class TestLoadClass {? ? public static void main(String[] args) throws Exception {? ? ? ? CustomClassLoader classLoader = new CustomClassLoader();? ? ? ? Class clazz = classLoader.findClass("com.guanpj.AutoGenerateClass");? ? ? ? Object object = clazz.newInstance();? ? ? ? Method showMethod = clazz.getMethod("show", null);? ? ? ? showMethod.invoke(object, null);? ? }}
后臺輸出如下:
成功執(zhí)行了 show 方法卵洗!
利用 JDK 中的 Proxy 類進行動態(tài)代理
使用動態(tài)代理的初衷是簡化代碼请唱,不管是 ASM 還是 Javassist,在進行動態(tài)代理的時候操作還是不夠簡便忌怎,這也違背了我們的初衷籍滴。我們來看一下怎么 InvocationHandler 怎么做:
InvocationHandler:
public class InvocationHandlerImpl implements InvocationHandler {? ? Operate operate;? ? //注入操作者對象? ? public InvocationHandlerImpl(Operate operate) {? ? ? ? this.operate = operate;? ? }? ? @Override? ? public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {? ? ? ? System.out.println("before calling method: "+ method.getName());? ? ? ? //調(diào)用操縱者的具體操作方法? ? ? ? method.invoke(operate, args);? ? ? ? System.out.println("after calling method: "+ method.getName());returnnull;? ? }}
調(diào)用者:
public class DynamicProxyTest {? ? public static void main(String[] args) {? ? ? ? //實例化操作者? ? ? ? Operate operate = new Operator();? ? ? ? //將操作者對象進行注入? ? ? ? InvocationHandlerImpl handler = new InvocationHandlerImpl(operate);? ? ? ? //生成代理對象? ? ? ? Operate operationProxy = (Operate) Proxy.newProxyInstance(operate.getClass().getClassLoader(),? ? ? ? ? ? ? ? operate.getClass().getInterfaces(), handler);? ? ? ? //調(diào)用操作方法? ? ? ? operationProxy.doSomething();? ? }}
跟靜態(tài)代理不同的是酪夷,動態(tài)代理的過程主要分為三個步驟
將操作者對象注入 InvocationHandlerImpl 類中榴啸。
將 InvocationHandlerImpl 對象注入 Proxy 類中并返回代理者對象,并在 invoke 方法中進行額外的操作
調(diào)用代理對象的操作方法
利用 CGLIB 進行動態(tài)代理
用 Proxy 類生成代理類的方法為 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 晚岭,第二個參數(shù)是操作者的接口數(shù)組鸥印,意味著只能代理它實現(xiàn)的接口里的方法,對于本來在操作者類中定義的方法表示無能為力坦报,CGLIB(Code Generation Library) 解決了這個問題库说。
MethodInterceptorImpl:
public class MethodInterceptorImpl implements MethodInterceptor {? ? @Override? ? public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {? ? ? ? System.out.println("before calling method:"+ method.getName());? ? ? ? proxy.invokeSuper(obj, args);? ? ? ? System.out.println("after calling method:"+ method.getName());returnnull;? ? }}
調(diào)用者:
public class ProxyTest {? ? public static void main(String[] args) {? ? ? ? Operator operator = new Operator();? ? ? ? MethodInterceptorImpl methodInterceptorImpl = new MethodInterceptorImpl();? ? ? ? //初始化加強器對象? ? ? ? Enhancer enhancer = new Enhancer();? ? ? ? //設(shè)置代理類? ? ? ? enhancer.setSuperclass(operator.getClass());? ? ? ? //設(shè)置代理回調(diào)? ? ? ? enhancer.setCallback(methodInterceptorImpl);? ? ? ? //創(chuàng)建代理對象? ? ? ? Operator operationProxy = (Operator) enhancer.create();? ? ? ? //調(diào)用操作方法? ? ? ? operationProxy.doSomething();? ? }}
使用 CGLIB 進行動態(tài)代理的過程分為四個步驟:
使用 MethodInterceptorImpl 實現(xiàn) MethodInterceptor 接口,并在 intercept 方法中進行額外的操作
創(chuàng)建增強器 Enhance 并設(shè)置被代理的操作類
生成代理類
調(diào)用代理對象的操作方法
總結(jié)
無論是靜態(tài)代理還是動態(tài)代理片择,都能一定程度地解決我們的問題潜的,在開發(fā)過程中可以根據(jù)實際情況選擇合適的方案∽止埽總之啰挪,沒有好不好的方案,只有適不適合自己項目的方案嘲叔,我們應(yīng)該深入研究和理解方案背后的原理亡呵,以便能夠應(yīng)對開發(fā)過程中產(chǎn)生的變數(shù)。
在此我向大家推薦一個架構(gòu)學(xué)習交流群硫戈。交流學(xué)習群號:938837867 暗號:555 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring锰什,MyBatis,Netty源碼分析丁逝,高并發(fā)汁胆、高性能、分布式霜幼、微服務(wù)架構(gòu)的原理嫩码,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備