springboots整合AOP
用过Spring都知道两个最主要的东西:AOP和IOC,我的这篇文章主要是说AOP在springboot中的使用,都是干活,直接使用,原理就先不说了,后续我的学习博客中慢慢更新,一步步来,先会使用再知道原理,深入了解。
什么是AOP?
原理在网上说的很多,我自己理解就是从这个AOP字面来理解,面向切面编程,先说一下java,java我们知道是面向对象编程,就是我的运行代码都是一步步按照我们的编写的代码来执行,因为我们写的代码都是一个对象一个对象的,然后就是对象之间的逻辑处理,比如:一个controller对象里,去到我们service层处理逻辑,再dao层拿数据,一个对象一个对象进行的,那个环节出问题都会导致程序报错,不能执行。
那什么是AOP面向切面编程呢,我在网上找了两个图,这个是我们数学写的切面图,我感觉这个能直观给我们看到这个切面在哪里。从下面两个图,我们首先把他当成我们的两个方法,一个方法是球形方法,一个方法是圆锥方法。方法里有两个面,对准了球和圆锥一个点,我们可以看到这个面是不会影响球和圆锥的形状,是独立的一个面,他存不存在都不会影响球和圆锥这个两个主体,就是像我现在要说的AOP切面,AOP就相当于我们球和圆锥上的这个面,完成是一个独立的东西,也可以说的我们方法的增强,就是不影响主体的情况增加一个逻辑处理,比如我们最常见的写日志,这个逻辑又是和业务不相干的,这个时候我们AOP就很完美的解决这个问题,而且AOP比这个两个面的功能更加强大,我在下面的介绍AOP的使用上会看到。
execution表示式
通过举例来说明一下,表达式举例:execution(* com.example.demo..*.*(..))
1) 执行 execution(xxx) | 表达式的主体,xxx是表达式的规则; |
2) 第一个"*"符号 | 表示返回值的类型任意; * 表示所有类型(就是方法的返回参数没有限制), string 表示string类型(只有方法的返回参数是string才生效) |
3) com.example.demo | AOP所切的包,就是在哪个包下生效,也可以用*(代表所有包,但是一般不会用*,不然一启动项目你就会产生大量的切面执行) |
4) 包名后面的".." | 表示当前包及子包 |
5) 第二个"*" | 表示类名,*即所有类, *Controller2 表示类名后缀是Controller2的类 |
6) .*(..) | 表示任何方法名,括号表示参数,两个点表示任何参数类型, .*Test1(..) 表示方法名后缀是Test1的所有方法,参数无限制, 比如: .*Test(*) 表示方法名后缀是Test1的所有方法,参数只能是一个的, 比如: .*Test(*,*) 表示方法名后缀是Test1的所有方法,参数只能是两个的。 |
注解说明
注解名称 | 说明 |
---|---|
@Pointcut | 切入点 |
@Before | 在目标方法被调用之前执行,定义可以用切入点方法 |
@After | 在目标方法完成之后执行,定义可以用切入点方法 |
@AfterThrowing | 在目标方法出现异常时执行,定义可以用切入点方法 |
@AfterReturning | 在目标方法正常完成后执行,定义可以用切入点方法 |
@Around | 环绕 |
注:具体使用可以看我下面的整合使用
springboot整合AOP使用
先创建一个简单的springboot工程,还不会的同学可以看我的《小白篇springboot干货使用-我的第一个springboot》
先给出我的代码目录结构
1.在pom加入依赖
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
完整的pom.xml内容
<?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.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springbootaop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_aop</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.在controller层创建一个AspectController类,给我们的切面使用
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AspectController {
private static final Logger LOG = LoggerFactory.getLogger(AspectController.class);
@RequestMapping("/testAsperctNoParameter")
public String testAsperct() {
LOG.info("我在controller层,没有参数");
return "success";
}
@RequestMapping("/testAsperctOneParameter")
public String testAsperct(String parameter) {
LOG.info("我在controller层,一个参数:{}", parameter);
int i = 1 / 0;
return "success";
}
@RequestMapping("/testAsperctTwoParameter")
public String testAsperct(String parameter1, String parameter2) {
LOG.info("我在controller层,两个参数:{},{}", parameter1, parameter2);
return "success";
}
@RequestMapping("/testAsperctAround")
public String testAsperctAround(String parameter) {
LOG.info("我在controller层,参数:{}", parameter);
return "success";
}
}
3.创建我们的切面MyAspect类,要我们的切面生效在类上加上注解@Aspect和 @Component,我们不需求去激活,springboot默认已经帮我们注册了,直接使用就行
package com.example.demo.aspectLogic;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
public class MyAspect {
private static final Logger LOG = LoggerFactory.getLogger(MyAspect.class);
/**
* 定义切入点
*
* 表达式举例说明:execution(* com.example.demo..*.*(..))
* 1) 执行 execution(xxx) 表达式的主体,xxx是表达式的规则;
* 2) 第一个"*"符号 表示返回值的类型任意; * 表示所有类型(就是方法的返回参数没有限制), string 表示string类型(只有方法的返回参数是string才生效)
* 3) com.example.demo AOP所切的包,就是在哪个包下生效,也可以用*(代表所有包,但是一般不会用*,不然一启动项目你就会产生大量的切面执行)
* 4) 包名后面的".." 表示当前包及子包
* 5) 第二个"*" 表示类名,*即所有类, *Controller2 表示类名后缀是Controller2的类
* 6) .*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型, .*Test1(..) 表示方法名后缀是Test1的所有方法,参数无限制,
* .*Test(*) 表示方法名后缀是Test1的所有方法,参数只能是一个的,
* .*Test(*,*) 表示方法名后缀是Test1的所有方法,参数只能是两个的。
*/
@Pointcut("execution(String com.example.demo.controller..*Asperct(..))")
public void MyPointcut(){
}
/**
* @Before 在目标方法被调用之前执行,定义可以用切入点方法,也可以自己用execution()表达式
* @param joinPoint
*/
// @Before("execution(* com.example.demo.controller..*Test(..))")
@Before("MyPointcut()")
public void doBefore(JoinPoint joinPoint){
LOG.info("我在AOP层,我在方法之前执行");
//获取参数值
Object[] args = joinPoint.getArgs();
if (args.length > 0){
for (int i = 0; i < args.length; i++) {
LOG.info("参数" + i + ":" + args[i]);
}
}
LOG.info("类的路径:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//request获取
HttpServletRequest request = attributes.getRequest();
}
/**
* @After 在目标方法完成之后执行,定义可以用切入点方法,也可以自己用execution()表达式
* @param joinPoint
*/
@After("MyPointcut()")
public void doAfter(JoinPoint joinPoint){
LOG.info("我在AOP层,我在方法之后执行");
}
/**
* @AfterThrowing 在目标方法出现异常时执行,定义可以用切入点方法,也可以自己用execution()表达式
* @param joinPoint
*/
@AfterThrowing("MyPointcut()")
public void doAfterThrowing(JoinPoint joinPoint){
LOG.info("我在AOP层,我在异常时执行");
}
/**
* @AfterReturning 在目标方法正常完成后执行,定义可以用切入点方法,也可以自己用execution()表达式
* @param joinPoint
*/
@AfterReturning("MyPointcut()")
public void doAfterReturning(JoinPoint joinPoint){
LOG.info("我在AOP层,我在返回一个结果后执行");
}
/**
*
* @param pJoinPoint
* @return
* @throws Throwable
*/
@Around("execution(String com.example.demo.controller.AspectController.testAsperctAround(..))")
public Object doAround(ProceedingJoinPoint pJoinPoint) throws Throwable {
LOG.info("我在AOP层");
LOG.info("我在AOP层,@Around 我在方法之前执行");
Object[] args = pJoinPoint.getArgs();
args[0] = "修改参数值";
Object proceed = pJoinPoint.proceed(args);
LOG.info("我在AOP层,@Around 我在方法之后执行");
return proceed;
}
}
整个代码已经完成,我们的切面已经可以使用了,我们启动项目,通过在浏览器上,输入localhost:8080/testAsperctNoParameter就可以访问了
我们看回自己的控制台日志输出,已经成功使用到了切面的方法。
其实大家有没有发现,我们现在的idea工具已经超级强大的,方法是否没切面使用,在方法的傍边都是有一个标志的
然后我们到我们的切面类看,细心的同学,我们发现,这个标记有一个不同的标识来表明是上切,下切,四周。
到这里我们已经完全把AOP使用起来了,大家把这个代码手打一下,使用起来,有什么不懂,都可以提出来,大家一起学习,谢谢大家