八、SpringAOP的相关概念

1 面向切面编程简介

​ Aspect Oriented Programming 面向切面编程,是一种利用"横切"的技术(底层实现就是动态代理),对原有的业务逻辑进行拦截,并且可以在这个拦截的横切面上添加特定的业务逻辑,对原有的业务进行增强。

基于动态代理,实现在不改变原有业务的情况下对业务逻辑进行增强

  • 首选 JDK 做动态代理。

  • 如果代理对象有接口,就用 JDK 动态代理,否则就是 Cglib 动态代理。

  • 如果代理对象没有接口,那么就直接是 Cglib 动态代理。

在这里插入图片描述

1.1 术语解释

1、连接点(joinpoint)

​ 程序执行过程中特定的节点,如方法的调用或异常的执行,通常是一个方法的执行。

2、切入点(pointcut)

​ 是指需要处理的连接点,所有的方法执行都是连接点,某个特定的连接点就是切入点(被拦截的连接点)

3、目标对象(target)

​ 是指被通知的对象,即代理的目标对象;

4、通知(advice)

​ 也被称为增强,是由切面添加到特定的连接点的一段代码,简单来说,通知就是指拦截到的连接点之后所要做的事情,因此通知是切面的具体实现;通知分为前置通知、后置通知、异常通知、最终通知、环绕通知;

5、切面(aspect)

​ 是指封装横向切到系统功能的类(例如事务处理),是切入点和通知的结合;

6、织入(weave)

是将切面代码插入到目标对象上,从而生成代理对象的过程;

7、代理(Proxy)

是指被应用了通知(增强)后,产生一个代理对象;

1.2 图示

在这里插入图片描述

2 程序环境部署

2.1 引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
</dependencies>

2.2 创建配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

2.3 创建目标对象的类

package com.yiwu.demo2;

import com.yiwu.demo1.LandlordInterface;

public class Landlord1{

    public void rentHouse(){
        System.out.println("出租房子");
    }
}

2.4 创建切面类

package com.yiwu.demo2;

public class MyAspect {

    public  void advertise(){
        System.out.println("做广告");
    }

    public void signContract(){
        System.out.println("签合同");
    }
}

3 xml方式实现AOP

  • 增强rentHouse方法,实现做广告,签合同的功能。
  • 仅修改配置文件即可。

3.1 基本实现

第一步,修改配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">


    <bean id="landlord1" class="com.yiwu.demo2.Landlord1"/>
    <bean id="myaspect" class="com.yiwu.demo2.MyAspect"/>

    <aop:config>
        <!--声明切入点-->
        <aop:pointcut id="landlordP" expression="execution(void com.yiwu.demo2.Landlord1.rentHouse())"/>
        <!--声明切面类-->
        <aop:aspect ref="myaspect">
            <!--通知策略-->
            <aop:before method="advertise" pointcut-ref="landlordP"/>
            <aop:after method="signContract" pointcut-ref="landlordP"/>
        </aop:aspect>
    </aop:config>


</beans>

第二步,编写测试方法

Spring AOP 有两种代理方法,

一种是常规JDK,一种是CGLIB。

  • 当代理对象实现了至少一个接口时,默认使用JDK动态创建代理对象;

  • 当代理对象没有实现任何接口时,就会使用CGLIB方法。

  • 如果实现了接口,强制转换必须用父类接口来定义

import com.yiwu.demo2.Landlord1;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest03 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(" applicationContext.xml");
        Landlord1 landlord = (Landlord1)applicationContext.getBean("landlord1");
        landlord.rentHouse();

    }
}

做广告
出租房子
签合同

3.2 切入点声明要点

🔴切点是com.yiwu.demo2.Landlord1.rentHouse()方法

<aop:pointcut id="id1" 
              expression="execution(* com.yiwu.demo2.Landlord1.rentHouse())"/>

🔴切点是com.yiwu.demo2.Landlord1类中所有无返回值,参数为空的方法

<aop:pointcut id="id2"
              expression= "execution(void com.yiwu.demo2.Landlord1.*())"/>

🔴切点是com.yiwu.demo2.Landlord1类中所有无返回值,对参数无要求的方法

<aop:pointcut id="id3" 
              expression="execution(void com.yiwu.demo2.Landlord1.*(..))"/>

🔴切点是com.yiwu.demo2.Landlord1类中所有对返回值无要求,参数为空方法

<aop:pointcut id="id4"
              expression="execution(* com.yiwu.demo2.Landlord1.*())"/>

🔴切点是com.yiwu.demo2.Landlord1类中所有方法,对返回值,参数均无要求。

<aop:pointcut id="id5" 
expression="execution(* com.yiwu.demo2.Landlord1.*(..))"/>

🔴切点是com.yiwu.demo2.包中所有类的所有方法,对返回值,参数均无要求。

<aop:pointcut id="id6" expression="execution(* com.yiwu.demo2.*.*(..))"/>

