当前位置: 首页 > news >正文

深入掌握Spring AOP:从原理到实战的完整指南

一、AOP核心概念与实战引入

1.1 传统开发中的痛点

以用户登录场景为例,假设我们有一个基础登录功能:

public class LoginService {
    public void login(String username, String password) {
        // 验证用户名密码
        System.out.println("核心登录逻辑执行");
    }
}

现在需要新增权限校验功能,传统方案有两种:

  1. 修改源代码:侵入性强,违反开闭原则
  2. 纵向继承扩展:产生冗余代码,维护困难

1.2 AOP的破局之道

// 目标类
public class LoginService {
    public void login(String username) {
        System.out.println(username + "登录成功!");
    }
}

// 切面类
@Aspect
@Component
public class AuthAspect {
    @Before("execution(* com.example.service.LoginService.login(..))")
    public void checkPermission() {
        System.out.println("[权限校验] 正在验证用户权限...");
    }
}

运行效果

[权限校验] 正在验证用户权限...
admin登录成功!

二、AOP底层原理深度剖析

1. 代理模式双雄

代理类型实现方式适用场景
JDK动态代理基于接口实现目标类有接口时优先使用
CGLIB动态代理生成子类继承目标类无接口或需要高性能场景

2. 动态代理实现原理

// JDK动态代理示例
public class JdkProxy implements InvocationHandler {
    private Object target;
    
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this);
    }

    @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;
    }
}

三、XML配置方式全解

1.AOP相关的术语

Joinpoint(连接点) 类里面有哪些方法可以增强这些方法称为连接点

Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强)-- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Aspect(切面)-- 是 切入点+通知 的结合,以后咱们自己来编写和配置的

2. 核心配置模板

<!-- 配置切面 -->
<aop:config>
    <aop:aspect ref="logAspect">
        <aop:pointcut id="servicePointcut" 
            expression="execution(* com.example.service.*.*(..))"/>
        
        <aop:before method="logStart" pointcut-ref="servicePointcut"/>
        <aop:after-returning method="logEnd" pointcut-ref="servicePointcut"/>
        <aop:after-throwing method="logException" pointcut-ref="servicePointcut"/>
    </aop:aspect>
</aop:config>

3. 切入点表达式速查表

表达式模式说明
execution(* *Service.*(..))拦截所有Service类的任意方法
execution(* save*(..))拦截所有以save开头的方法
within(com.example.dao.*)拦截指定包下的所有类方法

切入点表达式的格式如下:

execution([修饰符] [返回值类型] [类全路径] [方法名 ( [参数] )])

修饰符可以省略不写,不是必须要出现的。

返回值类型是不能省略不写的,根据你的方法来编写返回值,可以使用 * 代替。

包名,类名,方法名,参数的规则如下:

例如:com.qcby.demo3.BookDaoImpl.save()

首先包名,类名,方法名是不能省略不写的,但是可以使用 * 代替

中间的包名可以使用 * 号代替

类名也可以使用 * 号代替,也有类似的写法:*DaoImpl

方法也可以使用 * 号代替

参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..

比较通用的表达式:execution(* com.qcby.*.ServiceImpl.save(..))

举例2:com.qcby.demo3.BookDaoImpl当中所有的方法进行增强

execution(* com.qcby.*.ServiceImpl.*(..))

举例3:com.qcby.demo3包当中所有的方法进行增强

execution(* com.qcby.*.*.*(..))

四、注解方式高效开发

1. 全注解配置模板

@Configuration
@EnableAspectJAutoProxy  // 开启AOP自动代理
@ComponentScan("com.example")
public class AppConfig {}

@Aspect
@Component
public class LogAspect {
    @Around("@annotation(com.example.anno.OperateLog)")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long time = System.currentTimeMillis() - start;
        System.out.println("方法执行耗时:" + time + "ms");
        return result;
    }
}

2. 通知类型对比表

