SpringBoot是对spring和springmvc的进一步封装,因此在SpringBoot中同样支持Spring中的AOP切面编程,不过在SpringBoot中为了快速开发仅仅提供了注解方式的切面编程。
面向切面编程
(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很相似在SpringBoot中使用AOP较之于传统的SSM框架就比较简单了。只需要引用spring-boot-starter-aop
这一个依赖就可以了。
新建Module - springboot-06-aop
,按照下图所示填写信息:
点击下一步选择依赖Spring Web
,点击Finish
。如下图所示:
我们以springboot-05-logback
中的代码为例演示AOP的使用。首先我们规整一下项目,然后将springboot-05-logback
中的代码拷贝到本次案例。如下图所示:
我们在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>
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;
}
}
我们运行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()));
}
}
在正常情况下的运行流程:环绕通知
->前置通知
->方法
->后置结果通知
->后置通知
->环绕通知
。如下图所示:
正常情况下访问,通知和方法都能正确执行
我们修改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。这个怎么做呢?看下面的案例
import java.lang.annotation.*;
/** * @Author Christy * @Date 2021/9/14 14:44 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspectAnnotations {
String value();
}
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);
}
}
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) {
this.userMapper = userMapper;
}
@Override
@MyAspectAnnotations(value = "自定义通知注解:当前访问的方法UserServiceImpl-findAll()")
public List<User> findAll() {
return userMapper.findAll();
}
}
启动项目,浏览器访问http://localhost:8805/user/findAll
。控制台打印如下:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/bbxylqf126com/article/details/120342145
内容来源于网络,如有侵权,请联系作者删除!