🔴切点是com.yiwu.demo2.包中所有类的rentHouse方法,对返回值,参数均无要求。

<aop:pointcut id="pc6" 
              expression="execution(* com.yiwu.demo2.*.rentHouse(..))"/>

🔴所有类的所有方法,对返回值,参数均无要求。

<aop:pointcut id="pc7" expression="execution(* *(..))"/>

3.3 通知策略与执行顺序

  1. 前置通知(before):在目标方法执行之前执行执行的通知

  2. 后置通知(after):在目标方法执行之后执行的通知。

  3. 异常通知(after-throwing):在目标方法抛出异常时执行的通知。

  4. 最终通知(after-returning):在目标方法成功执行执行的通知。

  5. 环绕通知(around):目标方法执行之前和之后都可以执行额外代码的通知

通知的执行顺序与xml文件中配置的顺序有关

第一步,新建切面类

在MyAspect中添加5个方法

package com.yiwu.demo2;

import org.aspectj.lang.ProceedingJoinPoint;

public class MyAspect {


    public void method1(){
        System.out.println("~~~~~~~~~~~~~前置方法method1");
    }
    public void method2(){
        System.out.println("~~~~~~~~~~~~~后置方法method2");
    }
    public void method3(){
        System.out.println("~~~~~~~~~~~~~异常方法method3");
    }
    public void method4(){
        System.out.println("~~~~~~~~~~~~~最终通知method4");
    }
    //环绕通知的方法,必须遵守如下的定义规则
    //1:必须带有一个ProceedingJoinPoint类型的参数,
    //2:必须有Object类型的返回值
    //3:在前后增强的业务逻辑之间执行Object v = point.proceed();
    //4: 方法最后返回 return v;
    public Object method5(ProceedingJoinPoint point) throws Throwable {
        System.out.println("~~~~~~~~~~~~~环绕通知method5----before");
        //此句代码的执行就表示切入点方法的执行
        Object v = point.proceed();
        System.out.println("~~~~~~~~~~~~~环绕通知method5----after");
        return v;
    }

}

第二步,通知规则总结