注解执行时机典型应用场景
@Before方法执行前参数校验、权限控制
@After方法执行后(始终执行)资源清理
@AfterReturning方法正常返回后结果日志记录
@AfterThrowing方法抛出异常时异常监控报警
@Around方法执行前后性能监控、事务管
@Component
@Aspect  //生成代理对象
public class UserProxy {
    //增强/通知  ---》前置通知
    @Before(value = "execution(* com.*.User.add(..))")
    public void before(){
        System.out.println("before.............");

    }

    // 环绕通知
    @Around(value = "execution(* com.*.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("before.............");
        //  执行被增强的方法
        proceedingJoinPoint.proceed();
        System.out.println("after.............");
    }

    // 最终通知
    @After(value = "execution(* com.*.User.add(..))")
    public void after() {

        System.out.println("after.............");
    }

    //异常通知
    @AfterThrowing(value = "execution(* com.*.User.add(..))")
    public void afterThrowing() {

        System.out.println("afterThrowing.............");
    }

    //后置通知
    @AfterReturning(value = "execution(* com.*.User.add(..))")
    public void afterReturning() {

        System.out.println("afterReturning.............");
    }
}

 测试类:

@Test
public void aopTest1(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    user.add();
}

五、企业级最佳实践

1. 切面优先级控制

@Aspect
@Order(1)  // 数字越小优先级越高
public class SecurityAspect {
    // 安全检查切面
}

@Aspect
@Order(2)
public class LogAspect {
    // 日志记录切面
}

2. 自定义注解实现精准切入

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {}

@Aspect
@Component
public class AuditAspect {
    @AfterReturning("@annotation(AuditLog)")
    public void auditLog(JoinPoint jp) {
        // 审计日志记录逻辑
    }
}

六、性能优化与避坑指南

1. AOP性能影响三要素

  1. 代理类型选择:优先JDK动态代理,必要时使用CGLIB

  2. 切入点粒度:避免过于宽泛的表达式

  3. 通知复杂度:减少耗时操作在切面中的使用

2. 常见问题解决方案

问题1:循环依赖导致代理失效
✅ 解决方案:使用Setter注入代替构造器注入

问题2:内部方法调用切面失效
✅ 解决方案:通过AopContext获取代理对象

((Service)AopContext.currentProxy()).internalMethod();

相关文章:

  • 在 Qt 中,不带参数或整形的参选的信号能够从 std::thread 发送成功,而带枚举离线的信号却发送失败
  • cocos creator 笔记-路边花草
  • java8循环解压zip文件---实现Excel文件数据追加
  • 慧通测控汽车智能座舱测试技术
  • k8s基础知识总结node+pod(上)
  • CSS语言的双向链表
  • 4、pytest常用插件
  • word中指定页面开始添加页码
  • Python(4)Python函数编程性能优化全指南:从基础语法到并发调优
  • Java设计模式之访问者模式
  • 计算机网络 - OSI 七层模型
  • 笔记:分享如何使用github静态页面搭建个人页面
  • 【2025】基于springboot+uniapp的企业培训打卡小程序设计与实现(源码、万字文档、图文修改、调试答疑)
  • 专访成都昭音科技Jackal:AI内容营销助力中企走向全球
  • 鸿蒙学习笔记(2)-国际化配置、ArkTS简述
  • 【Javaweb】b站黑马视频学习笔记(导览)
  • 多模态大模型Qwen 和 Qwen2 的模型结构、训练方式与数据处理对比分析
  • 安卓应用兼容新方案:Android Translation Layer(ATL)
  • 抽奖的诱惑系统注册与登录功能测试报告
  • 机器学习都有哪些算法?
  • 中国田径巡回赛西安站完赛:男子跳远石雨豪夺冠,女子跳高刘肼毅折桂
  • 一周观展|一批重量级考古博物馆开馆:从凌家滩看到孙吴大墓
  • 以色列在加沙发起新一轮强攻,同步与哈马斯展开无条件谈判
  • 中国首颗地质行业小卫星“浙地一号”成功发射
  • AI赋能科学红毯,机器人与科学家在虚实之间叩问“科学精神”
  • 张国清将赴俄罗斯举行中俄“长江—伏尔加河”地方合作理事会第五次会议和“东北—远东”政府间合作委员会双方主席会晤