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