深入理解代理模式(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开发中非常重要的一个设计模式,掌握好它可以帮助我们写出更加优雅和可维护的代码。

相关推荐
空の鱼4 小时前
java开发,IDEA转战VSCODE配置(mac)
java·vscode
P7进阶路5 小时前
Tomcat异常日志中文乱码怎么解决
java·tomcat·firefox
小丁爱养花5 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
CodeClimb5 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
等一场春雨5 小时前
Java设计模式 九 桥接模式 (Bridge Pattern)
java·设计模式·桥接模式
带刺的坐椅5 小时前
[Java] Solon 框架的三大核心组件之一插件扩展体系
java·ioc·solon·plugin·aop·handler
不惑_6 小时前
深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)
java·深度学习·ui
费曼乐园6 小时前
Kafka中bin目录下面kafka-run-class.sh脚本中的JAVA_HOME
java·kafka
feilieren7 小时前
SpringBoot 搭建 SSE
java·spring boot·spring
阿岳3167 小时前
Java导出通过Word模板导出docx文件并通过QQ邮箱发送
java·开发语言