Spring AOP

1、AOP 简介

1. 什么是 AOP?

AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的一个重要特性,它提供了一种将横切关注点(如日志、事务管理、权限检测、异常处理、限流等)从业务逻辑代码中分离出来的机制,从而提高代码的可重用性、可维护性和模块化程度。

AOP 将应用程序分为核心业务和非核心的公共功能,AOP的关注点是系统中非核心的公共功能;AOP 可以通过预编译或者运行期动态代理的方式,为横跨多个对象(没有继承关系..)的业务逻辑添加统一的功能;

2. AOP 作用

  1. 提高代码的可重用性和可维护性:通过将横切关注点与业务逻辑代码分离,使得业务逻辑代码更加简洁,便于理解和维护。

  2. 实现模块之间的解耦:AOP可以将不同模块之间的依赖关系降低到最低,使得系统更加灵活和可扩展。将核心业务和非核心业务的公共功能解耦。

  3. 增强代码的安全性:通过AOP,可以在不修改原有代码的情况下,对方法进行权限控制、性能监控等操作。对一些方解析功能增强。

  4. 提高代码的复用性:通过切面编程,可以将多处使用的公共逻辑(如日志记录、事务管理等)封装在切面中,避免在多个地方重复编写相同的代码,提高代码的复用性。

3. AOP 核心概念

  • 连接点(Joinpoint):程序执行过程中的某个点,如方法调用、异常抛出等。

    • 在SpringAOP中,理解为方法的执行。

  • 切入点(Pointcut):一组连接点的集合,用于定义哪些连接点将被切面增强。

    • 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法,用切入点表达式匹配连接点。

    • 切入点表达式(execution):匹配连接点的式子。

  • 通知(Advice):在切入点指定的连接点处执行的代码,包括前置通知、后置通知、环绕通知等。

  • 切面(Aspect):横切关注点的模块化,是通知、引入和切入点的组合。

2、Advice 类型

  • 前置通知(Before Advice)

    • 定义:在目标方法执行之前执行的通知。

    • 特点:前置通知不会影响连接点的执行,除非它抛出一个异常,这样后面的连接点就不会执行。

  • 后置通知(After Returning Advice)

    • 定义:在目标方法成功执行之后执行的通知。

    • 特点:如果目标方法通过抛出异常退出,则不会执行此类型的通知。

  • 异常通知(After Throwing Advice)

    • 定义:在目标方法通过抛出异常退出时执行的通知。

    • 特点:专门用于处理异常情况,可以记录异常信息或进行异常处理。

  • 最终通知(After (finally)Advice)

    • 定义:无论目标方法通过何种方式退出(正常返回或异常退出),该通知都会执行。

    • 特点:类似于Java中的finally块,无论目标方法执行结果如何,最终通知都会被执行。

  • 环绕通知(Around Advice)

    • 定义:环绕通知是最强大的通知类型,它将目标方法封装起来,可以在方法调用之前和之后自定义行为,甚至可以完全控制是否调用目标方法。

    • 特点:环绕通知需要提供一个带有ProceedingJoinPoint参数的方法,可以执行目标方法 ProceedingJoinPoint.proceed();

知识点1:@After

名称@After
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行

知识点2:@AfterReturning

名称@AfterReturning
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行

知识点3:@AfterThrowing

名称@AfterThrowing
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行

知识点4:@Around

名称@Around
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行

知识点4:@Before

名称@Before
类型方法注解
位置通知方法定义上方
作用设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行

3、AOP 入门案例

1. 思路分析

1.引入依赖(pom.xml)

2.定义连接点

3.定义通知(通知(日志、权限校验、事务))

4.定义切入点

5.绑定切入点与通知关系(切面)

2. 实现步骤

  • 开启注解格式AOP功能

    @Configuration
    @ComponentScan
    /*
        aspectj自动代理
            proxy-target-class=true: 修改创建代理对象的方式 - 完全采用CGCLIB动态代理
            expose-proxy: 能够重新获取当前类型的代理对象  
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true" />
     */
    @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
    public class SpringConfig {
    }

  • 引入依赖

    <!--  Spring 整合 AspectJ 框架 - AOP框架  -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.1.1</version>
    </dependency>

  • 定义连接点

    public class UserServiceImpl implements UserService {
    ​
        @Override
        public void add() {
            System.out.println("核心业务 - 添加用户");
        }
    ​
        @Override
        public int update(int id) {
            System.out.println("核心业务 - 更新用户");
            return 1;
        }
    }

  • 定义切入点

    //对service包下的任意子包下的任意类下的任意方法 任意参数进行添加功能
    @Pointcut("execution(* com.advice.service..*.*(..))")
    public void advice(){
    ​
    }

  • 定义通知以及 绑定切入点与通知关系

    @Before("advice()")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName + "准备执行");
        //方法参数值列表
        //Object[] args = joinPoint.getArgs();
    }
    ​
    @AfterReturning(pointcut = "advice()",returning = "returnVal")
    public void afterReturning(JoinPoint joinPoint,Object returnVal){
        System.out.println(joinPoint.getSignature().getName() +"执行结束,返回值:"+returnVal);
    }
    ​
    @AfterThrowing(pointcut = "advice()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e){
        System.out.println(joinPoint.getSignature().getName() + "执行异常!");
    }
    ​
    @After("advice()")
    public void after(JoinPoint joinPoint){
        System.out.println("after:不管连接点是否执行成功,都会执行");
    }
    ​
    @Around("advice()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("前置环绕通知");
        Object result = proceedingJoinPoint.proceed();
        System.out.println("后置环绕通知");
        return  result;
    }

  • 将通知类配给容器并标识其为切面类

    @Component
    @Aspect
    public class AopAdvice {
        ......
            ...
            ...通知
    }

  • 运行程序

    public static void main(String[] args) {
            ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
            // 代理对象 - 默认使用JDK动态代理
            UserService userService = ioc.getBean(UserServiceImpl.class);
            userService.update(100);    
        }

执行结果:

知识点1:@EnableAspectJAutoProxy

名称@EnableAspectJAutoProxy
类型配置类注解
位置配置类定义上方
作用开启注解格式AOP功能

知识点2:@Aspect

名称@Aspect
类型类注解
位置切面类定义上方
作用设置当前类为AOP切面类

知识点3:@Pointcut

名称@Pointcut
类型方法注解
位置切入点方法定义上方
作用设置切入点方法
属性value(默认):切入点表达式

4、切入点表达式

1. 通配符

我们使用通配符描述切入点,主要的目的就是简化之前的配置,具体都有哪些通配符可以使用?

  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

    execution(public * com.user.*.UserService.find*(*))

    匹配com.user包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution(public User com..UserService.findById(..))

    匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

  • +:专用于匹配子类类型

    execution(* *..*Service+.*(..))

2. 书写技巧

  • 所有代码按照标准规范开发,否则以下技巧全部失效

  • 描述切入点通==常描述接口==,而不描述实现类,如果描述到实现类,就出现紧耦合了

  • 访问控制修饰符针对接口开发均采用public描述(==可省略访问控制修饰符描述==

  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述

  • ==包名==书写==尽量不使用..匹配==,效率过低,常用*做单个包描述匹配,或精准匹配

  • ==接口名/类名==书写名称与模块相关的==采用*匹配==,例如UserService书写成*Service,绑定业务层接口名

  • ==方法名==书写以==动词==进行==精准匹配==,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll

  • 参数规则较为复杂,根据业务方法灵活调整

  • 通常==不使用异常==作为==匹配==规则

  • 25
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值