使用xml配置,通知的执行顺序与配置的顺序有关。

  • 使用前置通知,后置通知,最终通知

    • 无异常前置通知>切入点方法->后置通知和最终通知(执行顺序根据xml配置的顺序决定)

    • 有异常前置通知>切入点方法->后置通知>异常通知

  • 环绕通知,异常通知

    • 无异常:环绕通知前置 > 切入点方法 > 环绕通知后置
    • **有异常:**环绕通知前置 > 切入点方法 > 异常通知
  • 使用所有通知:

    • 无异常:执行顺序如下:

      • 前置通知和环绕通知的前置(依据xml配置顺序)
      • 切入点方法
      • 后置通知,最终通知,环绕通知后置(依据xml配置顺序执行有两种情况,一是前置或环绕存在其中之一二是前置与环绕同时存在且中间没有其他配置
    • 有异常:执行顺序如下:

      • 前置通知和环绕通知的前置(依据xml配置顺序)
      • 切入点方法
      • 后置通知,异常通知(执行顺序同上

      总结:当前置与环绕之间没有其他配置项时,后置,异常,最终的执行顺序与配置相同

在这里插入图片描述
测试1,使用前置通知,后置通知,最终通知,无异常时

前置通知>切入点方法->后置通知和最终通知(执行顺序根据xml配置的顺序决定)

<!--声明切面类-->
<aop:aspect ref="myaspect">

    <aop:before method="method1" pointcut-ref="landlordP"/>
    <!--调整after与after-returning的顺序-->
    <aop:after method="method2" pointcut-ref="landlordP"/>
    <aop:after-returning method="method4" pointcut-ref="landlordP"/

</aop:aspect>
</aop:config>

在这里插入图片描述

测试1,使用前置通知,后置通知,最终通知,有异常时

前置通知>切入点方法->后置通知>异常通知

​ 在切入点方法中制造异常 int a = 5/0,除数为0;

package com.yiwu.demo2;
    public class Landlord1 {
        public void rentHouse(){
            System.out.println("出租房子");
            int a = 5/0;
        }
}
<aop:aspect ref="myaspect">
    <aop:before method="method1" pointcut-ref="landlordP"/>
    <aop:after-returning method="method4" pointcut-ref="landlordP"/>
    <aop:after method="method2" pointcut-ref="landlordP"/>
</aop:aspect>
~~~~~~~~~~~~~前置方法
出租房子
~~~~~~~~~~~~~后置方法

测试2 环绕通知,异常通知

无异常:环绕通知 > 切入点方法 > 环绕通知

有异常:环绕通知 > 切入点方法 > 异常通知

<aop:aspect ref="myaspect">
    <aop:around method="method5" pointcut-ref="landlordP"/>
    <aop:after-throwing method="method3" pointcut-ref="landlordP"/>
</aop:aspect>

无异常:

~~~~~~~~~~~~~环绕通知----before
出租房子
~~~~~~~~~~~~~环绕通知----after

有异常

~~~~~~~~~~~~~环绕通知----before
出租房子
~~~~~~~~~~~~~异常方法

测试3 使用所有通知无异常

执行顺序如下:

  • 前置通知和环绕通知的前置(依据xml配置顺序)
  • 切入点方法
  • 后置通知,最终通知,环绕通知后置(依据xml配置顺序执行有两种情况,一是前置或环绕仅存在其中之一二是前置与环绕同时存在并且中间没有其他配置

前置与环绕之间存在其他配置项,执行顺序与配置顺序相反。
在这里插入图片描述

其余情况,后置通知,最终通知,环绕通知后置(依据xml配置顺序)

在这里插入图片描述

测试3 使用所有通知 有异常

  • 执行顺序如下:
    • 前置通知和环绕通知的前置(依据xml配置顺序)
    • 切入点方法
    • 后置通知,异常通知(依据xml配置顺序执行有两种情况,一是前置或环绕仅存在其中之一二是前置与环绕同时存在并且中间没有其他配置

前置与环绕之间存在其他配置项,执行顺序与配置顺序相反。
在这里插入图片描述

其余情况,后置通知,异常通知(依据xml配置顺序)
在这里插入图片描述

测试类

public class Mytest3 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        GeneralDao bookDao = (GeneralDao) context.getBean("bookDao");
        bookDao.insert();
    }
}

4 注解方式实现AOP

Spring5中执行顺序如下:

程序执行正常,无异常:

  • 环绕通知(around)

  • 前置通知(before)

  • 切入点方法

  • 最终通知(after-returning)

  • 后置通知(after)

  • 环绕通知(around)

程序执行异常:

  • 环绕通知(around)

  • 前置通知(before)

  • 切入点方法

  • 异常通知(after-throwing)

  • 后置通知(after)

4.1 修改配置文件

conetxt:component-scan base-package=“com.qlit”

​ 该语句作用包括注册+激活,指定包扫描范围(包含子包),可在扫描返回内使用相关注解,若存在多个包,可用逗号隔开。

​ 要注意和 context:annotation-config区分,该语句的作用是激活那些已经在spring容器里注册过的bean,也就是使用他的前提是类已经xml中使用bean标签注册过,不能进行注册。

aop:aspectj-autoproxy

该语句开启spring对注解aop的⽀持。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:conetxt="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <conetxt:component-scan base-package="com.yiwu"/>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4.2 切面类

  • 使用@Aspect注解,声明切面类。

  • 使用@Before,@After,@AfterThrowing,@AfterReturning, @Around声明对应的通知方法。

  • 使用@Pointcut定义表达式语句,优化代码结构。

package com.yiwu.demo3;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {

    @Pointcut("execution(* com.yiwu.demo3.Landlord2.rentHouse())")
    public void p(){}

    //@Before("execution(* com.yiwu.demo3.Landlord2.rentHouse())")
    @Before("p()")
    public void method1(){
        System.out.println("~~~~~~~~~~~~~前置方法method1");
    }

    //@After("execution(* com.yiwu.demo3.Landlord2.rentHouse())")
    @After("p()")
    public void method2(){
        System.out.println("~~~~~~~~~~~~~后置方法method2");
    }

    //@AfterThrowing("execution(* com.yiwu.demo3.Landlord2.rentHouse())")
    @AfterThrowing("p()")
    public void method3(){
        System.out.println("~~~~~~~~~~~~~异常方法method3");
    }
    //@AfterReturning("execution(* com.yiwu.demo3.Landlord2.rentHouse())")
    @AfterReturning("p()")
    public void method4(){
        System.out.println("~~~~~~~~~~~~~最终通知method4");
    }
   
    //@Around("execution(* com.yiwu.demo3.Landlord2.rentHouse())")
    @Around("p()")
    public Object method5(ProceedingJoinPoint point) throws Throwable {
        System.out.println("~~~~~~~~~~~~~环绕通知method5----before");
        //此句代码的执行就表示切入点方法的执行
        Object v = point.proceed();
        System.out.println("~~~~~~~~~~~~~环绕通知method5----after");
        return v;
    }
}

4.3 环绕通知方法优化

3.4 四合一通知

可以优化环绕通知的方法的编写,减少通知的配置。

如下:

 public Object method5(ProceedingJoinPoint point){
        Object v = null;
        try {
            System.out.println("~~~~~~~~~~~~~环绕通知method5----前置通知");
            v = point.proceed();
            System.out.println("~~~~~~~~~~~~~环绕通知method5----最终通知");
        } catch (Throwable throwable) {
            //环绕异常通知@AfterThrowing
            System.out.println("~~~~~~~~~~~~~环绕通知method5----异常通知");
            throwable.printStackTrace();
        }finally {
            System.out.println("~~~~~~~~~~~~~环绕通知method5----后置通知");
        }
        return v;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

老去的90后

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

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

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

打赏作者

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

抵扣说明:

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

余额充值