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和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.Proxyjava.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等众多优秀框架都大量使用了动态代理技术,理解代理模式对于掌握这些框架的原理至关重要。

相关推荐
紫荆鱼3 小时前
设计模式-代理模式(Proxy)
c++·后端·设计模式·代理模式
helloworddm3 小时前
Orleans 自定义二进制协议在 TCP 上层实现的完整过程
java·网络协议·tcp/ip
超级大只老咪4 小时前
蓝桥杯知识点大纲(JavaC组)
java·算法·蓝桥杯
Yiii_x4 小时前
如何使用IntelliJ IDEA进行Java编程
java·课程设计·ai编程
阿杰AJie4 小时前
如何在程序中避免出现大量if和case
java·后端
摇滚侠5 小时前
Spring Boot3零基础教程,云服务停机不收费,笔记71
java·spring boot·笔记
豐儀麟阁贵5 小时前
5.5类的主方法
java·开发语言
不光头强5 小时前
maven进阶
java·maven
智海观潮5 小时前
聊聊Spark的分区
java·大数据·spark