java设计模式七、代理模式
什么是代理模式
代理模式是一种常见的设计模式,它通过创建一个代理对象来控制对原始对象的访问。这种模式在现实生活中有很多类比,比如房产中介、律师代理等,他们都代表另一个人或组织执行某些功能。
在软件开发中,代理模式主要用于:
- 控制对原始对象的访问
- 添加额外的功能(如日志、权限检查)
- 延迟创建开销大的对象
- 提供远程访问的本地代表
静态代理
静态代理的基本概念
静态代理是通过显式创建一个代理类来实现的,这个代理类和原始类实现相同的接口。代理对象在调用原始对象方法的前后,可以添加额外的处理逻辑。
静态代理的实现示例
假设我们有一个用户数据库操作的场景,首先定义接口:
java
// 用户DAO接口
public interface UserDao {void save(User user);User findById(int id);void delete(int id);
}// 用户实体类
public class User {private int id;private String name;private String email;// 构造方法、getter和setterpublic User(int id, String name, String email) {this.id = id;this.name = name;this.email = email;}// getter和setter方法省略...
}
接下来是实现类:
java
// 具体的用户DAO实现
public class UserDaoImpl implements UserDao {@Overridepublic void save(User user) {System.out.println("YA33: 保存用户信息到数据库 - " + user.getName());// 实际的数据库保存逻辑}@Overridepublic User findById(int id) {System.out.println("YA33: 从数据库查询用户ID - " + id);// 实际的数据库查询逻辑return new User(id, "用户" + id, "user" + id + "@example.com");}@Overridepublic void delete(int id) {System.out.println("YA33: 从数据库删除用户ID - " + id);// 实际的数据库删除逻辑}
}
然后是静态代理类:
java
// 静态代理类
public class UserDaoStaticProxy implements UserDao {private UserDao target;public UserDaoStaticProxy(UserDao target) {this.target = target;}@Overridepublic void save(User user) {System.out.println("YA33: [代理] 开始事务");try {target.save(user);System.out.println("YA33: [代理] 提交事务");} catch (Exception e) {System.out.println("YA33: [代理] 回滚事务");throw e;}}@Overridepublic User findById(int id) {System.out.println("YA33: [代理] 记录查询日志 - 查询用户ID: " + id);long startTime = System.currentTimeMillis();User user = target.findById(id);long endTime = System.currentTimeMillis();System.out.println("YA33: [代理] 查询耗时: " + (endTime - startTime) + "ms");return user;}@Overridepublic void delete(int id) {System.out.println("YA33: [代理] 权限检查");// 模拟权限检查if (checkPermission()) {target.delete(id);System.out.println("YA33: [代理] 删除操作完成");} else {System.out.println("YA33: [代理] 权限不足,删除操作被拒绝");}}private boolean checkPermission() {// 模拟权限检查逻辑return true;}
}
静态代理的使用
java
// 测试静态代理
public class StaticProxyTest {public static void main(String[] args) {// 创建目标对象UserDao target = new UserDaoImpl();// 创建代理对象UserDao proxy = new UserDaoStaticProxy(target);// 使用代理对象User user = new User(1, "YA33", "ya33@example.com");proxy.save(user);User foundUser = proxy.findById(1);System.out.println("找到用户: " + foundUser.getName());}
}
静态代理的优缺点
优点:
- 结构简单,易于理解和实现
- 可以在不修改目标对象的情况下添加功能
- 符合开闭原则
缺点:
- 如果接口增加方法,代理类和目标类都需要修改
- 每个需要代理的类都需要创建一个对应的代理类,导致类数量增加
- 代码重复,如果多个类需要相同的增强功能,需要在每个代理类中重复编写
动态代理
动态代理的基本概念
动态代理在运行时动态生成代理对象,不需要像静态代理那样为每个类显式编写代理类。Java提供了两种主要的动态代理方式:JDK动态代理和CGLIB动态代理。
JDK动态代理
JDK动态代理原理
JDK动态代理基于接口实现,使用Java反射机制在运行时创建代理对象。核心类是java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
JDK动态代理实现
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 创建动态代理对象
public class JdkProxyFactory {// 维护一个目标对象private Object target;public JdkProxyFactory(Object target) {this.target = target;}// 给目标对象生成代理对象public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("YA33: [JDK代理] 开始执行方法: " + method.getName());// 方法执行前增强preProcess(method, args);long startTime = System.currentTimeMillis();// 执行目标对象方法Object returnValue = method.invoke(target, args);long endTime = System.currentTimeMillis();// 方法执行后增强postProcess(method, args, returnValue, endTime - startTime);System.out.println("YA33: [JDK代理] 方法执行完成: " + method.getName());return returnValue;}private void preProcess(Method method, Object[] args) {System.out.println("YA33: [JDK代理] 前置处理 - 方法: " + method.getName());if (args != null) {for (int i = 0; i < args.length; i++) {System.out.println("YA33: [JDK代理] 参数" + i + ": " + args[i]);}}}private void postProcess(Method method, Object[] args, Object returnValue, long duration) {System.out.println("YA33: [JDK代理] 后置处理 - 方法: " + method.getName());System.out.println("YA33: [JDK代理] 执行耗时: " + duration + "ms");if (returnValue != null) {System.out.println("YA33: [JDK代理] 返回值: " + returnValue);}}});}
}
JDK动态代理使用示例
java
// 测试JDK动态代理
public class JdkProxyTest {public static void main(String[] args) {// 创建目标对象UserDao target = new UserDaoImpl();// 创建代理工厂JdkProxyFactory factory = new JdkProxyFactory(target);// 获取代理对象UserDao proxy = (UserDao) factory.getProxyInstance();// 使用代理对象User user = new User(1, "YA33", "ya33@example.com");proxy.save(user);User foundUser = proxy.findById(1);System.out.println("找到用户: " + foundUser.getName());proxy.delete(1);}
}
CGLIB动态代理
CGLIB动态代理原理
CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它通过继承目标类并重写方法来实现代理,因此不需要目标类实现接口。
CGLIB动态代理实现
首先需要添加CGLIB依赖:
xml
<!-- Maven依赖 -->
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
然后实现CGLIB代理:
java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;// CGLIB代理工厂
public class CglibProxyFactory implements MethodInterceptor {// 维护目标对象private Object target;public CglibProxyFactory(Object target) {this.target = target;}// 给目标对象创建代理对象public Object getProxyInstance() {// 1. 工具类Enhancer en = new Enhancer();// 2. 设置父类en.setSuperclass(target.getClass());// 3. 设置回调函数en.setCallback(this);// 4. 创建子类(代理对象)return en.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("YA33: [CGLIB代理] 开始执行方法: " + method.getName());// 方法执行前增强preProcess(method, args);long startTime = System.currentTimeMillis();// 执行目标对象的方法Object returnValue = method.invoke(target, args);long endTime = System.currentTimeMillis();// 方法执行后增强postProcess(method, args, returnValue, endTime - startTime);System.out.println("YA33: [CGLIB代理] 方法执行完成: " + method.getName());return returnValue;}private void preProcess(Method method, Object[] args) {System.out.println("YA33: [CGLIB代理] 前置处理 - 方法: " + method.getName());if ("save".equals(method.getName())) {System.out.println("YA33: [CGLIB代理] 数据验证...");}if ("delete".equals(method.getName())) {System.out.println("YA33: [CGLIB代理] 权限验证...");}}private void postProcess(Method method, Object[] args, Object returnValue, long duration) {System.out.println("YA33: [CGLIB代理] 后置处理 - 方法: " + method.getName());System.out.println("YA33: [CGLIB代理] 执行耗时: " + duration + "ms");if ("findById".equals(method.getName()) && returnValue != null) {User user = (User) returnValue;System.out.println("YA33: [CGLIB代理] 查询结果: " + user.getName() + " (" + user.getEmail() + ")");}}
}
CGLIB动态代理使用示例
java
// 测试CGLIB动态代理
public class CglibProxyTest {public static void main(String[] args) {// 创建目标对象 - 注意这里不需要接口UserDaoImpl target = new UserDaoImpl();// 创建代理工厂CglibProxyFactory factory = new CglibProxyFactory(target);// 获取代理对象UserDaoImpl proxy = (UserDaoImpl) factory.getProxyInstance();// 使用代理对象User user = new User(1, "YA33", "ya33@example.com");proxy.save(user);User foundUser = proxy.findById(1);System.out.println("找到用户: " + foundUser.getName());}
}
静态代理与动态代理对比
实现方式对比
| 特性 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 实现方式 | 手动编写代理类 | 基于接口,使用Proxy类 | 基于继承,使用Enhancer类 |
| 是否需要接口 | 是 | 是 | 否 |
| 性能 | 较高 | 中等(反射调用) | 较高(方法调用优化) |
| 灵活性 | 低 | 高 | 高 |
| 依赖 | 无 | JDK自带 | 需要CGLIB库 |
应用场景
静态代理适用场景:
- 代理类较少,功能简单
- 需要明确控制代理逻辑
- 性能要求极高的场景
JDK动态代理适用场景:
- 目标对象实现了接口
- 需要代理多个类,且有相同接口
- AOP编程
CGLIB动态代理适用场景:
- 目标对象没有实现接口
- 需要高性能的代理
- Spring AOP默认使用方式
在MyBatis-Spring中的应用
在MyBatis-Spring框架中,动态代理被广泛应用于Mapper接口的实现。框架在启动时会为每个Mapper接口创建动态代理对象,当调用接口方法时,代理对象会将方法调用转换为SQL执行。
MyBatis Mapper代理示例
java
// Mapper接口
public interface UserMapper {User selectUserById(int id);void insertUser(User user);void updateUser(User user);void deleteUser(int id);
}// 使用示例
@Autowired
private UserMapper userMapper; // 实际上是一个动态代理对象public void testMapper() {User user = userMapper.selectUserById(1);System.out.println("YA33: 查询到用户: " + user.getName());
}
总结
代理模式是软件开发中非常重要的设计模式,它通过引入代理对象来控制对原始对象的访问。静态代理简单直观但灵活性差,动态代理则提供了更大的灵活性。
- 静态代理:适用于代理类较少、功能明确的场景
- JDK动态代理:基于接口,适用于目标类已实现接口的场景
- CGLIB动态代理:基于继承,适用于目标类没有接口的场景,性能较高
在实际开发中,Spring框架、MyBatis等众多优秀框架都大量使用了动态代理技术,理解代理模式对于掌握这些框架的原理至关重要。
