概念
代理:為控制A對象,而建立出新B對象,由B對象取代執行A對象所有操作,稱為代理。一個代理體系建立涉及3個參與角色:真實物件(A),代理物件(B),客戶端。
其中的代理物件(B)起到中介作用,連通真實物件(A)與客戶端,如果進一步拓展,代理物件可以實現更加複雜邏輯,例如對真實物件進行存取控制。
案例
需求:員工業務層介面呼叫save需要admin權限,呼叫list不需要權限,沒權限呼叫時拋出例外提示。
靜態代理
1 2 3 4 5 6 7 8 |
public interface IEmployeeService {
void save();
void list();
}
|
登入後複製
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save() {
System.out.println( "EmployeeServiceImpl-正常的save...." );
}
@Override
public void list() {
System.out.println( "EmployeeServiceImpl-正常的list...." );
}
}
|
登入後複製
1 2 3 4 5 6 7 8 9 10 11 12 |
public class SessionHolder {
private static String currentUser;
public static String getCurrentUser(){
return currentUser;
}
public static void setCurrentUser(String currentUser){
SessionHolder.currentUser = currentUser;
}
}
|
登入後複製
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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();
}
}
|
登入後複製
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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();
}
}
|
登入後複製
1 2 3 4 5 6 7 8 | ----------------真实对象--------------------
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,可以完成需求實作。
透過直接建立新類別新類別代理物件方式完成代理邏輯,這種方式稱之為靜態代理模式。
JDK動態代理模式
Java常用的動態代理模式有JDK動態代理,也有cglib動態代理,此處重點講解JDK的動態代理
還是原來的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都沒變,新加一個JDK代理控制器-EmployeeInvocationHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class EmployeeInvocationHandler implements InvocationHandler {
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@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類別稍微改動下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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>設定真實物件
1 2 3 4 5 | private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
|
登入後複製
2>自訂代理方法實作邏輯
為真實物件save方法新增了權限校驗邏輯
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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動態建構出來。
1 2 3 4 5 6 7 | public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
|
登入後複製
Proxy:動態代理控制類,是JDK動態產生的$ProxyX類的父類,它作用如下:
1>透過呼叫ProxyBuilder 類別builder方法建構代理對象類別
1 2 3 4 5 6 7 8 | 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類別的實例
1 2 3 4 5 | public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
}
|
登入後複製
$Proxy0:App類別運行時,JDK動態建構出來的代理類,繼承至Proxy類別
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class App {
public static void main(String[] args) {
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方法中配置代理參數讓字節碼保留
1 2 3 4 | System.setProperty( "sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" );
System.setProperty( "jdk.proxy.ProxyGenerator.saveGeneratedFiles" , "true" );
|
登入後複製
執行完之後,會在專案根目錄產生代理類別字節碼物件。

為了方便解讀,將一些不需要的方法剔除之後
#$Proxy0類別
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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中文網其他相關文章!