SpringBoot.07.SpringBoot切面编程之AOP
Posted 潮汐先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot.07.SpringBoot切面编程之AOP相关的知识,希望对你有一定的参考价值。
SpringBoot.07.SpringBoot切面编程之AOP
前言
SpringBoot是对spring和springmvc的进一步封装,因此在SpringBoot中同样支持Spring中的AOP切面编程,不过在SpringBoot中为了快速开发仅仅提供了注解方式的切面编程。
AOP介绍
概念
面向切面编程
(AOP是Aspect Oriented Program的首字母缩写)。通过预编译
方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而是的业务逻辑各部分之间的耦合度降低,提高程序的重用性,有人提高开发效率。
AOP采取横向抽取机制,补充了传统**纵向继承体系(OOP)**无法解决的重复性代码优化(性能监视、事务管理、安全检查、缓存),将业务逻辑和系统处理的代码(关闭连接、事务管理、操作日志记录)解耦。这样维护起来更加方便。
纵向集成体系
横向抽取机制
相关术语
Joinpoint
:连接点。所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点Pointcut
:切入点。所谓切入点是指我们要对哪些Joinpoint进行拦截的定义Advice
:通知|增强。所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知
,后置通知
,异常通知
,最终通知
,环绕通知
(切面要完成的功能)Introduction
:引介。引介是一种特殊的通知。在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或FieldTarget
:目标对象。代理的目标对象Weaving
:织入。是指把增强应用到目标对象来创建新的代理对象的过程Proxy
:代理。一个类被AOP织入增强后,就产生一个结果代理类Aspect
:切面。是切入点和通知的结合,以后咱们自己来编写和配置的Advisor
:通知器、顾问。和Aspect很相似
AOP使用
在SpringBoot中使用AOP较之于传统的SSM框架就比较简单了。只需要引用spring-boot-starter-aop
这一个依赖就可以了。
小试牛刀
1.新建Module
新建Module - springboot-06-aop
,按照下图所示填写信息:
点击下一步选择依赖Spring Web
,点击Finish
。如下图所示:
2.项目配置
我们以springboot-05-logback
中的代码为例演示AOP的使用。首先我们规整一下项目,然后将springboot-05-logback
中的代码拷贝到本次案例。如下图所示:
3.pom.xml
我们在pom.xml
中引入AOP的依赖(记得刷新)。完整的pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/>
</parent>
<groupId>com.christy</groupId>
<artifactId>springboot-06-aop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-06-aop</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- mybatis-spring-boot-starter
由于springboot整合mybatis版本中默认依赖mybatis 因此不需要额外引入mybati版本,否则会出现冲突
-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- 每次新建的项目如果需要开启热部署都需要引入该依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.MyAspect.java
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @Author Christy
* @Date 2021/9/14 10:25
*
* @Aspect 该注解作用于类上,代表这个类是一个切面
**/
@Aspect
@Component
@Slf4j
public class MyAspect
/**
*
* @Before 该注解作用方法上 代表这个方法是一个前置通知方法 该方法是没有返回值的
* execution() 切点的函数
* 第一个*:代表的是方法返回值 *即代表所有的返回值
* com.christy.service:包名,代表切面切入的位置
* 包名后的第一个*:代表的是该包下面的所有类
* 包名后的第二个*:代表的是类中的所有方法
* (..):代表方法中的任意|所有参数
* execution(* com.christy.service.*.*(..)):
* 意思就是切面的切点作用在包com.christy.service下的所有类中的所有方法
*/
@Before("execution(* com.christy.service.*.*(..))")
public void before(JoinPoint joinPoint)
log.info("这是一个前置通知Before");
// 目标对象
log.info("目标对象:" + joinPoint.getTarget());
// 方法名
log.info("方法名:" + joinPoint.getSignature());
// 方法中的参数
log.info("方法中的参数:" + joinPoint.getArgs());
/**
*
* @After 该注解作用方法上 代表这个方法是一个后置(最终)通知方法
* 在目标对象方法之后执行通知,有没有异常都会执行
* 该方法是没有返回值的
*/
@After("execution(* com.christy.service.*.*(..))")
public void after(JoinPoint joinPoint)
log.info("这是一个后置通知After");
// 目标对象
log.info("目标对象:" + joinPoint.getTarget());
// 方法名
log.info("方法名:" + joinPoint.getSignature());
// 方法中的参数
log.info("方法中的参数:" + joinPoint.getArgs());
/**
*
* @AfterReturning 该注解作用方法上 代表这个方法是一个后置通知方法
* 在目标对象方法之后执行通知,有异常则不执行了
* 该方法可以获取目标方法的返回值
*/
@AfterReturning(value = "execution(* com.christy.service.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result)
log.info("这是一个后置结果通知AfterReturning");
// 目标对象
log.info("目标对象:" + joinPoint.getTarget());
// 方法名
log.info("方法名:" + joinPoint.getSignature());
// 方法中的参数
log.info("方法中的参数:" + joinPoint.getArgs());
// 目标方法返回值
log.info("目标方法返回值:",result);
/**
*
* @AfterReturning 该注解作用方法上 代表这个方法是一个后置异常通知方法
* 在目标对象方法抛出异常之后执行通知
* throwing = "ex" 声明ex的类型会限制目标方法抛出指定类型的异常
* 这里将ex的类型声明为Throwable说明不限制类型
*/
@AfterThrowing(value = "execution(* com.christy.service.*.*(..))",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex)
log.info("这是一个后置异常通知AfterThrowing");
// 目标对象
log.info("目标对象:" + joinPoint.getTarget());
// 方法名
log.info("方法名:" + joinPoint.getSignature());
// 方法中的参数
log.info("方法中的参数:" + joinPoint.getArgs());
// 方法中抛出的异常
log.info("方法中抛出的异常:", ex);
/**
*
* @Around 该注解作用方法上 代表这个方法是一个环绕通知方法
*/
@Around("execution(* com.christy.service.*.*(..))")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
log.info("这是一个环绕通知Around");
// 目标对象
log.info("目标对象:" + proceedingJoinPoint.getTarget());
// 方法名
log.info("方法名:" + proceedingJoinPoint.getSignature());
// 方法参数
log.info("方法参数:" + proceedingJoinPoint.getArgs());
// 放行执行目标方法
Object proceed = proceedingJoinPoint.proceed();
log.info("目标方法执行之后回到环绕通知");
// 返回目标方法返回值 如果不返回目标方法是获取不到返回值的
return proceed;
5.测试
我们运行test包下的类UserTest.java
中的方法testFindAll()
。具体的代码如下:
import com.christy.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class UserTest extends BaseTest
private UserService userService;
@Autowired
public UserTest(UserService userService)
this.userService = userService;
@Test
public void testFindALl()
userService.findAll().forEach(user -> System.out.println(user.toString()));
5.1 正常运行
在正常情况下的运行流程:环绕通知
->前置通知
->方法
->后置结果通知
->后置通知
->环绕通知
。如下图所示:
正常情况下访问,通知和方法都能正确执行
5.2 方法中抛出异常
我们修改UserServiceImpl
中的方法findAll
,在里面抛出一个异常。如下所示:
import com.christy.entity.User;
import com.christy.mapper.UserMapper;
import com.christy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author Christy
* @Date 2021/9/2 14:56
**/
@Service
public class UserServiceImpl implements UserService
private UserMapper userMapper;
@Autowired
public UserServiceImpl(UserMapper userMapper)
this.userMapper = userMapper;
@Override
public List<User> findAll()
int i = 1/0;
return userMapper.findAll();
启动项目,然后浏览器访问http://localhost:8805/user/findAll
。控制台打印如下:
在抛出异常情况下,
AfterReturning是不执行的
;afterThrowing
会执行。
注解方式
注解方式相较于上面的案例,他的控制粒度更细。什么意思呢?比如我不想某个包下的所有类中的所有方法都使用AOP。我希望指定的方法使用AOP。这个怎么做呢?看下面的案例
1.MyAspectAnnotations.java
import java.lang.annotation.*;
/**
* @Author Christy
* @Date 2021/9/14 14:44
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspectAnnotations
String value();
2.MyAspect2.java
import com.christy.config.annotations.MyAspectAnnotations;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Author Christy
* @Date 2021/9/14 14:47
**/
@Aspect
@Component
@Slf4j
public class MyAspect2
/**
* @PointCut: 该注解在切面类中定义一个通用的切入点
* @annotation: 用于匹配当前执行方法持有指定注解的方法;
**/
@Pointcut("@annotation(com.christy.config.annotations.MyAspectAnnotations)")
public void customerPointCut()
/**
*
* @After 该注解作用方法上 代表这个方法是一个后置(最终)通知方法
* 在目标对象方法之后执行通知,有没有异常都会执行
* 该方法是没有返回值的
*/
@After("customerPointCut()")
public void after(JoinPoint joinPoint)
log.info("这是一个后置通知After");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyAspectAnnotations myAspectAnnotations = method.getAnnotation(MyAspectAnnotations.class);
String value = myAspectAnnotations.value();
log.info("我是使用自定义通知注解返回的消息:",value);
3.UserServiceImpl.java
import com.christy.config.annotations.MyAspectAnnotations;
import com.christy.entity.User;
import com.christy.mapper.UserMapper;
import com.christy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author Christy
* @Date 2021/9/2 14:56
**/
@Service
public class UserServiceImpl implements UserService
private UserMapper userMapper;
@Autowired
public UserServiceImpl(UserMapper userMapper)