在 Java 开发中,代理模式是一种非常重要的设计模式,它通过引入代理对象来控制对目标对象的访问,从而实现额外功能的增强。
一、代理模式的基本概念
代理模式的核心思想是:通过一个代理对象来间接访问目标对象,在不修改目标对象代码的前提下,为其增加额外功能。这种设计模式主要包含三个角色:
- 抽象主题(Subject):定义代理类和目标类共同实现的接口,规定了要实现的业务方法
- 目标对象(Real Subject):真正执行业务逻辑的类
- 代理对象(Proxy):实现抽象主题接口,内部包含目标对象的引用,在调用目标方法前后可以添加额外操作
二、静态代理的实现与分析
静态代理是指在编译期就已经确定代理类的形式,代理类和目标类的关系在编译时就已固定。
1. 静态代理的代码实现
以服装工厂和动物工厂的代理为例,我们先定义抽象主题接口:
// 服装购买接口(抽象主题)
public interface ByClothes {public void clothes(String size);
}// 动物购买接口(抽象主题)
public interface ByAnimal {public void animals(String name);
}
然后实现目标对象类:
// 服装工厂(目标对象)
public class ClothesFactory implements ByClothes {@Overridepublic void clothes(String size) {System.out.println("生产尺寸为" + size + "的服装");}
}// 动物工厂(目标对象)
public class AnimalFactory implements ByAnimal {@Overridepublic void animals(String name) {System.out.println("生产名为" + name + "的小动物");}
}
最后创建静态代理类:
// 服装代理类
public class ProxyClothes implements ByClothes {// 持有目标对象引用public ClothesFactory factory = new ClothesFactory();@Overridepublic void clothes(String size) {System.out.println("售前服务"); // 额外功能factory.clothes(size); // 调用目标方法System.out.println("售后服务"); // 额外功能}
}// 动物代理类
public class ProxyAnimal implements ByAnimal {// 持有目标对象引用public AnimalFactory animalFactory = new AnimalFactory();@Overridepublic void animals(String name) {System.out.println("售前处理"); // 额外功能animalFactory.animals(name); // 调用目标方法System.out.println("售后处理"); // 额外功能}
}
2. 静态代理的优缺点
优点:
- 实现简单,易于理解
- 在不修改目标对象的前提下扩展功能
- 保护目标对象,降低直接访问风险
缺点:
- 代理类与目标类一一对应,接口变动时所有代理类都需修改
- 代码冗余,每个目标类都需要创建对应的代理类
- 编译期确定代理关系,灵活性差
三、动态代理的实现与分析
动态代理是指在程序运行时动态生成代理类,无需手动编写代理类代码,大大提高了代码的灵活性和可维护性。Java 通过java.lang.reflect
包中的Proxy
和InvocationHandler
实现动态代理。
1. 基于jdk的动态代理的代码实现
// 动态代理处理器
public class ProxyAll implements InvocationHandler {// 目标对象(可以是任何实现了接口的类)public Object factory;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强System.out.println("售前服务");// 调用目标对象的方法method.invoke(factory, args);// 后置增强System.out.println("售后服务");return null;}// 创建代理对象的方法public Object getProxyInstance() {// 参数说明:// 1. 目标对象的类加载器// 2. 目标对象实现的接口// 3. 当前InvocationHandler实例return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);}
}
使用动态代理:
public class Test {public static void main(String[] args) {// 创建目标对象ClothesFactory clothesFactory = new ClothesFactory();AnimalFactory animalFactory = new AnimalFactory();// 创建动态代理处理器ProxyAll proxyAll = new ProxyAll();// 为服装工厂创建代理proxyAll.factory = clothesFactory;ByClothes clothesProxy = (ByClothes)proxyAll.getProxyInstance();clothesProxy.clothes("XL"); // 调用代理方法// 为动物工厂创建代理proxyAll.factory = animalFactory;ByAnimal animalProxy = (ByAnimal)proxyAll.getProxyInstance();animalProxy.animals("小鸭"); // 调用代理方法}
}
2. 基于jdk的动态代理的工作原理
- 通过
Proxy.newProxyInstance()
方法在运行时生成代理类的字节码 - 代理类实现了目标对象所实现的所有接口
- 当调用代理对象的方法时,会自动转发到
InvocationHandler
的invoke()
方法 - 在
invoke()
方法中实现对目标方法的增强(前置处理、后置处理等)
3. 基于jdk的动态代理的优缺点
优点:
- 无需手动编写代理类,减少代码冗余
- 一个代理处理器可以代理多个目标对象,提高代码复用性
- 运行时动态生成代理,灵活性高,易于扩展
缺点:
- 只能代理实现了接口的类
- 实现相对复杂,理解难度较大
- 性能略低于静态代理(因为涉及反射调用)
4. 基于CGLIB的动态代理
CGLIB 是一个第三方代码生成类库,需要引入依赖(以 Maven 为例):
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
1. 代码实现步骤
(1)定义目标类(无需实现接口)
package 代理;// 无需实现接口的目标类
public class FoodFactory {public void produce(String foodName) {System.out.println("生产食品:" + foodName);}
}
(2)实现 CGLIB 回调接口(MethodInterceptor)
package 代理;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;// CGLIB代理拦截器
public class CglibProxy implements MethodInterceptor {// 目标对象private Object target;public CglibProxy(Object target) {this.target = target;}// 生成代理对象public Object getProxyInstance() {// 1. 创建Enhancer对象(CGLIB的核心类)net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();// 2. 设置父类(目标类)enhancer.setSuperclass(target.getClass());// 3. 设置回调对象(当前拦截器)enhancer.setCallback(this);// 4. 生成代理子类并返回return enhancer.create();}// 拦截方法调用(类似JDK动态代理的invoke方法)@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 前置增强System.out.println("CGLIB-售前检查");// 调用目标方法(通过方法代理调用父类方法)Object result = method.invoke(target, args);// 后置增强System.out.println("CGLIB-售后保障");return result;}
}
(3)测试 CGLIB 代理
package 代理;public class CglibTest {public static void main(String[] args) {// 创建目标对象FoodFactory foodFactory = new FoodFactory();// 创建CGLIB代理对象FoodFactory proxy = (FoodFactory) new CglibProxy(foodFactory).getProxyInstance();// 调用代理方法proxy.produce("面包");}
}
(4)输出结果
CGLIB-售前检查
生产食品:面包
CGLIB-售后保障
2. CGLIB 动态代理的工作原理
- 继承机制:通过
Enhancer
类生成目标类的子类(代理类),并重写目标方法 - 方法拦截:当调用代理对象的方法时,会被
MethodInterceptor
的intercept
方法拦截 - 字节码生成:底层通过 ASM 框架直接操作字节码,动态生成代理类的 class 文件
3. CGLIB 的优缺点
优点:
- 无需目标类实现接口,适用范围更广
- 基于继承实现,避免了 JDK 动态代理对接口的依赖
- 性能通常优于 JDK 动态代理(因为避免了反射调用的部分开销)
缺点:
- 不能代理
final
类或final
方法(因为无法继承) - 需要引入第三方依赖(JDK 动态代理是 JDK 自带的)
- 生成代理类的过程比 JDK 动态代理更复杂
四、三种代理方式的全面对比
特性 | 静态代理 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|---|
代理原理 | 手动编写代理类实现接口,持有目标对象引用 | 运行时生成代理类实现目标接口,通过 InvocationHandler 增强 | 运行时生成目标类的子类,通过 MethodInterceptor 增强 |
目标类要求 | 需实现接口 | 必须实现接口 | 无需实现接口(但不能是 final 类) |
代理类生成时机 | 编译期 | 运行期 | 运行期 |
灵活性 | 低(接口变动需修改代理类) | 中(可代理任意接口实现类) | 高(可代理任意非 final 类) |
性能 | 高(直接调用,无额外开销) | 中(反射调用有一定开销) | 高(字节码增强,接近直接调用) |
依赖 | JDK 自带,无额外依赖 | JDK 自带(java.lang.reflect) | 需引入 CGLIB 和 ASM 依赖 |
适用场景 | 接口稳定、代理类少的简单场景 | 基于接口的代理(如 Spring AOP 默认选择) | 无接口的类代理(如 Spring 中对没有接口的 Bean 使用 CGLIB) |
代码侵入性 | 高(需手动编写代理类) | 低(无需编写代理类) | 低(无需编写代理类) |
五、代理模式的应用场景
- 日志记录:在方法调用前后记录日志信息
- 权限控制:在调用目标方法前验证权限
- 性能监控:统计方法执行时间
- 事务管理:在数据库操作前后进行事务控制
- 缓存实现:对方法返回结果进行缓存处理
- 远程调用:如 RPC 框架中通过代理实现远程服务调用