1. 什么是Spring AOP

AOP的全称是Aspect Oriented Programming,也便是面向切面编程,是一种思维。它是针对OOP(面向目标编程)的一种补充,是对某一类作业的集中处理。比方一个博客网站的登陆验证功用,在用户进行新增、修改、删除博客等操作前都需求进行用户的登陆验证,咱们在对这些事务编码时,都需求考虑一下用户的登录验证。关于这种功用一致,且运用较多的功用,就能够考虑经过AOP来一致处理了。 引入AOP的思维之后,咱们在处理其他事务的时分就不需求再考虑如登录验证这样的其他功用。

Spring AOP是Spring公司针对AOP思维提供的一种完成办法,除了登录验证的功用之外,Spring AOP还能够完成:一致日志记载、一致回来格局设置、一致反常处理等。

2. AOP的基本术语

2.1 切面(Aspect)

切面相当于AOP完成的某个功用的集合,比方说登录验证功用,切面是由切点(Pointcut)和告诉(Advice)组成的

2.2 连接点(Join Point)

连接点是应用履行进程中能够刺进切面的一个点。比方说一个博客系统,包含许多事务,其间能够刺进切面的事务都能够称为连接点。

2.3 切点(Pointcut)

Pointcut的效果便是提供一组规矩来匹配连接点(Join Point),比方说关于博客系统,它有一些url是不需求做登录验证功用的,比方注册事务,经过切点提供的这么一组规矩,在不需求登录验证的地方,它就不会进行登录验证。

2.4 告诉(Advice)

告诉是切面要完成的作业,它界说了切面的详细完成是什么、何时运用,描绘了切面要完成的作业以及何时履行这个宫欧的问题。Spring切面类中,能够在办法上运用以下注解,会设置办法为告诉办法,在满意条件后会告诉本办法进行调用:

  • 前置告诉(@Before):在方针办法调用之前履行。
  • 后置告诉(@After):在方针办法回来或抛出反常后调用。
  • 回来之后告诉(@AfterReturning):在方针办法回来后调用。
  • 抛反常后告诉(@AfterThrowing):在方针办法抛出反常后调用。
  • 盘绕告诉(@Around):告诉包裹了方针办法,在被方针办法履行之前和调用之后履行自界说的行为。

2.5 图解

以用户的登录验证为例,用图来让大家更好的了解上述界说体现的思维:

13. Spring AOP(一)思维及运用

接下来咱们就来对面向切面编程的思维进行完成,假如看到这还是没有很了解AOP的思维的话,能够结合后边的完成代码再来看看这张图表达的意思。

3. Spring AOP 运用

Spring AOP的运用大体分为下面四步:

  1. 增加 Spring AOP的依靠
  2. 连接点办法的编写
  3. 界说切面和告诉
  4. 界说切点

3.1 原生Maven项目中Spring AOP的运用

Spring AOP相同有两种完成办法,一种是运用xml装备的办法,一种是运用注解的办法。

在这儿我将在原生的Maven项目中运用两种办法来完成搭乘地铁的事务,运用AOP的办法编写一个切面,并在切面里运用五种告诉来完成搭乘地铁事务的安全检查(前置告诉)、刷卡进出站(盘绕告诉)、告诉反常(反常告诉)、抵达告诉(回来后告诉)、记载行程(后置告诉)

3.1.1 xml办法运用Spring AOP

源码方位:spring-aop

1. 增加依靠

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.9.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.9.RELEASE</version>
</dependency>

2. 连接点办法的编写:在service包下新建一个SubwayService来完成乘坐地铁的事务

public class SubwayService {
    public void takeSubway() {
        System.out.println("乘坐地铁,行进中...");
        //int n = 10/0;
    }
}

3. 界说切面类和告诉办法:在aspect包下新增一个SubwayAspect类,并在里边编写对应的告诉办法

public class SubwayAspect {
    public void securityCheckAdvice() {
        System.out.println("前置告诉:开端安全检查");
    }
    public void recordAdvice() {
        System.out.println("后置告诉:记载本次行程");
    }
    public void expressionAdvice() {
        System.out.println("反常告诉:运转进程出现反常");
    }
    public void swipeAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("盘绕告诉start:开端并刷卡进站");
        joinPoint.proceed();
        System.out.println("盘绕告诉finish:完毕并刷卡出站");
    }
    public void arriveDestination() {
        System.out.println("回来后告诉:抵达目的地");
    }
}

