目录
代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过创建一个代理对象来间接访问原始对象。代理模式的核心思想是将对目标对象的访问进行控制,并在访问前后执行一些额外操作,以增强或限制原始对象的功能。
在代理模式中,代理对象和原始对象都实现相同的接口,使得客户端无需知道实际的对象,只需要通过代理去访问目标对象。代理对象可以拦截对目标对象的访问,然后决定是否允许、何时以及如何访问目标对象。
总之,代理模式通过引入代理对象,实现了对目标对象的间接访问和控制,提供了更加灵活和安全的对象访问方式。
代理模式的用途
代理模式的常见用途包括:
远程代理: 通过代理对象在不同的地址空间中访问远程对象,隐藏了网络通信细节。
虚拟代理: 延迟创建开销较大的对象,直到真正需要使用时才进行创建,以提升性能。
安全代理: 控制对敏感对象的访问,限制非授权用户的操作权限。
**智能代理:**在访问对象前后执行额外的逻辑,如缓存结果、记录日志、实现懒加载等。
代理模式的实现
代理模式的角色
抽象主题角色(Subject):定义了真实主题(Real Subject)和代理(Proxy)之间的共同接口,代理类和被代理类都要实现该接口,这样代理对象可以替代真实主题进行操作。
真实主题角色(Real Subject):定义了代理所代表的真实对象,是最终执行业务逻辑的对象,供代理角色调用。
代理角色(Proxy):实现了抽象主题接口,并维护一个指向真实主题对象的引用,是真实角色的代理, 需要持有真实角色的引用。代理对象可以在执行真实主题操作前后进行一些额外处理。
通过代理模式,客户端可以通过与代理对象进行交互来完成任务,代理对象在必要时会调用真实对象来执行具体的业务逻辑。这种方式可以增加额外的功能,同时也可以隐藏真实对象的细节,实现了客户端与真实对象之间的解耦。
代理模式的类图
代理模式的分类
代理模式分为:静态代理,JDK动态代理,CGLIB动态代理三类。
静态代理、JDK动态代理和CGLIB动态代理都是常见的代理模式实现方式,它们在实现上有一些区别:
静态代理:
- 静态代理需要手动编写代理类,代理类和真实类实现相同的接口或继承相同的父类。
- 在编译期间,代理类的代码就已经确定,无法在运行时动态修改代理行为。
- 需要为每个真实类编写一个对应的代理类,导致代码冗余。
JDK动态代理:
- JDK动态代理利用Java反射机制生成代理类,无需手动编写代理类。
- 基于接口的代理,代理对象必须实现一个或多个接口。
- 代理类是在运行时动态生成的,可以通过InvocationHandler接口在代理类的方法前后插入额外逻辑。
- JDK动态代理只能代理实现了接口的类,对于没有实现接口的类不能进行代理。
CGLIB动态代理:
- CGLIB是针对类进行代理的方式,通过继承来实现代理。
- CGLIB动态代理不需要目标类实现接口,因此可以代理没有实现接口的类。
- 代理类是通过字节码技术在运行时动态生成的,生成的代理类是目标类的子类。
- CGLIB代理相比JDK动态代理的效率略低。
综上所述,静态代理在编译期间就确定代理类,需要为每个真实类编写代理类;JDK动态代理通过反射在运行时动态生成代理类,代理对象必须实现接口;而CGLIB动态代理是针对类进行代理,不需要实现接口,通过继承在运行时生成代理类。根据具体需求和场景,选择适合的代理方式。
下面将依次展现三种代理模式的实现
静态代理
其代码实现如下
抽象主题角色代码
java
package com.common.demo.pattern.proxy;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 抽象主题角色(Subject) 代理人
* @date 2023/07/23 10:57:42
*/
public interface Agency {
void renting();
}
真实主题角色代码
java
package com.common.demo.pattern.proxy;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 真实主题角色(Real Subject) Evan
* @date 2023/07/23 10:59:29
*/
public class Evan implements Agency{
@Override
public void renting() {
System.out.println("Evan 有一百套房子要出租 ");
}
}
代理角色代码
java
package com.common.demo.pattern.proxy;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 代理角色(Proxy) 房屋代理人
* @date 2023/07/23 11:00:31
*/
public class ProxyAgency implements Agency {
private Agency agency;
public ProxyAgency(Agency agency){
this.agency = agency;
}
@Override
public void renting() {
System.out.println("向房客出租房屋");
this.agency.renting();
System.out.println("完成售后服务");
}
}
测试代码
java
package com.common.demo.pattern.proxy;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 客户类
* @date 2023/07/23 11:03:21
*/
public class Client {
public static void main(String[] args) {
Agency evan = new Evan();
Agency agency = new ProxyAgency(evan);
agency.renting();
}
}
测试截图
JDK动态代理
编写一个jdk代理实例的基本步骤如下:
- 编写业务接口
因为jdk代理是基于接口的,因此,只能将业务方法定义成接口,但它可以一次生成多个接口的代理对象 - 编写调用处理器
即编写一个java.lang.reflect.InvocationHandler接口的实现类,代理对象的业务逻辑就写成该接口的invoke方法中 - 生成代理对象
有了业务接口和调用处理器后,将二者作为参数,通过Proxy.newProxyInstance方法便可以生成这个(或这些)接口的代理对象。比如上述示例代码中的businessProxy对象,它拥有greeting()这个方法,调用该方法时,实际执行的就是invoke方法。
其代码实现如下
抽象主题角色代码
java
package com.common.demo.pattern.proxyJdk;
/**
* @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 抽象主题角色(Subject) 代理人
* @date 2023/07/23 10:57:42
*/
public interface JdkAgency {
void renting();
}
真实主题角色代码
java
package com.common.demo.pattern.proxyJdk;
/**
* @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 真实主题角色(Real Subject) JdkEvan
* @date 2023/07/23 10:59:29
*/
public class JdkEvan implements JdkAgency {
@Override
public void renting() {
System.out.println("JdkEvan 有一百套房子要出租 ");
}
}
代理角色代码
java
package com.common.demo.pattern.proxyJdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 代理角色(Proxy) 房屋代理人
* @date 2023/07/23 11:00:31
*/
public class JdkProxyAgency implements InvocationHandler {
//真实对象
private Object target;
/**
* 建立代理对象和真实对象的代理关系方法,并返回代理对象
*
* @param target 真实对象
* @return 代理对象
*/
public Object bing(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@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;
}
}
测试代码
java
package com.common.demo.pattern.proxyJdk;
/**
* @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 客户类
* @date 2023/07/23 11:03:21
*/
public class JdkClient {
public static void main(String[] args) {
JdkProxyAgency jdkProxyAgency = new JdkProxyAgency();
//绑定关系,因为挂在JdkAgency接口下,所以声明代理对象 jdkProxyAgency
JdkAgency proxy = (JdkAgency) jdkProxyAgency.bing(new JdkEvan());
//注意 此时JdkEvan对象已经是一个代理对象,它会进入代理的逻辑方法invoke
proxy.renting();
}
}
测试截图
CGLIB动态代理
编写一个cglib代理实例的基本步骤如下:
-
添加依赖:首先,需要在项目中添加 CGLIB 的相关依赖。可以通过 Maven 或其他构建工具将 CGLIB 加入到项目中。
-
创建目标类:定义一个目标类(被代理类),它不需要实现任何接口。
-
创建拦截器类:编写一个拦截器类,实现
MethodInterceptor
接口,并重写intercept
方法。该方法在代理对象的方法调用前后执行额外的逻辑。 -
创建代理对象:使用 CGLIB 的
Enhancer
类来生成代理对象。设置目标类为父类,设置拦截器为回调方法。 -
调用代理对象:通过代理对象调用目标类的方法,这时会先调用拦截器中的逻辑,再调用目标类的方法。
抽象主题角色代码(可不需要)
真实主题角色代码
java
package com.common.demo.pattern.proxyCglib;
/**
* @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 真实主题角色(Real Subject) CglibEvan
* @date 2023/07/23 10:59:29
*/
public class CglibEvan{
public void renting() {
System.out.println("CglibEvan 有一百套房子要出租 ");
}
}
代理角色代码
java
package com.common.demo.pattern.proxyCglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 代理角色(Proxy) 房屋代理人
* @date 2023/07/23 11:00:31
*/
public class CglibProxyAgency implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("带领房客看房...签租房协议");
// 动态的回调父类当中的方法
methodProxy.invokeSuper(o, objects);
System.out.println("售后服务");
return o;
}
}
测试代码
java
package com.common.demo.pattern.proxyCglib;
import net.sf.cglib.proxy.Enhancer;
/**
* @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 客户类
* @date 2023/07/23 11:03:21
*/
public class CglibClient {
public static void main(String[] args) {
//生成空的字节码对象
Enhancer enhancer = new Enhancer();
// 设置这个字节码对象的父类(目标对象)
enhancer.setSuperclass(CglibEvan.class);
//设置被增强的方法
enhancer.setCallback(new CglibProxyAgency());
//得到代理对象
CglibEvan factory = (CglibEvan) enhancer.create();
//执行被代理的方法
factory.renting();
}
}
测试截图
代理模式的特点
优点:
- 隐藏对象的具体实现:代理模式通过代理对象与客户端进行交互,客户端无需知道实际的对象是如何实现的,从而将对象的具体实现细节隐藏起来。
- 控制对对象的访问:代理对象可以控制对目标对象的访问,并在访问前后执行一些额外操作。例如,在访问一个敏感对象时,代理可以验证用户的权限;在访问一个远程对象时,代理可以处理网络通信等。
- 扩展原始对象的功能:代理模式可以通过在代理对象中添加一些额外的功能,来扩展原始对象的功能。这样可以避免直接修改原始对象的代码,符合开闭原则。
- 分离客户端和目标对象:代理模式使得客户端只需要与代理对象进行交互,无需直接与目标对象交互。这样可以降低系统的耦合度,提高系统的灵活性和可维护性。
- 懒加载:在使用虚拟代理时,可以延迟创建开销较大的对象,直到真正需要使用时才进行创建。这样可以提升系统的性能,并减少资源的占用。
- 透明性:代理模式可以实现透明的访问,即客户端无需感知自己正在使用的是代理对象还是目标对象,可以将代理视为目标对象的透明扩展。
缺点:
- 引入代理对象会增加系统的复杂性,涉及到多个类之间的交互,增加了开发和维护的成本。
- 由于代理模式需要额外的对象来进行封装和管理,可能会导致系统的运行效率降低。
- 代理模式在某些情况下可能会造成请求的处理速度变慢,特别是远程代理的情况下会涉及到网络通信的延迟。
- 如果代理对象的实现不当,可能会导致系统出现更多的问题,如资源泄漏、并发访问的问题等。
使用场景:
-
远程代理(Remote Proxy):在客户端和远程对象之间建立代理,使得客户端能够通过代理访问远程对象,隐藏了网络通信的细节。
-
虚拟代理(Virtual Proxy):当创建一个对象的成本很高时,可以先使用代理对象来替代真实对象,延迟真实对象的创建。例如,图片加载时可以使用虚拟代理,在需要显示图片时再真正加载。
-
安全代理(Protection Proxy):控制对真实对象的访问权限。代理对象可以检查调用者是否具有执行特定操作的权限,从而保护真实对象的安全性。
-
缓存代理(Caching Proxy):为开销较大的计算结果提供缓存,当再次请求相同的数据时,直接返回缓存结果,避免重复计算。
-
日志记录代理(Logging Proxy):在调用真实对象的方法前后添加日志记录功能,用于记录方法的调用信息、参数等,方便调试和跟踪。
-
延迟加载代理(Lazy Loading Proxy):延迟加载即只在真正需要时才创建真实对象,代理对象会在第一次访问时进行初始化。这种方式可以提高系统启动速度和内存占用。
-
AOP 切面编程(Aspect-Oriented Programming):代理模式常被应用于 AOP 中,通过代理将横切逻辑(如事务管理、日志记录)与业务逻辑分离。
注意事项:
-
接口设计要合理:在定义抽象主题(Subject)时,应该仔细考虑需要暴露给代理对象的方法,避免过于冗杂或不必要的接口方法。
-
选择适当的代理类型:代理模式有静态代理和动态代理两种实现方式。静态代理需要手动编写代理类,而动态代理则可以在运行时生成代理对象。根据具体需求选择合适的代理类型。
-
理解代理对象与真实对象的关系:代理对象作为真实对象的代表,应该能够处理与真实对象相关的事务,并在必要时将请求转发给真实对象。同时,代理对象还可以在调用前后执行额外的操作,如权限验证、性能监控等。
-
考虑代理对象的生命周期:代理对象的生命周期可能与真实对象不同。需要确保代理对象的创建、销毁等操作符合实际需求,避免产生过多的代理对象或无效的代理对象。
-
避免滥用代理模式:代理模式适用于在访问真实对象之前或之后添加额外逻辑的场景。但过度使用代理模式可能会导致代码复杂性增加,降低系统性能。
-
应用场景考虑:代理模式适用于很多场景,比如远程代理、安全代理、延迟加载等。在应用代理模式时,要明确自己的需求,选择合适的代理实现。
与其他模式比较
代理模式和装饰者模式的不同
装饰器模式:强调的是增强自身,增强后你还是你,只不过能力更强了而已。
代理模式:强调要让别人(代理类)帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)
代理模式和外观模式的不同
代理模式:是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
外观模式:定义了一个高层接口,为多个子系统中的接口提供一个一致的界面,不对目标功能进行增强。
更多消息资讯,请访问昂焱数据(https://www.ayshuju.com)