Spring AOP 快速入门使用

目录

一、Spring 中 AOP 的配置方式

在 Spring 的 AOP 配置中,也和 IoC 配置一样,支持3类配置方式。
第一类:使用 XML 配置;
第二类:使用 XML + 注解组合配置;
第三类:使用纯注解配置。

二、XML 配置模式

1、导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

2、aop 核心配置

<!-- 
    Spring 基于 XML 的 AOP 配置前期准备:
    在 spring 的配置文件中加入 aop 的约束 
    xmlns:aop="http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd"
    
    Spring基于XML的AOP配置步骤:
        第一步: 把通知 Bean 交给 Spring 管理 
        第二步: 使用 aop:config 开始 aop 的配置 
        第三步: 使用 aop:aspect 配置切面 
        第四步: 使用对应的标签配置通知的类型
            入⻔案例采用前置通知,标签为 aop:before 
 -->
<!-- 把通知 bean 交给 spring 来管理 -->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
    <!--配置切面-->
    <aop:aspect id="logAdvice" ref="logUtil">
        <!--配置前置通知-->
        <aop:before method="printLog" pointcut="execution( * 
                ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account))">
        </aop:before>
    </aop:aspect>
</aop:config>

3、核心配置细节

1)关于切入点表达式

上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo 方法进行增强,在其执行之前,输出了记录日志的语句。
这里面,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们往下看。

  • 概念及作用

切入点表达式,也称之为 AspectJ 切入点表达式,指的是遵循特定语法结构的字符串,其作用是用于对符合语法格式的连接点进行增强。它是 AspectJ 表达式的一部分。

  • 关于 AspectJ

AspectJ 是一个基于 Java 语言的 AOP 框架,Spring 框架从2.0版本之后集成了 AspectJ 框架中切入点表达式的部分,开始支持 AspectJ 切入点表达式。

  • 切入点表达式使用示例
全限定方法名:访问修饰符 返回值 包名.包名.包名.类名.方法名(参数列表) 
全匹配方式:
public void com.lagou.service.impl.TransferServiceImpl
        .updateAccountByCardNo(com.lagou.pojo.Account)

访问修饰符可以省略:
void com.lagou.service.impl.TransferServiceImpl
        .updateAccountByCardNo(com.lagou.pojo.Account)

返回值可以使用 *,表示任意返回值:
* com.lagou.service.impl.TransferServiceImpl
        .updateAccountByCardNo(com.lagou.pojo.Account)

包名可以使用 . 表示任意包,但是有几级包,必须写几个:
* ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)

包名可以使用 .. 表示当前包及其子包:
* ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)

类名和方法名,都可以使用 . 表示任意类,任意方法:
* ...(com.lagou.pojo.Account)

参数列表,可以使用具体类型:
基本类型直接写类型名称: int
引用类型必须写全限定类名: java.lang.String

参数列表可以使用 * ,表示任意参数类型,但是必须有参数:
* *..*.*(*)
参数列表可以使用..,表示有无参数均可。有参数可以是任意类型:
* *..*.*(..)
全通配方式:
* *..*.*(..)

2)改变代理方式的配置

Spring 在选择创建代理对象时,会根据被代理对象的实际情况来选择的。
被代理对象实现了接口,则采用基于接口的动态代理。
当被代理对象没有实现任何接口的时候,Spring 会自动切换到基于子类的动态代理方式。

但是我们都知道,无论被代理对象是否实现接口,只要不是 final 修饰的类都可以采用 cglib 提供的方式创建代理对象。
所以 Spring 也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理(即 cglib 的方式),配置的方式有两种:

  • 使用 aop:config 标签配置
<aop:config proxy-target-class="true" />
  • 使用 aop:aspectj-autoproxy 标签配置
<!-- 此标签是基于 XML 和注解组合配置 AOP 时的必备标签,表示 Spring 开启注解配置 AOP 的支持 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

3)五种通知类型

  • 前置通知

配置方式:
aop:before 标签

<!--
    作用:
        用于配置前置通知。
    出现位置: 
        它只能出现在 aop:aspect 标签内部。
    属性:
        method: 用于指定前置通知的方法名称;
        pointcut: 用于指定切入点表达式; 
        pointcut-ref: 用于指定切入点表达式的引用。
-->
<aop:before method="printLog" pointcut-ref="pointcut1">
</aop:before>

执行时机
前置通知永远都会在切入点方法(业务核心方法)执行之前执行。
细节
前置通知可以获取切入点方法的参数,并对其进行增强。

  • 正常执行时通知

配置方式
aop:after-returning 标签

<!--
    作用: 
        用于配置正常执行时通知。
    出现位置: 
        它只能出现在aop:aspect标签内部。
    属性: 
        method: 用于指定后置通知的方法名称;
        pointcut: 用于指定切入点表达式;
        pointcut-ref: 用于指定切入点表达式的引用。
