代理模式是軟件開發中常見的設計模式,它的目的是讓調用者不用持有具體操做者的引用,而是經過代理者去對具體操做者執行具體的操做。java
public interface Operate { void doSomething(); } 複製代碼
public class Operator implements Operate { @Override public void doSomething() { System.out.println("I'm doing something"); } } 複製代碼
public class OperationProxy implements Operate { private Operator operator = null; @Override public void doSomething() { beforeDoSomething(); if(operator == null){ operator = new Operator(); } operator.doSomething(); afterDoSomething(); } private void beforeDoSomething() { System.out.println("before doing something"); } private void afterDoSomething() { System.out.println("after doing something"); } } 複製代碼
public class StaticProxyTest { public static void main(String[] args) { Operate operate = new OperationProxy();//使用OperationProxy代替Operator operate.doSomething(); //代理者代替真實者作事情 } } 複製代碼
能夠看到,靜態代理讓調用者不用再直接持有操做者的引用,而是將一切操做交由代理者去完成。可是靜態代理也有它的侷限性:設計模式
可能有人想到能夠用策略模式和工廠模式分別解決上面兩個問題,可是,有沒有更加巧妙的方法呢?首先,咱們瞭解一下 Java 代碼的執行過程。數組
要從根本上理解動態代理的實現原理,得先從 Java 代碼的執行流程提及:架構
JVM 在運行 .class 文件以前,首先經過 ClassLoader 將 .class 文件以二進制的形式解析並生成實例以供調用,咱們的代碼執行邏輯是在 JVM 的運行期系統中進行工做的,那麼,咱們可不能夠在本身的代碼裏面按照 .class 的格式生成本身的 .class 文件,進而調用自定義的 ClassLoader 將其加載出來呢?答案是確定的,這樣咱們就能夠動態地建立一個類了。ide
固然咱們不用手動去一點一點拼裝 .class 文件,目前比較經常使用的字節碼生成工具備ASM 和Javassist,根據這個思路,生成 .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(); //建立 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); //保存生成的字節碼 cc.writeFile("D://temp"); } } 複製代碼
生成的 .class 文件以下:學習
反編譯後查看內容:this
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.guanpj; public class AutoGenerateClass { public void show() { System.out.println("I'm just test generate .class file by javassit....."); } public AutoGenerateClass() { } } 複製代碼
能夠看到,javassit 生成的類中,除了 show() 方法以外還默認生成了一個無參的構造方法。spa
爲了可以讓自定的類被加載出來,咱們自定義了一個類加載器來加載指定的 .class 文件:設計
public class CustomClassLoader extends ClassLoader { public CustomClassLoader() { } protected Class<?> findClass(String className) { String path = "D://temp//" + className.replace(".","//") + ".class"; byte[] classData = getClassData(path); return defineClass(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); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } } 複製代碼
接着,用 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); } } 複製代碼
後臺輸出以下:
成功執行了 show 方法!
使用動態代理的初衷是簡化代碼,不論是 ASM 仍是 Javassist,在進行動態代理的時候操做仍是不夠簡便,這也違背了咱們的初衷。咱們來看一下怎麼 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()); //調用操縱者的具體操做方法 method.invoke(operate, args); System.out.println("after calling method: " + method.getName()); return null; } } 複製代碼
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); //調用操做方法 operationProxy.doSomething(); } } 複製代碼
跟靜態代理不一樣的是,動態代理的過程主要分爲三個步驟
用 Proxy 類生成代理類的方法爲 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ,第二個參數是操做者的接口數組,意味着只能代理它實現的接口裏的方法,對於原本在操做者類中定義的方法表示無能爲力,CGLIB(Code Generation Library) 解決了這個問題。
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()); return null; } } 複製代碼
public class ProxyTest { public static void main(String[] args) { Operator operator = new Operator(); MethodInterceptorImpl methodInterceptorImpl = new MethodInterceptorImpl(); //初始化增強器對象 Enhancer enhancer = new Enhancer(); //設置代理類 enhancer.setSuperclass(operator.getClass()); //設置代理回調 enhancer.setCallback(methodInterceptorImpl); //建立代理對象 Operator operationProxy = (Operator) enhancer.create(); //調用操做方法 operationProxy.doSomething(); } } 複製代碼
使用 CGLIB 進行動態代理的過程分爲四個步驟:
不管是靜態代理仍是動態代理,都能必定程度地解決咱們的問題,在開發過程當中能夠根據實際狀況選擇合適的方案。總之,沒有好很差的方案,只有適不適合本身項目的方案,咱們應該深刻研究和理解方案背後的原理,以便可以應對開發過程當中產生的變數。