一、介绍
什么是代理模式
代理模式:在不改变原始类的情况下,引入代理类来给原始类附加不相关的其他功能。
拓展: OOP的纵向拓展和AOP的横向拓展
AOP Aspect-Oriented Programming 是代理模式的典型应用:
AOP关注的是跨越多个对象的行为或关注点,这些关注点通常贯穿于整个应用程序的多个模块中,比如日志记录、安全检查(权限控制)、事务管理等。这些功能并不属于任何一个特定的业务对象,但又广泛影响着多个对象的操作。AOP允许你将这些横切关注点从业务逻辑中分离出来,通过声明的方式将其插入到需要的地方,从而避免代码重复并减少模块间的耦合。
这种在程序运行时,不修改源代码而“编织”进新功能的方式,是跨越了传统的类或对象的边界,是从侧面或者说水平方向上对系统功能进行增强,因此称为横向拓展
OOP Object-Oriented Programming 的纵向拓展:
在JAVA这种OOP的编程语言中。程序被组织成了一系列包含上下级关系的对象,它强调的是从上到下、从抽象到具体的层次化设计。当你在OOP中添加新的功能时,通常会通过创建新的类或者向现有类中添加方法来实现,这涉及到类的继承、接口的实现、对象的组合等机制,这样的扩展方式是沿着类的层次结构进行的,因此被认为是纵向的。
二、静态代理
通过实现目标类相同的接口进行静态代理
在静态代理模式中,我们通常不是直接让代理类继承目标类,而是让代理类和目标类共同实现同一个接口或者继承相同的父类。这样设计的原因是为了保持目标类的独立性,使得代理类可以在不修改目标类的情况下,扩展或控制对目标类的访问。
// 目标接口
interface ISubject {
void doSomething();
}
// 目标类(被代理的对象)
class RealSubject implements ISubject {
@Override
public void doSomething() {
System.out.println("RealSubject is doing something.");
}
}
// 代理类
class ProxySubject implements ISubject {
private ISubject realSubject;
public ProxySubject(ISubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doSomething() {
System.out.println("ProxySubject: Preprocessing before calling doSomething()");
realSubject.doSomething();
System.out.println("ProxySubject: Postprocessing after calling doSomething()");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ISubject subject = new RealSubject();
ISubject proxy = new ProxySubject(subject);
proxy.doSomething();
}
}
基于继承实现动态代理
- 如果原始类并没有定义接口,并且原始类并不是由我们自己开发维护例如它来自一个第三方类库,那么我们就无法给其重新定义接口,和修改原始类。
- 这种情况下可以直接继承该外部类,在需要被增强的方法中调用父类对的同时增加拓展操作来进行代理增强
三、动态代理
静态代理的缺陷
- 每存在一个目标类就需要编写一个代理类,会导致类数量急剧增多
- 拓展性差,一旦修改目标类的接口,那么所有目标类和代理类都需要修改。
- 难以动态管理:静态代理在编译期就已经确定,无法在运行时为不同的目标生成代理类,限制类其的灵活性。
基于反射的动态代理
- 不需要事先编写代理类,而是运行时动态的创建代理类并且使用代理类替换原始类
JDK动态代理
- 根据Proxy.newProxyInstance来生成代理对象;
- 生成代理对象需要 InvocationHandler对象作为参数,并且重写其中的invoke方法来实现对目标代理方法的横向增强;
- 被代理类需要实现对应的被代理方法的接口
//目标类接口
public interface Person {
public void doSomething();
}
//目标类
public class PersonBob implements Person{
@Override
public void doSomething() {
System.out.println("Bob doing something! ");
}
}
/**
* jdk动态代理的实现
* 1. 根据Proxy.newProxyInstance来生成代理对象
* 2. 生成代理对象需要 InvocationHandler对象作为参数,并且重写其中的invoke方法来实现对目标代理方法的横向增强
*
* @author StoneYu
* @date 2022/03/15
*/
public class JDKDynamicProxy implements InvocationHandler {
//被代理的对象
private Person person;
//构造函数
public JDKDynamicProxy(Person person) {
this.person = person;
}
/**
* 创建代理对象
* @return {@link Object}
*/
public Object getTarget(){
Object o = Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
this
);
return o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理类实例
System.out.println("代理类实例:"+proxy.getClass().getName());
//被代理方法的前置增强方法
System.out.println("前置增强:代理逻辑前置处理");
//被代理的目标类原方法执行
Object invoke = method.invoke(person, args);
//被代理方法的后置增强方法
System.out.println("后置增强:代理逻辑后置处理");
return invoke;
}
}
//测试类
public class JDKProxyTest {
public static void main(String[] args) {
System.out.println("原声类调用");
Person bob=new PersonBob();
bob.doSomething();
System.out.println("代理类调用");
JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(new PersonBob());
Person target = (Person) jdkDynamicProxy.getTarget();
target.doSomething();
}
}
原声类调用
Bob doing something!
代理类调用
代理类实例:com.sun.proxy.$Proxy0
前置增强:代理逻辑前置处理
Bob doing something!
后置增强:代理逻辑后置处理
CGLIB动态代理
/**
* 使用Cglib创建代理对象
* 1.核心方法,创建Enhancer并且制定目标类,目标对象、
* @author Jean
* @date 2024/06/03
*/
public class CglibProxy implements MethodInterceptor {
/**
* 创建代理对象
*
* @param target
* @return {@link Object}
*/
public Object getInstance(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
Object o = enhancer.create();
return o;
}
/**
* 增强逻辑
*
* @param o 生成的代理对象
* @param method 被代理的方法信息
* @param objects 被代理的方法的实际调用参数
* @param methodProxy cglib提供的调用目标方法的工具,通过proxy.invokeSuper(obj,args) 来调用被代理的目标方法
* @return {@link Object}
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB实际代理对象:" + o.getClass().getName());
//前置增强
System.out.println("CGLIB前置增强");
//被代理类对象的方法调用
Object resultObject = methodProxy.invokeSuper(o, objects);
//前置增强
System.out.println("CGLIB后置增强");
return resultObject;
}
}
public class CglibProxyTest {
public static void main(String[] args) {
//被代理类
PersonBob personBob = new PersonBob();
//创建代理类
CglibProxy cglibProxy = new CglibProxy();
PersonBob instance = (PersonBob) cglibProxy.getInstance(personBob);
//执行代理
instance.doSomething();
}
}
CGLIB实际代理对象:designpattern.structuralPattern.proxyPattern.jdk.PersonBob$$EnhancerByCGLIB$$374dc67e
CGLIB前置增强
Bob doing something!
CGLIB后置增强
JDK 和 CGLIB 的效率对比
JDK动态代理和CGLIB动态代理在效率上的对比,历史上有过一些变化,尤其是随着Java版本的更新,两者的性能差异也在不断调整。以下是基于历史资料和最新趋势的一个总结:
JDK动态代理(主要基于反射技术):
- 优点:
- 使用简单,直接基于Java的反射机制和
java.lang.reflect.Proxy
类实现。 - 当代理类实现了接口时,JDK动态代理是一个自然的选择。
- 从Java 6到Java 8,JDK动态代理的性能得到了显著提升。
- 在Java 8及之后的版本中,JDK动态代理的效率非常高,甚至在某些场景下超过CGLib。
- 使用简单,直接基于Java的反射机制和
- 缺点:
- 被代理的类必须实现至少一个接口,限制了其应用范围。
CGLIB动态代理(主要基于字节码技术):
- 优点:
- 不要求被代理类实现接口,因此更加灵活,可以代理任何类。
- 早期版本中,CGLIB创建的动态代理对象在运行时的性能往往优于JDK动态代理,特别是在频繁调用方法的场景下。
- 缺点:
- CGLib通过字节码技术生成代理类,这导致在创建代理对象时的开销较大,尤其是在代理类需要频繁创建的情况下。
- 从Java 8开始,随着JDK动态代理性能的提升,CGLib在某些场景下的性能优势已不再明显,甚至可能不如JDK动态代理。
- 使用CGLib可能需要引入额外的依赖。
- final 修饰的类无法进行代理
总结:
- 在Java 8及之后的版本中,JDK动态代理由于其性能的提升和实现的简洁性,成为了许多场景下的首选,特别是当被代理类实现了接口时。
- 对于没有实现接口的类,CGLIB仍然是必要的选择,尽管在性能上可能不再像之前那样有显著优势。
- 在选择时,除了考虑性能,还需要权衡实现的复杂度、项目的依赖管理等因素。现代应用在设计时,越来越多地倾向于利用接口来设计代码,这也使得JDK动态代理成为更常用的选择。
四、代理模式的应用案例
非业务需求的开发
- 代理模式可以用来开发一些横切的非业务需求上,例如监控、统计、鉴权、限流、事物、幂等和日志。
- 将这些功能和具体业务逻辑解,例如在 SpringAOP 上开发一些日志和幂等等操作。
代理模式用在 RPC 中
- 代理模式可以隐藏与远程服务交互的细节,例如标准 RPC 中的数据发送和解析的过程。让使用者不需要关注具体交互的细节,调用远程方法和像调用本地方法一样容易
代理模式在缓存中的应用
例如使用 AOP 实现对某个接口在某个时间段内 重复请求直接返回相同的结果,而某些接口直接返回最新的接口数据。这些逻辑可以完全与具体的业务逻辑解耦。