什么是代理模式
代理模式是一种常见的设计模式,它通过创建一个代理对象来控制对原始对象的访问。这种模式在现实生活中有很多类比,比如房产中介、律师代理等,他们都代表另一个人或组织执行某些功能。
在软件开发中,代理模式主要用于:
- 控制对原始对象的访问
- 添加额外的功能(如日志、权限检查)
- 延迟创建开销大的对象
- 提供远程访问的本地代表
静态代理
静态代理的基本概念
静态代理是通过显式创建一个代理类来实现的,这个代理类和原始类实现相同的接口。代理对象在调用原始对象方法的前后,可以添加额外的处理逻辑。
静态代理的实现示例
假设我们有一个用户数据库操作的场景,首先定义接口:
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和setter
public 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 {
@Override
public void save(User user) {
System.out.println("YA33: 保存用户信息到数据库 - " + user.getName());
// 实际的数据库保存逻辑
}
@Override
public User findById(int id) {
System.out.println("YA33: 从数据库查询用户ID - " + id);
// 实际的数据库查询逻辑
return new User(id, "用户" + id, "user" + id + "@example.com");
}
@Override
public 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;
}
@Override
public 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;
}
}
@Override
public 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;
}
@Override
public 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() {
@Override
public 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();
}
@Override
public 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等众多优秀框架都大量使用了动态代理技术,理解代理模式对于掌握这些框架的原理至关重要。