-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1">
</aop:after-returning>
  • 异常通知

配置方式
aop:after-throwing 标签

<!--
    作用: 
        用于配置异常通知。
    出现位置: 
        它只能出现在 aop:aspect 标签内部。
    属性: 
        method: 用于指定异常通知的方法名称;
        pointcut: 用于指定切入点表达式; 
        pointcut-ref: 用于指定切入点表达式的引用。
-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1">
</aop:after-throwing>

执行时机
异常通知的执行时机是在切入点方法(业务核心方法)执行产生异常之后,异常通知执行。
如果切入点方法执行没有产生异常,则异常通知不会执行。
细节
异常通知不仅可以获取切入点方法执行的参数,也可以获取切入点方法执行产生的异常信息。

  • 最终通知

配置方式
aop:after 标签

<!--
    作用: 
        用于指定最终通知。
    出现位置: 
        它只能出现在 aop:aspect 标签内部。
    属性: 
        method: 用于指定最终通知的方法名称;
        pointcut: 用于指定切入点表达式;
        pointcut-ref: 用于指定切入点表达式的引用。
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1">
</aop:after>

执行时机
最终通知的执行时机是在切入点方法(业务核心方法)执行完成之后,切入点方法返回之前执行。
换句话说,无论切入点方法执行是否产生异常,它都会在返回之前执行。
细节
最终通知执行时,可以获取到通知方法的参数。同时它可以做一些清理操作。

  • 环绕通知

配置方式
aop:around 标签

<!--
    作用: 
        用于配置环绕通知。
    出现位置: 
        它只能出现在 aop:aspect 标签的内部。
    属性: 
        method: 用于指定环绕通知的方法名称;
        pointcut: 用于指定切入点表达式;
        pointcut-ref: 用于指定切入点表达式的引用。
-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1">
</aop:around>

4)特别说明

环绕通知,它是有别于前面四种通知类型外的特殊通知。
前面四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。
而环绕通知,它是 Spring 框架为我们提供的一种可以通过编码的方式,控制增强代码何时执行的通知类型。
它里面借助的 ProceedingJoinPoint 接口及其实现类, 实现手动触发切入点方法的调用。

三、XML + 注解模式

1、XML 中开启 Spring 对注解 AOP 的支持

<!-- 开启 spring 对注解 aop 的支持 -->
<aop:aspectj-autoproxy />

2、示例

/**
 * 模拟记录日志
 * @author jason559
 */
@Component("logUtil")
@Aspect
public class LogUtil {

    /**
     * 我们在 xml 中已经使用了通用切入点表达式,供多个切面使用,那么在注解中如何使用呢?
     * 第一步: 编写一个方法;
     * 第二步: 在方法使用 @Pointcut 注解;
     * 第三步: 给注解的 value 属性提供切入点表达式;
     * 细节: 
     *     1. 在引用切入点表达式时,必须是方法名+(),例如"pointcut()"。
     *     2. 在当前切面中使用,可以直接写方法名。在其他切面中使用必须是全限定方法名。
     */
    @Pointcut("execution(* com.jason.service.impl.*.*(..))")
    public void pointcut() {}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        System.out.println("方法前执行。参数是:"+ Arrays.toString(args));
    }

    @After("pointcut()")
    public void after() {
        System.out.println("方法后执行。无论如何都要执行。");
    }

    @AfterReturning(value = "pointcut()",returning = "rtValue")
    public void afterReturning(Object rtValue) {
        System.out.println("方法后返回时执行。返回值是:"+rtValue);
    }

    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(Throwable e) {
        System.out.println("方法后抛错时执行。异常是:"+e.getMessage());
    }

    /**
     * 环绕通知
     * @param pjp
     * @return 
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 定义返回值
        Object rtValue = null;
        try{
            // 前置通知
            System.out.println("前置通知");
            // 1.获取参数
            Object[] args = pjp.getArgs();
            // 2.执行切入点方法
            rtValue = pjp.proceed(args);
            // 后置通知
            System.out.println("后置通知");
        }catch (Throwable t){
            // 异常通知
            System.out.println("异常通知");
            t.printStackTrace();
            throw t;
        }finally {
            // 最终通知
            System.out.println("最终通知,");
        }
        return  rtValue;
    }
}

四、注解模式

1、注解

在使用注解驱动开发 aop 时,我们要明确的就是,是注解替换掉配置文件中的下面这行配置:

<!-- 开启 spring 对注解 aop 的支持 -->
<aop:aspectj-autoproxy />

在配置类中使用如下注解进行替换上述配置:

@Configuration
@ComponentScan("com.jason") 
@EnableAspectJAutoProxy // 开启 spring 对注解 AOP 的支持 
public class SpringConfiguration {
}

文章内容输出来源:拉勾教育Java高薪训练营;

上一篇:Spring AOP 基础概念


下一篇:spring aop切面切点