代理模式:为另一个对象提供一个替身或占位符,以便于控制对这个对象的访问
静态代理:
就是将一些在方法中重复的功能提取出来,通过一个专门的类去封装,在具体类中需要的时候就用那个专门的类的对象去调用
动态代理:
原理和静态代理差不多,只是用了一个反射的接口,去调用一些方法方便进行动态调度
动态代理的步骤就是:
先把代理类实例化,再实例化被代理类,将被代理类的对象传到代理类的方法中,因为是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
1、 引入maven依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2、 实现被代理类
// 被代理类(无需实现接口)
public class WechatPayService {
public void pay(int amount) {
System.out.println("微信支付: " + amount + "元");
}
}
3、 实现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动态代理?
1、 优先考虑JDK动态代理,确保符合面向接口编程原则
2、 实在是无需使用接口的使用CGLib
3、 高频调用的方法建议使用CGLib【性能要求高的场景】
4、 注意CGLib的类初始化问题