注意事项:swipeAdvice()是盘绕告诉的办法,因为其是盘绕告诉,因而会在连接点办法开端前和完毕后的时分别离履行不同的逻辑,因而需求运用一个ProceedingJoinPoint的目标来对应连接点的办法,并运用proceed()来履行连接点办法,别离在joinPoint.proceed();句子履行的前后编写编写盘绕告诉。

光编写完切面和告诉还没什么用,还需求在xml文件中装备才行。

装备schema路径,直接仿制即可:

<?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
        https://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">
</beans>

将类注册到Spring容器中,这一步不多赘述:

<bean id="subwayAspect" class="com.chenshu.xml_aop.aspect.SubwayAspect"></bean>
<bean id="subwayService" class="com.chenshu.xml_aop.service.SubwayService"></bean>

装备AspectAdvice

  1. <beans>标签内增加一对<aop:config>的标签,有关aop的装备都放在这儿边
  2. <aop:config>标签内增加一对<aop:aspect>标签,id特点的值自己界说,用于标识一个切面的idref特点里边的值对应前面注册入Spring的Aspect类的beanid
  3. <aop:aspect>标签内装备告诉办法,不同的告诉办法对应不同的标签,其间有method特点对应前面编写的告诉办法的办法名,以及一个pointcut-ref来界说该告诉的切点,因为咱们的切点还未界说,因而这儿用一个”?”代替
<aop:config>
    <aop:aspect id="subwayAspect" ref="subwayAspect">
        <aop:before method="securityCheckAdvice" pointcut-ref="?"/>
        <aop:after method="recordAdvice" pointcut-ref="?"/>
        <aop:after-throwing method="expressionAdvice" pointcut-ref="?"/>
        <aop:around method="swipeAdvice" pointcut-ref="?"/>
        <aop:after-returning method="arriveDestination" pointcut-ref="?"/>
    </aop:aspect>
</aop:config>

4. 编写切点:这儿咱们就要根据切点表达式来创立一个切点用于描绘一组匹配规矩

【引入】切点表达式

一个切点表达式便是形如上面expression特点中的内容,切点表达式中能够包括以下内容:

  • execution(表达式前缀)
  • 权限修饰符(如public、private)
  • 办法回来类型(如void、String)
  • 包名
  • 类名
  • 办法名
  • 办法的参数列表

下面是一些切点表达式的示例:

  • execution(public * com.example.service.SomeService.*(..)):匹配 com.example.service.SomeService 类中一切 public 办法。
  • execution(public void com.example.service.SomeService.*(..)):匹配 com.example.service.SomeService 类中一切 public void 办法。
  • execution(* com.example.service.*.*(..)):匹配 com.example.service 包下一切类的一切办法。
  • execution(* com.example.service.SomeService.*(..)):匹配 com.example.service.SomeService 类的一切办法。
  • execution(* com.example.service.SomeService.*(String)):匹配 com.example.service.SomeService 类中接受一个 String 类型参数的一切办法。
  • execution(* *(..)):匹配任何类中的任何办法。

了解了切点表达式后咱们就能够编写切点了:

<aop:pointcut id="takeSubway" expression="execution(* com.chenshu.xml_aop.service.SubwayService.takeSubway())"/>

该切点的名字为takeSubway,切点表达式的意思是匹配com.chenshu.xml_aop.service.SubwayService这个类下的名为takeSubway无传入参数的办法。

然后咱们就能够在告诉中填入pointcut-ref特点的值了,完好的aop装备如下:

<aop:config>
    <aop:pointcut id="takeSubway" expression="execution(* com.chenshu.xml_aop.service.SubwayService.takeSubway())"/>
    <aop:aspect id="subwayAspect" ref="subwayAspect">
        <aop:before method="securityCheckAdvice" pointcut-ref="takeSubway"/>
        <aop:after method="recordAdvice" pointcut-ref="takeSubway"/>
        <aop:after-throwing method="expressionAdvice" pointcut-ref="takeSubway"/>
        <aop:around method="swipeAdvice" pointcut-ref="takeSubway"/>
        <aop:after-returning method="arriveDestination" pointcut-ref="takeSubway"/>
    </aop:aspect>
</aop:config>

编写测验类进行测验:

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-aop.xml");
        SubwayService subwayService  =
                context.getBean("subwayService", SubwayService.class);
        subwayService.takeSubway();
    }
}

【总结】不同告诉的履行次序

在没有发生反常的情况下成果如下,经过成果能够了解不同告诉的履行次序:

前置告诉:开端安全检查
盘绕告诉start:开端并刷卡进站
乘坐地铁,行进中...
回来后告诉:抵达目的地
盘绕告诉finish:完毕并刷卡出站
后置告诉:记载本次行程

因为没有出现反常因而看不到抛反常后的告诉

制作一个算数反常:

public class SubwayService {
    public void takeSubway() {
        System.out.println("乘坐地铁,行进中.。。");
        int n = 10/0;
    }
}

发生反常的情况下成果如下,经过成果能够了解不同告诉的履行次序:

前置告诉:开端安全检查
盘绕告诉start:开端并刷卡进站
乘坐地铁,行进中...
反常告诉:运转进程出现反常
后置告诉:记载本次行程

因为办法履行一半就抛出反常,因而没有回来后的告诉以及盘绕告诉的后半段

3.1.2 注解完成运用Spring AOP

源码方位:spring-aop_2

谈到Spring AOP的注解,就不得不谈到Spring AOP和AspectJ的联系:Spring AOP 的注解是根据 AspectJ 注解的一种简化和封装。这意味着你能够运用 AspectJ 注解来界说切面,但实际的织入进程是由 Spring AOP 来完成的。

增加依靠和xml办法是一样的,这儿就不赘述了。

注解的办法只需求在xml中增加下面两个标签:上面是组件注解的扫描路径,下面是aspectj注解的声明

<context:component-scan base-package="com.chenshu.aop_annotation"/>
<aop:aspectj-autoproxy/>

编写Aspect类:

  1. Aspect类上增加@Aspect注解
  2. 界说一个办法,并在办法上运用@Pointcut注解将其声明一个切点,并在注解内增加value特点的值(切点表达式)
  3. 在告诉办法上运用@Before(前置告诉)、@After(后置告诉)、@AfterThrowing(抛反常后告诉)、@Around(盘绕告诉)、@AfterReturning(回来后的告诉)注解声明不同类型的告诉,并在注解特点中增加上一步界说的切点"myPointcut()"
@Component
@Aspect
public class SubwayAspect {
    @Pointcut(value = "execution(* com.chenshu.aop_annotation.service.SubwayService.takeSubway())")
    private void myPointcut() {}
    @Before("myPointcut()")
    public void securityCheckAdvice() {
        System.out.println("前置告诉:开端安全检查");
    }
    @After("myPointcut()")
    public void recordAdvice() {
        System.out.println("后置告诉:记载本次行程");
    }
    @AfterThrowing("myPointcut()")
    public void expressionAdvice() {
        System.out.println("反常告诉:运转进程出现反常");
    }
    @Around("myPointcut()")
    public void swipeAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("盘绕告诉start:开端并刷卡进站");
        joinPoint.proceed();
        System.out.println("盘绕告诉finish:完毕并刷卡出站");
    }
    @AfterReturning("myPointcut()")
    public void arriveDestination() {
        System.out.println("回来后告诉:抵达目的地");
    }
}

3.2 Spring Boot项目中Spring AOP的运用

源码方位:MyBatis_demo

这儿我直接根据上一篇文章中的代码来演示Spring AOP的运用。

1. 增加依靠

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 新建一个aspect包编写切面类LoginAspect

@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.chenshu.mybatis_demo.controller.UserController.*(..))")
    public void myPointcut() {}
    @Before("myPointcut()")
    public void before() {
        System.out.println("进行登录验证");
    }
}

3. 测验切面是否生效:

因为我编写的切面中的前置办法对一切UserController类下的办法都生效,这儿我直接访问一下UserControllergetUsers办法的路由"/getall"

13. Spring AOP(一)思维及运用

检查日志信息:咱们发现在履行getUsers()办法之前成功履行了前置告诉

进行登录验证
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3e84be0f] was not registered for synchronization because synchronization is not active
2024-04-20 17:07:40.813  INFO 40289 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-04-20 17:07:40.890  INFO 40289 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@212054083 wrapping com.mysql.cj.jdbc.ConnectionImpl@3ef56d86] will not be managed by Spring
==>  Preparing: select * from userinfo
==> Parameters: 
<==    Columns: id, username, password, photo, createtime, updatetime, state
<==        Row: 1, zhang, 12345, doge.png, 2024-04-19 13:09:45, 2024-04-19 13:09:45, 1
<==        Row: 2, lisi, 123, , 2024-04-19 13:31:01, 2024-04-19 13:31:01, 1
<==        Row: 3, wangwu, 123, , 2024-04-19 14:17:29, 2024-04-19 14:17:29, 1
<==      Total: 3