代理模式深度解析:業務解耦與安全管控實踐
- 代理模式:為另一個對象提供一個替身或占位符,以便于控制對這個對象的訪問
靜態代理:
就是將一些在方法中重復的功能提取出來,通過一個專門的類去封裝,在具體類中需要的時候就用那個專門的類的對象去調用
動態代理:
原理和靜態代理差不多,只是用了一個反射的接口,去調用一些方法方便進行動態調度
動態代理的步驟就是:
先把代理類實例化,再實例化被代理類,將被代理類的對象傳到代理類的方法中,因為是Object類型,所以需要轉型再輸出那個方法
圖片
圖片
靜態代理
抽象角色:一般會使用接口或抽象類
package com.carl.agent;
/**
* @Version 1.0.0
* @author carl蔡先生
* @Date 2022/10/03
* @Description 租房
*/
public interface RentHouse {
/**
* 租房流程
*/
void rent();
}
真實角色:被代理的角色
package com.carl.agent;
/**
* @Version 1.0.0
*
* @author carl蔡先生
* @Date 2022/10/03
* @Description 房東--要租房
*/
public class Landlord implements RentHouse {
@Override
public void rent() {
System.out.println("三室一廳,獨廚獨衛,家具齊全!價格面議");
}
}
代理角色:代理真實角色,一般會做一些附屬操作
package com.carl.agent;
/**
* @Version 1.0.0
*
* @author carl蔡先生
* @Date 2022/10/03
* @Description 靜態代理
*/
public class StaticProxy {
private RentHouse rentHouse;
public StaticProxy(){
}
public StaticProxy(RentHouse rentHouse){
this.rentHouse = rentHouse;
}
public void rent(){
rentHouse.rent();
System.out.println("中介費用為房價的一半!");
}
}
使用者:調用代理角色
@Test
public void testStaticAgent(){
//租房直接找房東
RentHouse rentHouse=new Landlord();
//辦理租房流程
rentHouse.rent();
//找中介租房子
StaticProxy staticProxy=new StaticProxy(rentHouse);
//中介給你辦理租房流程
staticProxy.rent();
}
代理的好處
- 可以使真實角色的操作更加純粹,不用關注一些公共的業務
- 公共的業務交給代理角色,實現業務的分工
- 公共業務發生擴展的時候,方便集中管理
缺點
- 一個真實角色就會產生一個代理角色,代碼量增加,開發效率變低
例如:
UserService作為抽象角色,UserServiceImpl作為一個真實角色,需要每次訪問該業務類中的方式的時候,都要做權限判斷,判斷權限是否滿足要求
這個時候使用代理角色,調用方法時,都統一進行權限管理,這樣權限判斷的業務和查詢User表的業務就分離了,互不影響。當UserServiceImpl增加一些業務,也可以通過代理,增加權限管理的業務,統一集中管理
動態代理
動態代理和靜態代理的角色一致,只是通過反射機制,讓代理變成了動態
動態代理的兩大類【Spring的AOP同時支持JDK和CGLib動態代理,具體使用哪種代理取決于目標對象的實現與配置】
- 基于接口的動態代理(JDK動態代理==SpringBoot 2.x默認)
- 基于類的動態代理(CGlib動態代理==SpringBoot 3.x默認)
JDK動態代理
通過java.lang.reflect.Proxy類生成代理對象,要求被代理類必須實現至少一個接口。代理對象會實現與原類相同的接口。
// 接口
public interface PaymentService {
void pay(int amount);
}
// 實現類
public class AlipayService implements PaymentService {
@Override
public void pay(int amount) {
System.out.println("支付寶支付: " + amount + "元");
}
}
//-----以上都是正常接口實現------
//-----以下則是實現動態代理的地方 ------
// InvocationHandler實現
public class SecurityHandler implements InvocationHandler {
private final Object target;
public SecurityHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[安全校驗] 開始驗證身份...");
Object result = method.invoke(target, args);
System.out.println("[安全校驗] 交易完成記錄日志");
return result;
}
}
// 使用代理
public class JdkProxyDemo {
public static void main(String[] args) {
PaymentService realService = new AlipayService();
PaymentService proxy = (PaymentService) Proxy.newProxyInstance(
PaymentService.class.getClassLoader(),
new Class[]{PaymentService.class},
new SecurityHandler(realService)
);
proxy.pay(100); // 代理方法調用
}
}
執行結果:
[安全校驗] 開始驗證身份...
支付寶支付: 100元
[安全校驗] 交易完成記錄日志
Proxy類
Proxy提供了創建動態代理類和實例的靜態方法(newProxyInstance())
常用方法
- getInvocationHandler(Object proxy):返回指定代理實例的調用處理程序
- getProxyClass(ClassLoader loader,類<?>...interfaces):給出類加載器和接口的代理類
- isProxyClass():如果當且僅當使用getProxyClass方法或newProxyInstance方法將指定的類動態生成代理類時,返回true
- newProxyInstance(ClassLoader loader,類<?>[] interfaces,InvocationHandler h):返回指定接口的代理類的實例,該接口將方法調用分派給指定的調用處理程序
JDK提供了兩種方式獲取動態代理類的實例:
InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
/**
* 獲取代理
* @return {@link Object }
*/
public Object getProxy() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
* 第一個參數:this.getClass().getClassLoader(),使用handler對象的classLoader加載代理對象
* 第二個參數:handler.getClass().getInterfaces(),為代理類提供真實對象的接口,便于調用接口中的所有方法
* 第三個參數:this,InvocationHandler對象
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(), handler.getClass().getInterfaces(),this);
}
InvocationHandler類
InvocationHandler類是被代理實例調用處理程序的接口【代理邏輯處理器,實現invoke()方法增強邏輯】
Object invoke(Object proxy, Method method, Object[] args):通過反射獲取被代理類的示例
- proxy:代理類代理的真實對象
- method:通過反射獲取到的需要調用某個對象的方法
- args:指代代理對象方法的傳遞參數
在invoke對象中,調用被代理對象的處理程序,并可以進行增強,具體實現如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(handler, args);
return result;
}
實現步驟
抽象被代理角色:
package com.carl.proxy.dynamic;
/**
* @Version 1.0.0
*
* @author carl蔡先生
* @Date 2022/10/03
* @Description 租房
*/
public interface RentHouse {
/**
* 租金
*/
void rent();
}
真實被代理角色:
package com.carl.proxy.dynamic;
/**
* @Version 1.0.0
*
* @author carl蔡先生
* @Date 2022/10/03
* @Description 房東--要租房
*/
public class Landlord implements RentHouse {
@Override
public void rent() {
System.out.println("三室一廳,獨廚獨衛,家具齊全!價格面議");
}
}
代理角色:
package com.carl.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Version 1.0.0
* @see InvocationHandler
* @author carl蔡先生
* @Date 2022/10/03
* @Description 動態代理--必須實現InvocationHandler接口
*/
public class DynamicProxy implements InvocationHandler {
/**
* 被代理對象
*/
private RentHouse rentHouse;
public void setRentHouse(RentHouse rentHouse) {
this.rentHouse = rentHouse;
}
/**
* 被代理對象的處理程序
* @param proxy 代理對象
* @param method 方法
* @param args 參數
* @throws Throwable 拋出最高異常
* @return {@link Object }
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前后增加操作
lookHouse();
Object result = method.invoke(rentHouse, args);
fare();
return result;
}
/**
* 獲取代理類
* @return {@link Object }
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rentHouse.getClass().getInterfaces(),this);
}
public void lookHouse(){
System.out.println("中介帶看房源!");
}
public void fare(){
System.out.println("中介收中介費!");
}
}
package com.carl.proxy.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Version 1.0.0
* @see InvocationHandler
* @author carl蔡先生
* @Date 2022/10/04
* @Description 通用代理調用處理程序
*/
public class ProxyInvocationHandler implements InvocationHandler {
/**
* @see Object
* 被代理對象
*/
private Object handler;
public ProxyInvocationHandler() {
}
public ProxyInvocationHandler(Object handler) {
this.handler = handler;
}
/**
* 獲取代理
* @return {@link Object }
*/
public Object getProxy() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
* 第一個參數:this.getClass().getClassLoader(),使用handler對象的classLoader加載代理對象
* 第二個參數:handler.getClass().getInterfaces(),為代理類提供真實對象的接口,便于調用接口中的所有方法
* 第三個參數:this,InvocationHandler對象
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(), handler.getClass().getInterfaces(),this);
}
/**
* @param proxy 代理類
* @param method 方法
* @param args 參數
* @throws Throwable 拋出異常
* @return {@link Object }
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(handler, args);
return result;
}
}
CGLib動態代理
通過操作字節碼生成被代理類的子類,不需要實現接口。需要引入第三方庫CGLib
- 引入maven依賴
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
- 實現被代理類
// 被代理類(無需實現接口)
public class WechatPayService {
public void pay(int amount) {
System.out.println("微信支付: " + amount + "元");
}
}
- 實現MethodInterceptor接口
// MethodInterceptor實現
public class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[日志] 方法調用: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 調用父類方法
System.out.println("[日志] 方法執行完成");
return result;
}
}
測試
// 使用代理
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(WechatPayService.class); // 設置父類
enhancer.setCallback(new LogInterceptor()); // 設置回調
WechatPayService proxy = (WechatPayService) enhancer.create();
proxy.pay(200); // 代理方法調用
}
}
執行結果:
[日志] 方法調用: pay
微信支付: 200元
[日志] 方法執行完成
注意事項:
- CGLib無法代理final修飾的最終類
- CGlib需要默認構造函數【空參構造器】
- 匿名類隱式依賴外部類實例,導致CGLib無法生成代理
new ByteBuddy()
.subclass(type)
.method(any()).intercept(MethodDelegation.to(new Object() {
@RuntimeType
public Object intercept(@SuperCall Callable<?> c,
@Origin Method m,
@AllArguments Object[] a) throws Exception {
// implement your interception logic
}
}).make();
指定構造器參數。enhancer.create(new Class<?>[] {type.getEnclosingClass()}, new Object[] {null})
傳遞 null
作為外部類實例(需確保邏輯安全)
該用其他字節碼庫【如:ByteBuddy】
- CGLib存在首次生成代理類時加載過慢問題【需要操作字節碼,可以在啟動階段提前加載代理類】
- MethodProxy.invokeSuper()代替Method.invoke()。可以減少反射開銷。
總結
針對一些開發場景,如何選擇使用JDK動態代理還是CGLib動態代理?
- 優先考慮JDK動態代理,確保符合面向接口編程原則
- 實在是無需使用接口的使用CGLib
- 高頻調用的方法建議使用CGLib【性能要求高的場景】
- 注意CGLib的類初始化問題