在 Java 编程领域,代理模式是一种常见且重要的设计模式。它通过引入代理对象,实现对目标对象的访问控制和功能增强。Java 中的代理主要分为静态代理和动态代理,它们在实现方式和应用场景上各有特点。本文将深入探讨 Java 代理的相关知识,重点分析静态代理与动态代理的区别,并详细讲解动态代理的原理与实现。
一、代理模式概述
代理模式的核心思想是为目标对象提供一个代理对象,由代理对象控制对目标对象的引用。其主要目的有两个:一是控制访问,通过代理对象间接访问目标对象,避免直接访问目标对象给系统带来不必要的复杂性;二是功能增强,借助代理对象对原有业务进行功能扩展。
例如,在一个转账业务场景中,目标对象负责具体的转账操作,而代理对象可以在转账前后增加日志记录、权限验证等功能,从而对转账业务进行增强。
二、静态代理
(一)静态代理的实现方式
静态代理通常需要定义一个目标类的接口,目标类和代理类都实现该接口。代理类持有目标类的引用,在代理类的方法中调用目标类的相应方法,并可以在调用前后添加额外的逻辑。
以下是一个简单的示例:
java
// 目标类接口
interface TransAction {
void transfer();
}
// 目标类
class TransActionImpl implements TransAction {
@Override
public void transfer() {
System.out.println("执行转账操作");
}
}
// 代理类
class TransActionProxy implements TransAction {
private TransAction target;
public TransActionProxy(TransAction target) {
this.target = target;
}
@Override
public void transfer() {
System.out.println("权限验证");
target.transfer();
System.out.println("记录转账日志");
}
}
在上述代码中,TransAction
是目标类接口,TransActionImpl
是目标类,实现了转账操作。TransActionProxy
是代理类,持有目标类的引用,并在transfer
方法中添加了权限验证和日志记录的功能。
(二)静态代理的缺点
静态代理虽然实现简单,但存在明显的缺点。当目标类增多时,需要为每个目标类创建对应的代理类,这会导致代理类数量急剧增加,造成代理关系混乱,代码维护成本升高。
三、动态代理
(一)动态代理的概念与优势
动态代理是在运行时动态地生成代理类,而不是像静态代理那样在编译时就确定代理类。它可以更灵活地处理多个目标类,避免了静态代理中代理类过多的问题。
(二)JDK 动态代理的实现
JDK 提供了动态代理的支持,主要涉及java.lang.reflect.Proxy
类和InvocationHandler
接口。
以下是一个使用 JDK 动态代理的示例:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标类接口
interface Service {
void execute();
}
// 目标类
class ServiceImpl implements Service {
@Override
public void execute() {
System.out.println("执行服务操作");
}
}
// 调用处理器
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@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;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
Service target = new ServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(target);
Service proxy = (Service) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
proxy.execute();
}
}
在这个示例中:
- 定义了
Service
接口和实现该接口的ServiceImpl
目标类。 - 创建了
MyInvocationHandler
类实现InvocationHandler
接口,在invoke
方法中添加了前置处理和后置处理的逻辑,并通过method.invoke
方法调用目标对象的方法。 - 使用
Proxy.newProxyInstance
方法动态生成代理对象,该方法接受三个参数:目标对象的类加载器、目标对象实现的接口数组以及调用处理器。
(三)CGLIB 动态代理
除了 JDK 动态代理,CGLIB 也是一种常用的动态代理实现方式。CGLIB 通过继承目标类来生成代理类,因此不需要目标类实现接口。
以下是一个简单的 CGLIB 动态代理示例:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class TargetClass {
public void operation() {
System.out.println("执行目标操作");
}
}
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB前置处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB后置处理");
return result;
}
}
public class CglibProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback(new MyMethodInterceptor());
TargetClass proxy = (TargetClass) enhancer.create();
proxy.operation();
}
}
在 CGLIB 动态代理中,通过Enhancer
类设置目标类和回调函数(实现MethodInterceptor
接口),然后生成代理对象。
(四)JDK 动态代理与 CGLIB 动态代理的比较
- JDK 动态代理:要求目标对象实现接口,代理类实现相同接口,通过反射机制调用目标方法,适用于有接口的情况。
- CGLIB 动态代理:通过继承目标类生成代理类,无需目标类实现接口,性能相对较好,但无法代理
final
类和final
方法。
四、静态代理与动态代理的区别
(一)实现方式
静态代理在编译时就确定了代理类,代理类与目标类实现相同的接口;动态代理则在运行时动态生成代理类,JDK 动态代理基于接口实现,CGLIB 动态代理基于类的继承。
(二)灵活性
静态代理的代理类是固定的,每个目标类都需要一个对应的代理类;动态代理更加灵活,一个代理类可以处理多个目标类,适用于目标类数量较多或变化频繁的场景。
(三)代码维护
静态代理随着目标类的增加,代理类也会增多,导致代码维护困难;动态代理通过统一的代理生成逻辑,减少了代码量,便于维护。
五、代理模式的应用场景
(一)AOP 编程
在面向切面编程(AOP)中,代理模式被广泛应用于实现横切关注点,如日志记录、事务管理、权限控制等。通过代理对象在目标方法调用前后插入相应的逻辑,实现功能的增强和分离。
(二)远程调用
在远程方法调用(RPC)中,代理模式可以用于创建远程对象的本地代理,隐藏远程调用的细节,使调用者感觉像是在调用本地对象。
(三)缓存代理
代理对象可以实现缓存功能,当目标对象的方法被频繁调用时,代理对象可以缓存方法的返回结果,避免重复调用目标对象,提高系统性能。
六、总结
Java 代理模式为我们提供了一种强大的编程手段,通过静态代理和动态代理的不同实现方式,可以满足不同场景下的需求。静态代理简单直观,适用于目标类较少的情况;动态代理则更加灵活,能够应对复杂多变的业务场景。深入理解代理模式的原理和应用,有助于我们编写出更加灵活、可维护和高性能的 Java 程序。在实际开发中,根据具体需求选择合适的代理方式,能够提升代码的质量和可扩展性。希望本文能帮助你更好地掌握 Java 代理的相关知识。如果你在学习和实践过程中有任何疑问,欢迎在评论区留言交流。
public interface ByClothes {
public abstract void clothes(String size);
}
/**
* 卖衣服的工厂
* 目标类
*/
public class ClothesFactory implements ByClothes{
public void clothes(String size){
System.out.println("已经给您定制好了一件大小为"+size+"的衣服");
}
public void 机器处理(){
}
public void 原材料(){}
}
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("为您提供一条龙的包办服务");
}
}
public class TestProxy {
public static void main(String[] args) {
ClothesFactory clothesFactory = new ClothesFactory();
ByClothes clothes = (ByClothes) new DyProxy(clothesFactory).getProxyInterFace();
clothes.clothes("XXXL");
}
}