Spring 配置使用 - AOP 通知类型

基本概念

AOP,即 Aspect-Oriented Programming(面向切面编程),它其实是 OOP(Object-Oriented Programing,面向对象编程)思想的补充和完善。

在使用 Spring AOP 之前,首先需要掌握以下几个常用术语:


1.aspect

aspect,即切面。切面是通知和切点的结合。通知和切点共同定义了关于切面的全部内容。


2.advice

advice,即通知。通知在 AOP 中代表切面的工作,它定义了切面具体要做什么,何时做。

在 Spring 中共有 5 类通知类型,用来分别表示切面在不同时候要做的工作。

  • 前置通知(Before Advice):在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回)。

  • 后置通知(After Advice):在切入点选择的连接点处的方法之后执行的通知(无论方法执行是否成功都会被调用)。

  • 后置返回通知(After returning Advice):在切入点选择的连接点处的方法正常执行完毕时执行的通知,必须是连接点处的方法没抛出任何异常正常返回时才调用。

  • 后置异常通知(After throwing Advice): 在切入点选择的连接点处的方法抛出异常返回时执行的通知,必须是连接点处的方法抛出任何异常返回时才调用异常通知。

  • 环绕通知(Around Advices):环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。


3.pointcut

pointcut,即切点。在 AOP 中切点表示切面生效的地方。


4.joinpoint

joinpoint,连接点。在 AOP 中表示能让切面生效,产生作用的地方。这个点可以是调用方法时,抛出异常时,甚至是字段被修改时。乍看之下会觉的连接点和切点没什么区别,其实它们就是全部与部分的关系,如下图:

这里写图片描述


实例探究

1.前置通知

首先来看一个关于 Spring AOP 前置通知的简单例子。

  • 定义一个 Bean,用它来表示连接点(joinpoint)的一部分
public class Animals {
    public void setName(String name) {
        System.out.println(name);
    }
}
  • 定义一个 Bean,用来表示切面
public class AnimalsAspect {
    // 该方法表示切面的具体工作
    public void beforeAdvice() {
        System.out.println("before advice..." );
    }
}
  • 在 xml 文件中配置
<!-- 1.容器中注入 Bean -->
<bean id="joinpoint" class="com.demo.Animals" />
<bean id="aspect" class="com.aop.AnimalsAspect"/>

<!-- 2.AOP 配置 -->
<aop:config>
    <!-- 2.1指定切面 -->
    <aop:aspect ref="aspect">
        <!-- 2.2指定通知和切点-->
        <!-- before 表示通知类型是前置通知,method 表示通知的具体作用,即指定了切面在何时做什么-->
        <!-- pointcut 表示切点,即指定了切面在哪生效-->
        <aop:before method="beforeAdvice" pointcut="execution(* com.demo.Animals.setName(..))" />
    </aop:aspect>
</aop:config>
  • 调用验证
String location = ...
ApplicationContext factory = new FileSystemXmlApplicationContext(location);
Animals animals = (Animals) factory.getBean("joinpoint");
animals1.setName("cat");
// 输出结果:
// before advice...
// cat

2.后置通知

上面的例子中介绍了 Spring AOP 的简单使用,且具体使用的是 AOP 的前置通知,接下来探究下其他通知类型的作用。

  • 定义一个 Bean,用它来表示连接点(joinpoint)的一部分。
public class Animals {
    public void setName(String name) {
        // 为空时,打印并抛出异常
        if(name == null){
             System.out.println("Exception");
             throw new NullPointerException();
        }
        System.out.println(name);
    }
}
  • 定义切面
public class AnimalsAspect {

    public void afterAdvice() {
        System.out.println("after advice...");
    }

    public void afterReturningAdvice() {
        System.out.println("after returning advice...");
    }

    public void afterThrowingAdvice(Exception exception) {
        System.out.println("after throwing advice exception..." );
    }

}
  • 在 xml 配置文件定义:
<!-- 容器中注入 Bean ,同上-->

<!-- AOP 配置 -->
<aop:config>
    <!-- 定义切点,简化配置(由于多种通知类型都要作用在该切点)-->
    <aop:pointcut expression="execution(* com.demo.Animals.setName(..))" id="pointcut"/>
    <aop:aspect ref="aspect">
        <!-- 相应的,这里不再使用 pointcut 而使用 poincut-ref 指定切点 -->
        <!-- pointcut 和 pointcut-ref 都能指定切点(前者为指定切点,后者为匿名切点)-->
        <aop:after method="afterAdvice" pointcut-ref="pointcut"/>
        <aop:after-returning method="afterReturningAdvice" pointcut-ref="pointcut"/>
        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>
  • 调用验证
