目录
代理模式的用途
代理模式的实现
静态代理
JDK动态代理
CGLIB动态代理
代理模式的特点
与其他模式比较
代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过创建一个代理对象来间接访问原始对象。代理模式的核心思想是将对目标对象的访问进行控制,并在访问前后执行一些额外操作,以增强或限制原始对象的功能。
在代理模式中,代理对象和原始对象都实现相同的接口,使得客户端无需知道实际的对象,只需要通过代理去访问目标对象。代理对象可以拦截对目标对象的访问,然后决定是否允许、何时以及如何访问目标对象。
总之,代理模式通过引入代理对象,实现了对目标对象的间接访问和控制,提供了更加灵活和安全的对象访问方式。
代理模式的常见用途包括:
远程代理:通过代理对象在不同的地址空间中访问远程对象,隐藏了网络通信细节。
虚拟代理:延迟创建开销较大的对象,直到真正需要使用时才进行创建,以提升性能。
安全代理:控制对敏感对象的访问,限制非授权用户的操作权限。
智能代理:在访问对象前后执行额外的逻辑,如缓存结果、记录日志、实现懒加载等。
代理模式的角色
抽象主题角色(Subject):定义了真实主题(Real Subject)和代理(Proxy)之间的共同接口,代理类和被代理类都要实现该接口,这样代理对象可以替代真实主题进行操作。
真实主题角色(Real Subject):定义了代理所代表的真实对象,是最终执行业务逻辑的对象,供代理角色调用。
代理角色(Proxy):实现了抽象主题接口,并维护一个指向真实主题对象的引用,是真实角色的代理, 需要持有真实角色的引用。代理对象可以在执行真实主题操作前后进行一些额外处理。
通过代理模式,客户端可以通过与代理对象进行交互来完成任务,代理对象在必要时会调用真实对象来执行具体的业务逻辑。这种方式可以增加额外的功能,同时也可以隐藏真实对象的细节,实现了客户端与真实对象之间的解耦。
代理模式的类图
代理模式的分类
代理模式分为:静态代理,JDK动态代理,CGLIB动态代理三类。
静态代理、JDK动态代理和CGLIB动态代理都是常见的代理模式实现方式,它们在实现上有一些区别:
静态代理:
JDK动态代理:
CGLIB动态代理:
综上所述,静态代理在编译期间就确定代理类,需要为每个真实类编写代理类;JDK动态代理通过反射在运行时动态生成代理类,代理对象必须实现接口;而CGLIB动态代理是针对类进行代理,不需要实现接口,通过继承在运行时生成代理类。根据具体需求和场景,选择适合的代理方式。
下面将依次展现三种代理模式的实现
其代码实现如下
抽象主题角色代码
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();
}
真实主题角色代码
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 有一百套房子要出租 ");
}
}
代理角色代码
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("完成售后服务");
}
}
测试代码
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代理实例的基本步骤如下:
其代码实现如下
抽象主题角色代码
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();
}
真实主题角色代码
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 有一百套房子要出租 ");
}
}
代理角色代码
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;
}
}
测试代码
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 的相关依赖。可以通过 Maven 或其他构建工具将 CGLIB 加入到项目中。
创建目标类:定义一个目标类(被代理类),它不需要实现任何接口。
创建拦截器类:编写一个拦截器类,实现 MethodInterceptor
接口,并重写 intercept
方法。该方法在代理对象的方法调用前后执行额外的逻辑。
创建代理对象:使用 CGLIB 的 Enhancer
类来生成代理对象。设置目标类为父类,设置拦截器为回调方法。
调用代理对象:通过代理对象调用目标类的方法,这时会先调用拦截器中的逻辑,再调用目标类的方法。
抽象主题角色代码(可不需要)
真实主题角色代码
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 有一百套房子要出租 ");
}
}
代理角色代码
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;
}
}
测试代码
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)