深入理解代理模式(Proxy):静态代理、动态代理与AOP

目录

  • [1. 代理模式简介](#1. 代理模式简介)
  • [2. 静态代理](#2. 静态代理)
  • [3. 动态代理](#3. 动态代理)
    • [3.1 JDK动态代理](#3.1 JDK动态代理)
    • [3.2 CGLIB动态代理](#3.2 CGLIB动态代理)
  • [4. 面向切面编程(AOP)](#4. 面向切面编程(AOP))
  • [5. 实战示例](#5. 实战示例)
  • [6. 总结与最佳实践](#6. 总结与最佳实践)

1. 代理模式简介

代理模式是一种结构型设计模式,它允许我们提供一个代理来控制对其他对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。

代理模式的UML类图

<<interface>> Subject +request() RealSubject +request() Proxy -realSubject: RealSubject +request()

代理模式的主要角色

  • Subject(抽象主题):定义代理类和真实主题的共同接口
  • RealSubject(真实主题):定义代理所代表的真实对象
  • Proxy(代理):包含对真实主题的引用,从而可以操作真实主题

2. 静态代理

静态代理是最简单的代理模式实现方式。代理类在编译时就已经确定,在程序运行前就已经存在代理类的 .class 文件。

示例代码

java 复制代码
// 抽象主题接口
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

// 真实主题类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户: " + username);
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
}

// 静态代理类
public class UserServiceProxy implements UserService {
    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser(String username) {
        System.out.println("开始添加用户...");
        long startTime = System.currentTimeMillis();
        userService.addUser(username);
        long endTime = System.currentTimeMillis();
        System.out.println("添加用户完成,耗时:" + (endTime - startTime) + "ms");
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("开始删除用户...");
        long startTime = System.currentTimeMillis();
        userService.deleteUser(username);
        long endTime = System.currentTimeMillis();
        System.out.println("删除用户完成,耗时:" + (endTime - startTime) + "ms");
    }
}
plain 复制代码
开始添加用户...
添加用户完成,耗时:1ms
开始删除用户...
删除用户完成,耗时:1ms

介绍:静态代理通过代理类为目标方法添加了额外的功能(如记录执行时间),并且不需要修改目标类的代码。上述输出表示在添加用户和删除用户时,代理类能够控制方法执行并统计耗时。

静态代理的优缺点

优点:

  • 可以在不修改目标对象的前提下,实现对目标对象的功能扩展
  • 实现简单,容易理解

缺点:

  • 为每个服务都需要创建代理类,工作量大
  • 不易维护,一旦接口增加方法,所有代理类都需要修改

3. 动态代理

动态代理是在程序运行时,通过反射机制动态创建代理类的代理模式实现方式。

3.1 JDK动态代理

JDK动态代理只能代理实现了接口的类。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 动态代理处理器
public class LogHandler implements InvocationHandler {
    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始执行方法:" + method.getName());
        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long endTime = System.currentTimeMillis();
        System.out.println("方法执行完成,耗时:" + (endTime - startTime) + "ms");
        return result;
    }

    // 获取代理对象
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LogHandler(target)
        );
    }
}

// 使用示例
public class DynamicProxyDemo {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) LogHandler.getProxy(userService);
        proxy.addUser("张三");
        proxy.deleteUser("张三");
    }
}
plain 复制代码
开始执行方法:addUser
方法执行完成,耗时:1ms
开始执行方法:deleteUser
方法执行完成,耗时:1ms

介绍 :使用JDK动态代理,代理对象的行为由InvocationHandler接口的invoke方法决定。这里我们添加了日志记录功能,输出了执行方法的时间统计。

3.2 CGLIB动态代理

CGLIB可以代理没有实现接口的类,通过继承被代理类来实现代理。

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {
    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始执行方法:" + method.getName());
        long startTime = System.currentTimeMillis();
        Object result = proxy.invokeSuper(obj, args);
        long endTime = System.currentTimeMillis();
        System.out.println("方法执行完成,耗时:" + (endTime - startTime) + "ms");
        return result;
    }
}

// 使用示例
public class CglibProxyDemo {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) new CglibProxy(userService).getProxy();
        proxy.addUser("李四");
        proxy.deleteUser("李四");
    }
}
plain 复制代码
开始执行方法:addUser
方法执行完成,耗时:1ms
开始执行方法:deleteUser
方法执行完成,耗时:1ms

