本文字数:4806字,阅读大约需要15 分钟。
代理模式(Proxy Pattern)是一种结构型设计模式,也叫做委托模式,它允许你提供一个间接访问对象的方式。
用一句话描述代理模式就是:为其他对象提供一种代理以控制对这个对象的访问
代理模式在Java中的Spring框架和Dubbo框架中都有广泛的应用:
通过代理模式,可以实现对对象的访问控制、附加功能增强、性能优化等目的,提高系统的灵活性、可维护性和可扩展性。
代理模式涉及以下几个角色:
实现代理模式步骤如下:
首先定义一个接口:
public interface Subject {
void request();
}
然后实现真实主题类:
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Real Subject handles the request.");
}
}
接着创建代理类:
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
//前置处理
private void preRequest() {
System.out.println("Proxy performs pre-request actions.");
}
//后置处理
private void postRequest() {
System.out.println("Proxy performs post-request actions.");
}
}
客户端调用:
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
输出:
Proxy performs pre-request actions.
Real Subject handles the request.
Proxy performs post-request actions.
Tips:一个代理类,可以代理多个真实角色,并且真实角色之间允许有耦合关系。
在代理模式中,可以区分普通代理和强制代理:
上面提供的代码例子就是普通代理,下面用代码演示下强制代理:
// 抽象主题接口
public interface Subject {
/**
* 待具体实现的方法
*/
void request();
/**
* 获取每个具体实现对应的代理对象实例
* @return 返回对应的代理对象
*/
Subject getProxy();
}
// 强制代理对象
public class ForceProxy implements Subject {
private Subject subject;
public ForceProxy(Subject subject) {
this.subject = subject;
}
/**
* 待具体实现的方法
*/
@Override
public void request() {
preRequest();
subject.request();
postRequest();
}
/**
* @return 返回对应的代理对象就是自己
*/
@Override
public Subject getProxy() {
return this;
}
private void postRequest() {
System.out.println("访问真实主题以后的后续处理");
}
private void preRequest() {
System.out.println("访问真实主题之前的预处理");
}
}
// 具体的实现对象
public class RealSubject implements Subject {
/**
* 该具体实现对象的代理对象
*/
private Subject proxy;
@Override
public Subject getProxy() {
proxy = new ForceProxy(this);
return proxy;
}
/**
* 待具体实现的方法
*/
@Override
public void request() {
if (isProxy()) {
System.out.println("访问真实主题方法");
} else {
System.out.println("请使用指定的代理访问");
}
}
private boolean isProxy() {
return proxy != null;
}
}
客户端调用:
public static void main(String[] args) {
Subject subject = new RealSubject();
subject.request();
}
Output:
请使用指定的代理访问
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxy = new ForceProxy(subject);
proxy.request();
}
Output:
访问真实主题之前的预处理
请使用指定的代理访问
访问真实主题以后的后续处理
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxy = subject.getProxy();
proxy.request();
}
Output:
访问真实主题之前的预处理
访问真实主题方法
访问真实主题以后的后续处理
通过代码可以观察到,强制代理模式下,不允许通过真实角色来直接访问,只有通过真实角色来获取代理对象,才能访问。
高层模块只需调用getProxy
就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。
前面讲的普通代理和强制代理都属于静态代理,也就是说自己写代理类的方式就是静态代理。
静态代理有一个缺点就是要在实现阶段就要指定代理类以及被代理者,很不灵活。
而动态代理是一种在运行时动态生成代理类的机制,可以在不预先知道接口的情况下动态创建接口的实现类,允许在运行阶段才指定代理哪一个对象,比如Spring AOP就是非常经典的动态代理的应用
下面是两个动态代理常用的实现方式:
java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
接口来实现代理对象的生成和方法调用。JDK实现动态代理的核心机制就是java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。
JDK动态代理的动态代理类需要去实现JDK自带的java.lang.reflect.InvocationHandler
接口,该接口中的invoke()
方法能够让动态代理类实例在运行时调用被代理类需要对外实现的所有接口中的方法,也就是完成对真实主题类方法的调用。
具体实现步骤如下:
Subject
表示被代理的对象需要实现的方法。RealSubject
,实现Subject
接口,定义真正的业务逻辑。InvocationHandler
接口的代理处理器类DynamicProxyHandler
,在invoke
方法中执行额外的操作,并调用真实主题的方法。Proxy.newProxyInstance()
方法动态生成代理对象,并调用代理对象的方法。下面是动态代理的示例代码,一起来感受一下:
// 1. 创建接口
public interface Subject {
void request();
}
// 2. 创建真实主题类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject handles the request.");
}
}
// 3. 创建代理处理器类
public class DynamicProxyHandler implements InvocationHandler {
private Object target;
DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行额外操作
System.out.println("Before requesting...");
// 调用真实主题对象的方法
Object result = method.invoke(target, args);
// 执行额外操作
System.out.println("After requesting...");
return result;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
// 创建真实主题对象
Subject realSubject = new RealSubject();
// 创建代理处理器对象
InvocationHandler handler = new DynamicProxyHandler(realSubject);
// 创建动态代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler);
// 调用代理对象的方法
proxy.request();
}
}
这段代码演示了使用 JDK 动态代理实现动态代理的过程:
realSubject
,表示被代理的真实对象。handler
,类型为 InvocationHandler
,并将真实主题对象传入代理处理器中,用于处理代理对象的方法调用。Proxy.newProxyInstance()
方法创建了一个动态代理对象 proxy
,该方法接受三个参数:handler
。proxy
调用 request()
方法,实际上会委托给代理处理器 handler
的 invoke()
方法来处理方法调用,进而调用真实主题对象的对应方法。这段代码通过 JDK 动态代理机制实现了代理对象的动态创建和方法调用处理,实现了对真实主题对象的间接访问,并在调用真实主题对象方法前后进行了额外的处理。
其动态调用过程如图所示:
JDK的动态代理机制只能代理实现了接口的类,否则不能实现JDK的动态代理,具有一定的局限性。
CGLIB(Code Generation Library)是一个功能强大的字节码生成库,可以用来在运行时扩展Java类和实现动态代理。
相对于JDK动态代理基于接口的代理,cglib动态代理基于子类的代理,可以代理那些没有接口的类,通俗说cglib可以在运行时动态生成字节码。
cglib的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,因为采用的是继承,所以不能对final修饰符的类进行代理。
下面是一个使用cglib实现动态代理的示例代码,包括实现步骤:
RealSubject
,无需实现任何接口。MethodInterceptor
接口的代理处理器类DynamicProxyHandler
,在intercept
方法中执行额外的操作,并调用真实主题的方法。Enhancer
类创建代理对象,并设置代理处理器。使用 cglib 需要添加对应的依赖:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
// 1. 创建真实主题类
public class RealSubject {
public void request() {
System.out.println("RealSubject handles the request.");
}
}
// 2. 创建代理处理器类
public class DynamicProxyHandler implements MethodInterceptor {
/**
* 通过Enhancer 创建代理对象
*/
private Enhancer enhancer = new Enhancer();
/**
* 通过class对象获取代理对象
* @param clazz class对象
* @return 代理对象
*/
public Object getProxy(Class<?> clazz) {
// 设置需要代理的类
enhancer.setSuperclass(clazz);
// 设置enhancer的回调
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 执行额外操作
System.out.println("Before requesting...");
// 调用真实主题对象的方法
Object result = proxy.invokeSuper(obj, args);
// 执行额外操作
System.out.println("After requesting...");
return result;
}
}
public class CglibProxyExample {
public static void main(String[] args) {
DynamicProxyHandler proxy = new DynamicProxyHandler();
RealSubject realSubject = (RealSubject) proxy.getProxy(RealSubject.class);
// 调用代理对象的方法
realSubject.request();
}
}
输出:
Before requesting...
RealSubject handles the request.
After requesting...
cglib动态代理相比于JDK动态代理的优缺点如下:
优点:
缺点:
代理模式是一种常用的设计模式,在软件开发中有着广泛的应用。通过引入代理对象,可以实现对真实对象的访问控制、附加功能增强、性能优化等目的。
优点
缺点
总的来说,代理模式通过引入代理对象,实现了对真实对象的间接访问和控制,为系统的设计提供了一种简洁而有效的解决方案。在日常的软件开发中,合理地运用代理模式可以为系统带来更好的结构和性能表现。
希望这篇文章能给你带来收获和思考,如果你也有可借鉴的经验和深入的思考,欢迎评论区留言讨论。