代理:為控制A對象,而建立出新B對象,由B對象取代執行A對象所有操作,稱為代理。一個代理體系建立涉及3個參與角色:真實物件(A),代理物件(B),客戶端。
其中的代理物件(B)起到中介作用,連通真實物件(A)與客戶端,如果進一步拓展,代理物件可以實現更加複雜邏輯,例如對真實物件進行存取控制。
需求:員工業務層介面呼叫save需要admin權限,呼叫list不需要權限,沒權限呼叫時拋出例外提示。
/** * 代理接口 */ public interface IEmployeeService { void save(); void list(); }
/** * 真实对象 */ public class EmployeeServiceImpl implements IEmployeeService { @Override public void save() { System.out.println("EmployeeServiceImpl-正常的save...."); } @Override public void list() { System.out.println("EmployeeServiceImpl-正常的list...."); } }
/** * 模拟当前登录用户对象 */ public class SessionHolder { private static String currentUser; public static String getCurrentUser(){ return currentUser; } public static void setCurrentUser(String currentUser){ SessionHolder.currentUser = currentUser; } }
/** * 代理对象 */ public class EmployeeProxy implements IEmployeeService { //真实对象 private EmployeeServiceImpl employeeService; public EmployeeProxy(EmployeeServiceImpl employeeService){ this.employeeService = employeeService; } @Override public void save() { //权限判断 if("admin".equals(SessionHolder.getCurrentUser())){ employeeService.save(); }else{ throw new RuntimeException("当前非admin用户,不能执行save操作"); } } @Override public void list() { employeeService.list(); } }
public class App { public static void main(String[] args) { System.out.println("----------------真实对象--------------------"); EmployeeServiceImpl employeeService = new EmployeeServiceImpl(); employeeService.list(); employeeService.save(); System.out.println("----------------代理对象--------------------"); SessionHolder.setCurrentUser("dafei"); //设置权限(当前登录用户) EmployeeProxy employeeProxy = new EmployeeProxy(employeeService); employeeProxy.list(); employeeProxy.save(); } }
----------------真实对象-------------------- EmployeeServiceImpl-正常的list.... EmployeeServiceImpl-正常的save.... ----------------代理对象-------------------- EmployeeServiceImpl-正常的list.... Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作 at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20) at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)
使用真實物件EmployeeServiceImpl 直接呼叫時,不管是list 還是save都能直接訪問,但不符合需求上的admin權限限制。如果使用代理對象EmployeeProxy,可以完成需求實作。
透過直接建立新類別新類別代理物件方式完成代理邏輯,這種方式稱之為靜態代理模式。
Java常用的動態代理模式有JDK動態代理,也有cglib動態代理,此處重點講解JDK的動態代理
還是原來的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都沒變,新加一個JDK代理控制器-EmployeeInvocationHandler
/** * jdk动态代理控制类,由它牵头代理类获取,代理方法的执行 */ public class EmployeeInvocationHandler implements InvocationHandler { //真实对象-EmployeeServiceImpl private Object target; public EmployeeInvocationHandler(Object target){ this.target = target; } //获取jvm在内存中生成代理对象 public Object getProxy(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //代理对象控制执行方法 //参数1:代理对象 //参数2:真实对象的方法(使用方式得到方法对象) //参数3:真实对象方法参数列表 //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){ throw new RuntimeException("当前非admin用户,不能执行save操作"); } return method.invoke(target, args); } }
測試App類別稍微改動下:
public class App { public static void main(String[] args) { System.out.println("----------------真实对象--------------------"); EmployeeServiceImpl employeeService = new EmployeeServiceImpl(); employeeService.list(); employeeService.save(); System.out.println("----------------代理对象--------------------"); SessionHolder.setCurrentUser("dafei"); EmployeeInvocationHandler handler = new EmployeeInvocationHandler(employeeService); IEmployeeService proxy = (IEmployeeService) handler.getProxy(); proxy.list(); proxy.save(); } }
上面程式碼一樣可以實現需求,跟靜態代理區別就在於少創建了代理物件。此時存在疑問點,沒有創建代理對象,為啥可以實現代理類別呼叫呢? ?
先拋出結論JDK動態代理底層實作原理:使用介面實作方式,運行時,在記憶體中動態建構出一個類,然後編譯,執行。這個類別是一次性的,JVM停止,代理類別就消失。
參與角色 要理解JDK動態代理原理,首先得了解JDK動態代理所涉及的類別
InvocationHandler :真實物件方法呼叫處理器,內建invoke方法,其功能:為真實物件自訂代理邏輯
EmployeeInvocationHandler:員工服務真實物件方法呼叫處理器,此類有3個用途: 1>設定真實物件
//真实对象-EmployeeServiceImpl private Object target; public EmployeeInvocationHandler(Object target){ this.target = target; }
2>自訂代理方法實作邏輯
為真實物件save方法新增了權限校驗邏輯
//代理对象控制执行方法 //参数1:代理对象 //参数2:真实对象的方法(使用方式得到方法对象) //参数3:真实对象方法参数列表 //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){ throw new RuntimeException("当前非admin用户,不能执行save操作"); } return method.invoke(target, args); }
#3>傳回代理物件
方法執行完之後,傳回一個名為:$ProxyX的代理類別(其中的X是序號,一般預設為0),而這個代理類別由JDK動態建構出來。
//获取jvm在内存中生成代理对象 public Object getProxy(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }
Proxy:動態代理控制類,是JDK動態產生的$ProxyX類的父類,它作用如下:
1>透過呼叫ProxyBuilder 類別builder方法建構代理對象類別
private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces){ return proxyCache.sub(intf).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() ); }
2>透過newProxyInstance方法傳回$ProxyX類別的實例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { //... }
$Proxy0:App類別運行時,JDK動態建構出來的代理類,繼承至Proxy類別
public class App { public static void main(String[] args) { //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); System.out.println("----------------真实对象--------------------"); EmployeeServiceImpl employeeService = new EmployeeServiceImpl(); employeeService.list(); employeeService.save(); System.out.println("----------------代理对象--------------------"); SessionHolder.setCurrentUser("dafei"); EmployeeInvocationHandler handler = new EmployeeInvocationHandler(employeeService); IEmployeeService proxy = (IEmployeeService) handler.getProxy(); proxy.list(); proxy.save(); } }
預設情況下JVM是不保存動態建立代理類別字節碼物件的,可以在main方法中配置代理參數讓字節碼保留
//JDK8之前 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //JDK8之后 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
執行完之後,會在專案根目錄產生代理類別字節碼物件。
為了方便解讀,將一些不需要的方法剔除之後
#$Proxy0類別
public class $Proxy0 extends Proxy implements IEmployeeService { private static Method m4; private static Method m3; static { try { m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService") .getMethod("save"); m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService") .getMethod("list"); } catch (Exception e) { e.printStackTrace(); } } public $Proxy0(InvocationHandler var1) throws Throwable { super(var1); } public final void save() throws Throwable { super.h.invoke(this, m4, (Object[])null); } public final void list() throws Throwable{ super.h.invoke(this, m3, (Object[])null); } }
從原始碼上看,$Proxy0的特點:
1>繼承了Proxy類,實作了IEmployeeService 介面
2>透過靜態區塊的方式反射IEmployeeService介面save與list方法,得到他們的方法物件Method
3>呼叫父類別建構器,需要傳入InvocationHandler 參數
4>重寫IEmployeeService介面的save list方法靠的是父類別Proxy的h屬性.invoke方法
下圖所有參與動態代理的類別:
下圖是上圖的操作時序圖,跟著走就對了
以上是Java如何實作JDK動態代理的詳細內容。更多資訊請關注PHP中文網其他相關文章!