String location = ...
ApplicationContext factory = new FileSystemXmlApplicationContext(location);
Animals animals = (Animals) factory.getBean("joinpoint");
animals1.setName("cat");
animals1.setName(null);
// 输出结果:
// cat
// after advice...
// after returning advice...
// 
// Exception
// after advice...
// after throwing advice exception...

3.通知参数

到目前的例子中,几乎所有通知类型的方法都是不带参数。下面来看下如何在通知时调用【被通知方法】的方法参数。

  • 定义一个 Bean,用它来表示连接点(joinpoint)的一部分。
public class Animals {
    public String setName(String name) {
        // 为空时,打印并抛出异常
        if(name == null){
             System.out.println("Exception");
             throw new NullPointerException();
        }
        System.out.println(name);

        // 返回值
        return "return "+name;
    }
}
  • 定义切面
public class AnimalsAspect {
    // 这里需要注意的是:切面的参数类型只有与被通知方法的参数类型一致时才能生效,因此这里采用 Object,因为它能匹配任意类型
    public void beforeAdvice(Object param) {
        System.out.println("before advice...,param is :"+param);
    }

    public void afterAdvice(Object param) {
        System.out.println("after advice...,param is :"+param);
    }

    public void afterReturningAdvice(Object param) {
        System.out.println("after returning advice...,param is :"+param);
    }

    public void afterThrowingAdvice(Exception exception) {
        System.out.println("after throwing advice exception...,exception is :"+exception);
    }
}
  • 在 xml 配置文件定义:
<!-- 容器中注入 Bean ,同上-->

<!-- AOP 配置 -->
<aop:config>
    <!-- 这里定义了两种切点:带参、不带参 -->
    <aop:pointcut expression="execution(* com.demo.Animals.setName(..)) and args(param)" id="pointcut"/>
    <aop:pointcut expression="execution(* com.demo.Animals.setName(..))" id="point-noparam"/>
    <aop:aspect ref="aspect">
        <aop:before method="beforeAdvice" pointcut-ref="pointcut" arg-names="param"/>
         <aop:after method="afterAdvice" pointcut-ref="pointcut" arg-names="param"/>
        <!-- 以下两种通知并没有调用到方法参数,所以使用不带参的切点,注意区别 -->
        <aop:after-returning method="afterReturningAdvice" pointcut-ref="point-noparam" returning="param"/>
        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="point-noparam"  throwing="exception" /> 
    </aop:aspect>
</aop:config>
  • 调用验证:
String location = ...
ApplicationContext factory = new FileSystemXmlApplicationContext(location);
Animals animals = (Animals) factory.getBean("joinpoint");
animals1.setName("cat");
animals1.setName(null);
// 输出结果:
// before advice...,param is :cat
// cat
// after advice...,param is :cat
// after returning advice...,param is :return cat
//
// before advice...,param is :null
// after advice...,param is :null
// after throwing advice exception...,exception is :java.lang.NullPointerException

4.环绕通知

环绕通知非常强大,可以决定【被通知方法】是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。

  • 定义一个 Bean,用它来表示连接点(joinpoint)的一部分。(同上)

  • 定义切面

public class AnimalsAspect {

    // 环绕通知第一个参数必须是 org.aspectj.lang.ProceedingJoinPoint 类型
    public void aroundAdvice(ProceedingJoinPoint joinpoint,Object param) throws Throwable {

        // 关键 -> 取得方法的入参,由于被通知方法只有一个入参,所以取数组的第一个元素
        String param = joinpoint.getArgs()[0];

        System.out.println("around before advice..,param is:"+param);

        // 关键 -> 执行被通知的方法(替换方法入参,并取得返回值)
        Object returnValue = joinpoint.proceed(new Object[]{"replace"});
        System.out.println("around after advice.."+returnValue);
    }
}
  • 在 xml 配置文件定义:
<!-- 容器中注入 Bean ,同上-->

<!-- AOP 配置 -->
<aop:config>
    <aop:pointcut expression="execution(* com.demo.Animals.setName(..))" id="point-noparam"/>
    <aop:aspect ref="aspect">
        <aop:around method="aroundAdvice" pointcut-ref="point-noparam"/> 
    </aop:aspect>
</aop:config>
  • 调用验证
String location = ...
ApplicationContext factory = new FileSystemXmlApplicationContext(location);
Animals animals = (Animals) factory.getBean("joinpoint");
animals1.setName("cat");
// 输出结果:
// around before advice..,param is cat
// replace
// around after advice..return replace

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值