代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
一、代理模式的主要作用
- **控制访问:**通过代理对象的方式间接地访问目标对象,防止直接访问对象给系统带来不必要的复杂性
- **功能增强:**通过代理业务对原有业务进行增强
二、代理的两种实现方式
1. 静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码 ),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改 )且麻烦(需要对每个目标类都单独写一个代理类)。
实现步骤:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
示例:定义三个类和一个接口
java
package proxy;
// 定制接口
public interface ByClothes {
public abstract void clothes(String size);
}
java
package proxy;
/**
* 销售类
*/
public class Proxy implements ByClothes {
private ClothesFactory clothesFactory = new ClothesFactory();
@Override
public void clothes(String size) {
FrontService();
clothesFactory.clothes(size);
endService();
}
//前置服务
public void FrontService() {
System.out.println("根据您的需求进行市场调研");
}
//前置服务
public void endService() {
System.out.println("为您提供一条龙的包办服务");
}
}
java
package proxy;
// 工厂类
public class ClothesFactory implements ByClothes{
public void clothes(String size){
System.out.println("已经给您定制好了一件大小为"+size+"的衣服");
}
public void 机器处理(){
}
public void 原材料(){}
}
java
package proxy;
//测试类
public class Test {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.clothes("xxxL");
}
}
在工厂类中,我们定义了定制衣服的具体方法,还有机器处理和原材料两个方法,在代理类中,我们静态的创建了工厂类的实例对象,这个对象是代理类中的静态属性,只能由本类调用,通过这种方式实现了代理类的控制访问功能。
接口的作用则是对特定功能实现增强,我们可以看到工厂类中不止一种方法,如果想要指定调用定制衣服的方法,则需要接口的帮助,在接口中定义该方法,然后让实现类和代理类共同继承接口,则代理类和实现类都需要重写该方法,在测试类中调用代理类的定制方法,在代理类中的定制方法中调用实现类也就是工厂类的定制方法,同时在代理类的定制方法中添加其他的逻辑(功能增强)
2. 动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制 )。从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
假设我们现在再创建一个工厂用于定制鞋子,叫做ShootesFactory类,继续用静态代理方式的话,我们可以再创建一个定制鞋子的接口,让实现类ShootesFactory和代理类都去继承该接口

但是这种静态代理的方法实际使用时就会暴露它的缺点:调用不同的方法都需要在代理对象中创建对象,静态代理需要为每个被代理类都生成对应的代理类,在编译时需要处理大量的代码,这会导致编译时间增加,显然这种方法会降低开发的效率,基于此问题设计出了一种更为方便快捷的代理模式,也就是动态代理。
动态代理类
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DyProxy implements InvocationHandler {
//被代理的对象
//代理对象如何得知自己代理的是哪个目标类
//这里这样写其实是让用户告诉我,我要代理谁
private Object o ;
public DyProxy(Object o){
this.o = o;
}
//2.获取目标类的接口,要知道自己代理的核心方法是什么
public Object getProxyInterFace(){
return Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(),this);
}
//知道了接口----》变相得知了自己要代理的核心方法:
//invoke方法是利用反射的方式获取到了要代理的核心方法
//1.Object:jdk创建的代理类,无需赋值
//2.Method:目标类当中的方法,jdk提供,无需赋值
//3.Object[]:目标类当中的方法的参数,jdk提供,无需赋值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
FrontService();
method.invoke(o,args);
endService();
return null;
}
//前置服务
public void FrontService() {
System.out.println("根据您的需求进行市场调研");
}
//前置服务
public void endService() {
System.out.println("为您提供一条龙的包办服务");
}
}
在上述代码中,实现类是以代理对象构造器参数的形式传入的,在代理类中有获取目标类接口的方法,获取到目标类接口之后通过代理类中的invoke方法反射调用实现类中的invoke方法,这种方式有以下优点:
- 动态代理的接口是靠根据目标类获取的,而在静态代理中是手动添加的
- 动态代理在添加新的类时不需要改变本身代码
动态代理的应用场景
- AOP(面向切面编程):动态代理是实现 AOP 的核心技术之一。通过动态代理,可以在不修改业务逻辑的情况下,将一些通用的横切关注点(如日志记录、事务管理、权限验证等)织入到业务方法中,提高代码的可维护性和可复用性。
- 远程方法调用(RMI):在分布式系统中,动态代理可以用于实现远程方法调用。客户端通过代理对象调用远程服务的方法,代理对象负责处理网络通信和序列化等细节,使得客户端就像调用本地方法一样调用远程方法。
- 缓存代理:可以使用动态代理实现缓存功能。在调用目标方法之前,先检查缓存中是否存在结果,如果存在则直接返回缓存结果,否则调用目标方法并将结果存入缓存。
比较 | 静态代理 | 动态代理 |
---|---|---|
定义时机 | 在编译期间就已经确定代理类和被代理类的关系,代理类的代码是手动编写或者通过工具生成的,在程序运行前就已经存在于代码中 | 在程序运行时动态生成代理类和代理对象,无需手动编写代理类的代码,而是利用反射机制在运行时创建代理类的字节码并加载到 JVM 中 |
代码复杂度 | 需要为每个被代理类手动创建对应的代理类,如果被代理类的方法较多,或者有多个被代理类,会导致代码量大幅增加,且存在大量重复代码,使代码变得臃肿,可维护性和可读性降低 | 无需手动编写代理类,代码量相对较少,结构更简洁。尤其是在处理多个被代理类或方法时,优势更加明显,提高了代码的可维护性和开发效率 |
灵活性 | 灵活性较差,当被代理类的接口发生变化(如增加、删除方法)时,代理类需要进行相应的修改。同时,代理逻辑在编译时就已经确定,运行期间无法动态调整 | 具有很高的灵活性,可以在运行时根据需要动态地为不同的目标对象创建代理对象,并且可以动态调整代理逻辑。即使被代理类的接口发生变化,也不需要修改代理类的代码 |
可扩展性 | 可扩展性受限,当需要为代理类添加新的功能时,可能需要在多个代理类中都进行修改,工作量大且容易出错 | 易于扩展,通过修改调用处理器(如 JDK 动态代理的 InvocationHandler 或 CGLIB 动态代理的 MethodInterceptor )的逻辑,就可以方便地为代理对象添加新的功能,无需修改大量代码 |
性能 | 由于代理类和被代理类的关系在编译时就已经确定,方法调用直接指向代理类的方法,因此在方法调用时性能较高,没有额外的反射开销 | 在创建代理对象时,需要利用反射机制生成代理类的字节码并加载到 JVM 中,创建过程有一定性能开销。在方法调用时,JDK 动态代理使用反射调用目标方法,也会带来一定的性能损耗;不过 CGLIB 动态代理通过继承实现,方法调用性能相对较好 |
适用场景 | 适用于被代理类数量较少、接口稳定且不需要频繁修改代理逻辑的场景 | 适用于需要在运行时动态改变代理逻辑、处理多个不同类型的被代理对象,或者实现 AOP 等场景 |