当前位置:首页 > 技术 > 正文内容

Spring AOP原理与实现:代理模式与通知类型详解

访客 技术 2026年6月13日 1

Spring AOP概述与核心概念

Spring框架通过面向切面编程(AOP)技术,将与业务逻辑无关但被多个业务模块共同使用的功能代码进行封装,从而提升代码复用性并降低模块间的耦合度。

在Spring AOP中,系统功能被划分为两个主要部分:核心关注点和横切关注点。核心关注点代表主要的业务处理流程,而横切关注点则是被多个核心关注点共享的功能模块,如安全验证、日志记录、事务管理等。横切关注点的典型特征是它们会在核心关注点的多个位置执行,且执行逻辑基本相似。AOP的核心思想是将这两类关注点解耦,使系统结构更加清晰。

两种代理实现机制

Spring框架提供了两种创建代理对象的方式:基于JDK的动态代理和基于CGLib的动态代理。具体采用哪种方式由AopProxyFactory根据AdvisedSupport对象的配置来决定。Spring的默认策略是:如果目标类实现了接口,则使用JDK动态代理;否则,使用CGLib动态代理。

JDK动态代理实现原理

JDK动态代理主要依靠java.lang.reflect包中的Proxy类和InvocationHandler接口。InvocationHandler是一个接口,通过实现不同的类可以定义各种横切逻辑,并利用反射机制调用目标类的方法,将横切逻辑与业务逻辑动态结合。Proxy类则利用InvocationHandler动态创建符合特定接口的实例,生成目标对象的代理。

JDK动态代理的关键实现原理如下:

public class DynamicProxy implements InvocationHandler {
    // 目标对象
    private Object target;
    
    public DynamicProxy(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置处理
        System.out.println("方法执行前处理");
        
        // 调用目标方法
        Object result = method.invoke(target, args);
        
        // 后置处理
        System.out.println("方法执行后处理");
        
        return result;
    }
    
    // 创建代理对象
    public static <T> T getProxy(Class<T> clazz, Object target) {
        return (T) Proxy.newProxyInstance(
            clazz.getClassLoader(),
            clazz.getInterfaces(),
            new DynamicProxy(target)
        );
    }
}

CGLib动态代理实现原理

CGLib(Code Generation Library)是一个高性能的代码生成库,能够在运行时扩展Java类和实现Java接口。CGLib通过字节码处理框架ASM来实现其功能,通过转换字节码来生成新的类。

与JDK动态代理的主要区别在于:JDK只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,只能通过CGLib创建动态代理。

五种通知类型详解

Spring AOP支持五种类型的通知,用于在目标方法的不同执行阶段插入横切逻辑:

  1. 前置通知(Before):在目标方法执行前调用
  2. 后置通知(AfterReturning):在目标方法正常执行后调用
  3. 异常通知(AfterThrowing):在目标方法抛出异常时调用
  4. 最终通知(After):在目标方法执行后调用(无论是否异常)
  5. 环绕通知(Around):包围目标方法的执行,可以在方法执行前后自定义逻辑

实际应用示例

下面是一个使用Spring AOP实现日志记录的示例:

@Aspect // 声明切面
public class LoggingAspect {
    
    // 定义切入点:拦截com.example.service包下的所有public方法
    @Pointcut("execution(public * com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    // 前置通知:方法执行前记录日志
    @Before("servicePointcut()")
    public void logBefore(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行方法: " + className + "." + methodName + "() - 开始执行");
    }
    
    // 后置通知:方法正常执行后记录日志
    @AfterReturning(pointcut = "servicePointcut()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行方法: " + className + "." + methodName + "() - 执行成功,返回值: " + result);
    }
    
    // 异常通知:方法抛出异常时记录日志
    @AfterThrowing(pointcut = "servicePointcut()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行方法: " + className + "." + methodName + "() - 执行异常: " + ex.getMessage());
    }
    
    // 环绕通知:方法执行前后记录日志
    @Around("servicePointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        
        // 执行目标方法
        Object result = joinPoint.proceed();
        
        long elapsedTime = System.currentTimeMillis() - start;
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        
        System.out.println("执行方法: " + className + "." + methodName + "() - 耗时: " + elapsedTime + "ms");
        
        return result;
    }
}

通过上述切面定义,系统会自动在com.example.service包下的所有public方法执行前后插入日志记录逻辑,无需修改业务代码即可实现横切功能。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。