介绍:CGLIB动态代理通过继承目标类来生成代理类,它不要求目标类实现接口。类似于JDK动态代理,这里输出的日志同样记录了方法执行的时间。

4. 面向切面编程(AOP)

AOP是一种编程范式,它通过预编译方式和运行期动态代理实现程序功能的统一维护。

Spring AOP示例

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Around("execution(com.example.service..(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println(joinPoint.getSignature() + " 执行耗时:" + (endTime - startTime) + "ms");
        return result;
    }

    @Before("execution(com.example.service..(..))")
    public void logBefore() {
        System.out.println("方法执行前的处理...");
    }

    @After("execution(com.example.service..(..))")
    public void logAfter() {
        System.out.println("方法执行后的处理...");
    }
}

5. 实战示例

下面是一个完整的实战示例,展示如何使用代理模式实现方法执行时间统计和日志记录。

java 复制代码
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
}

// 切面类
@Aspect
@Component
public class MethodExecutionAspect {
    private static final Logger logger = LoggerFactory.getLogger(MethodExecutionAspect.class);

    @Around("@annotation(LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        logger.info("方法 [{}] 执行耗时:{}ms",
            joinPoint.getSignature().getName(), (endTime - startTime));
        return result;
    }
}

// 使用示例
@Service
public class UserServiceImpl implements UserService {
    @LogExecutionTime
    @Override
    public void addUser(String username) {
        // 业务逻辑
    }
}
plain 复制代码
方法 [addUser] 执行耗时:2ms

介绍 :该示例使用了自定义注解@LogExecutionTime来标记需要记录执行时间的方法。Spring AOP通过切面对这些方法进行增强,输出了方法名称及其执行的耗时。

6. 总结与最佳实践

代理模式是一种非常实用的设计模式,它可以帮助我们在不修改原有代码的情况下,添加新的功能:

  1. 静态代理
    • 简单直观,但是代理类需要手动编写
    • 适用于代理类较少的场景
  2. JDK动态代理
    • 只能代理实现了接口的类
    • 运行时动态生成代理类
    • 灵活性高
  3. CGLIB动态代理
    • 可以代理未实现接口的类
    • 通过继承实现
    • 性能较好
  4. Spring AOP
    • 提供了更高层次的抽象
    • 支持声明式代理
    • 结合了动态代理的优点

使用建议

  1. 如果目标类有接口,优先使用JDK动态代理
  2. 如果目标类没有接口,使用CGLIB动态代理
  3. 在Spring框架中,优先使用Spring AOP,它能自动选择合适的代理方式

注意事项

  1. 代理模式会增加系统的复杂度
  2. 动态代理可能会带来一定的性能开销
  3. 使用CGLib时注意final方法无法被代理
  4. 在使用Spring AOP时要注意代理对象的作用域

代理模式是Java开发中非常重要的一个设计模式,掌握好它可以帮助我们写出更加优雅和可维护的代码。

相关推荐
码字哥8 分钟前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS加油站管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·maven
Hello.Reader3 小时前
Spring Retry 与 Redis WATCH 结合实现高并发环境下的乐观锁
java·redis·spring
西岭千秋雪_3 小时前
设计模式の单例&工厂&原型模式
java·单例模式·设计模式·简单工厂模式·工厂方法模式·抽象工厂模式·原型模式
fanchael_kui3 小时前
使用elasticsearch-java客户端API生成DSL语句
java·大数据·elasticsearch
m0_748256564 小时前
[CTF夺旗赛] CTFshow Web1-14 详细过程保姆级教程~
java
T.O.P114 小时前
Spring&SpringBoot常用注解
java·spring boot·spring
O(1)的boot4 小时前
微服务的问题
java·数据库·微服务
一个略懂代码的程序员4 小时前
Redis01
java·redis
IT界的奇葩4 小时前
基于springboot使用Caffeine
java·spring boot·后端·caffeine