一、什么是代理
在 Java 开发中,Java代理(Proxy)是Java编程语言中一个非常重要的概念,通过引入一个代理类来间接访问目标对象,在不修改原有目标类代码的前提下,增加或修改目标类的行为,将复杂的 Java 代码分解成更易处理的部分,同时也能在不同环境中独立运行。
二、理解Java代理的基本概念
Java代理(Proxy)是指能够从 Java 代码中分离出来并执行特定功能的其他程序。这些代理通常使用代理类来实现,代理类负责管理 Java 代码的运行,并将结果返回给主程序,举个生活中的例子,某皮革厂老板带着小姨子跑路了,连累很多企业主生活揭不开锅,被迫去申请仲裁,法院会委派一位代理律师去全权负责处理该事宜,这其实就用到了代理模式,再比如租房,找中介,中介会代理房东带你看房,签订合同等,也是用到了代理模式。
三、代理模式
代理模式(Proxy Pattern)是软件开发中常用的设计模式之一,它通过创建代理对象来控制对原始对象的访问。这种模式在面向切面编程(AOP)、远程方法调用(RMI)、事务管理等场景中发挥着重要作用。
![](https://i-blog.csdnimg.cn/direct/4705ae10bbd54685bd2a9ddb210853de.png)
-
Subject(抽象角色)
定义代理类和真实主体的公共对外方法,也是代理类代理真实主体的方法
-
Proxy(代理角色/代理对象)
代理角色内部包含了对真实角色的引用,从而可以在任何时候操作真实角色对象,包括在真实角色处理前后做一些预处理或善后工作
-
RealSubject(真实角色/目标对象)
真实角色是业务逻辑的具体执行者,真正实现业务逻辑的类
四、代理模式的两种实现
1.静态代理
静态代理是指在程序运行前就已经存在代理类的字节码文件,代理类和目标类的关系在运行前就已确定,且代理类通常是手工编写的,但是其实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
静态代理的两种实现方式
基于接口编程
java">//抽象主题类
public interface UserService {public void select(); public void update();
}//委托类
public class UserServiceImpl implements UserService {// public void select() { System.out.println("查询 selectById");}public void update() {System.out.println("更新 update");}
}//代理类 通过静态代理对功能进行增强,在调用select和update之前记录一些日志
public class UserServiceProxy implements UserService {private UserService target; // 被代理的对象//在构造函数中,通过传入的参数`target`来初始化`target`成员变量。//这样,代理类就可以将实际的业务逻辑委托给被代理的对象来完成。public UserServiceProxy(UserService target) {this.target = target;}public void select() {before();target.select(); // 这里才实际调用真实主题角色的方法after();}public void update() {before();target.update(); // 这里才实际调用真实主题角色的方法after();}private void before() { // 在执行方法之前执行System.out.println(String.format("log start time [%s] ", new Date()));}private void after() { // 在执行方法之后执行System.out.println(String.format("log end time [%s] ", new Date()));}
}
基于继承
java">public class UserServiceProxy extends UserServiceImpl {public void select() {before();super.select(); // 这里才实际调用真实主题角色的方法after();}public void update() {before();super.update(); // 这里才实际调用真实主题角色的方法after();}private void before() { // 在执行方法之前执行System.out.println(String.format("log start time [%s] ", new Date()));}private void after() { // 在执行方法之后执行System.out.println(String.format("log end time [%s] ", new Date()));}
}
静态代理优点
- 封装性强:代理类对真实角色进行封装,使真实角色处理的业务更加纯粹,不再去关注一些公共的事情,只需要和代理类交互
- 扩展性强:通过代理类,可以在不修改真实对象的情况下,对其进行功能扩展或增强
- 访问控制:通过代理类可以控制客户端对真实对象的访问,实现访问权限的管理,例如在访问某些敏感方法前后进行权限验证或日志记录静态代理缺点
- 编码复杂:每一个需要代理的对象都需要单独编写代理类,如果真实对象很多,代理类的数量也会很多,增加了系统的复杂度
- 灵活性差:由于静态代理在编译期间就已经确定了代理对象和真实对象的关系,因此无法在运行时动态改变代理对象,灵活性较差
- 局限性:静态代理只能代理固定类型的对象,无法代理不同类型的对象,因此对于不同类型的真实对象需要编写不同的代理类
- 难维护:当真实对象的接口发生变化时,代理类的接口也需要相应地进行修改,维护起来相对比较麻烦
2.动态代理
动态代理是指在程序运行时动态地创建代理类,无需手动编写代理类的源代码。动态代理主要依靠JDK的反射(Reflection)API来实现。
动态代理的两种实现方式
基于接口实现的动态代理——JDK动态代理
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
实现原理:
java">public class JdkDynamicProxy implements InvocationHandler {private Object target;public JdkDynamicProxy(Object target) {this.target = target;}public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK代理前置处理");Object result = method.invoke(target, args);System.out.println("JDK代理后置处理");return result;}
}
使用示例:
java">UserService service = new UserServiceImpl();
UserService proxy = (UserService) new JdkDynamicProxy(service).getProxy();
proxy.saveUser();
基于继承实现的动态代理——CGLIB
实现原理:
java">public class CglibProxy implements MethodInterceptor {public Object getProxy(Class<?> clazz) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(this);return enhancer.create();}@Overridepublic 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;}
}
代理机制对比分析
特性 | 静态代理 | JDK动态代理 | CGLIB代理 |
---|---|---|---|
实现方式 | 手动编码 | 反射机制 | 字节码增强 |
代理目标 | 接口 | 接口 | 类 |
性能 | 高 | 中等 | 较高(优化后) |
依赖 | 无 | JDK原生 | 第三方库 |
方法过滤 | 不支持 | 支持 | 支持 |
初始化消耗 | 编译时 | 运行时 | 运行时 |
动态代理优点
- 灵活性高:与静态代理相比,动态代理可以在运行时动态地生成代理类,无需提前编写大量的代理类,更加灵活
- 代码简洁:由于动态代理是在运行时生成的,因此可以大大减少代码量,使代码更加简洁、清晰
- 维护成本低:由于动态代理不需要为每个被代理的类编写单独的代理类,因此当原始类的接口发生变化时,对代码的影响较小,维护成本低
- 适用范围广:动态代理可以代理任意实现了接口的类,不限于特定的类或接口类型,因此适用范围更广泛
动态代理缺点
- 性能低:相比静态代理,动态代理在运行时需要动态生成代理类,因此可能会稍微降低程序的运行效率
- 复杂度高:动态代理涉及到反射机制和动态生成字节码等技术,因此相对于静态代理而言,实现和理解的难度较高
- 不支持对类的直接代理:动态代理只能代理实现了接口的类,无法直接代理类,这在某些情况下可能会限制其使用
- 难以调试:动态代理生成的代理类通常是在运行时动态生成的字节码,因此在调试时可能会增加一定的难度,不如静态代理那样直观
五、代理在Spring框架中的应用
AOP切面编程
- 切面(Aspect):在AOP中,切面是一个横切关注点的模块化,如日志记录、事务管理等。切面可以横切多个类或方法,将横切关注点从业务逻辑中分离出来
- 通知(Advice):通知是切面在特定连接点(如方法调用前后)执行的动作。Spring支持多种类型的通知,如前置通知、后置通知、环绕通知等
- 代理实现:在Spring AOP中,代理是实现切面的关键机制。当方法被调用时,代理会拦截方法调用,并根据配置执行相应的通知逻辑。如果目标对象实现了接口,Spring默认使用JDK动态代理;如果目标对象没有实现接口,则使用CGLIB动态代理
事务管理
- 事务:事务是一组要么全都执行成功,要么全都回滚的操作。事务管理对于确保数据一致性和完整性至关重要
- 声明式事务管理:Spring提供了声明式事务管理,允许开发者通过注解或XML配置来管理事务,而无需手动编写事务管理代码
- 代理实现:在声明式事务管理中,Spring使用代理来拦截方法调用,并在方法执行前后添加事务管理的逻辑。例如,在方法执行前开启事务,在方法执行后提交或回滚事务
使用Spring AOP和JDK动态代理的代码示例
java">import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.stereotype.Component;// 目标接口
public interface UserService {void saveUser(String username, String password);
}// 目标类实现
@Component
public class UserServiceImpl implements UserService {@Overridepublic void saveUser(String username, String password) {System.out.println("保存用户信息: " + username);}
}// 切面类
@Aspect
public class LoggingAspect {@Before("execution(* com.example.service.UserServiceImpl.saveUser(..))")public void logBefore() {System.out.println("执行saveUser方法前记录日志");}
}// 代理工厂类(模拟Spring AOP的代理创建过程)
public class AopProxyFactory {public static Object createProxy(Object target) {ProxyFactory proxyFactory = new ProxyFactory(target);// 添加切面逻辑// proxyFactory.addAdvice(new LoggingAdvice()); // 模拟代理过程return proxyFactory.getProxy();}
}// 客户端代码
public class Client {public static void main(String[] args) {UserService userService = new UserServiceImpl();UserService proxyUserService = (UserService) AopProxyFactory.createProxy(userService);proxyUserService.saveUser("testUser", "password123");}
}