SpringBoot整和AOP+模板
参考网址:
https://www.cnblogs.com/pecool/p/8275400.html
https://mp.weixin.qq.com/s/1R07OKP_-CEPwiwT1rkCkw
http://www.itsoku.com/article/299
参考图片
1.execution表达式
说明:
spring在ssm正常我们都是使用的execution表单式,他比较通用
切入点表达式写法:
参数包括:execution("修饰符 返回值类型 包.类.方法名(参数..) throws异常")
修饰符(举例):一般省略
* 任意
public 公共访问
返回值(举例):
void 无返回值
String 返回值是字符串类型
* 返回值任意
包(举例):
com.xx.user.dao 固定包
com.xx.*.dao com.xx下的任意包中的dao包
com.xx.user.dao.. 包括dao下所有子包中
类(举例):
UserDaoImpl 具体类
User* 以User开头类
*User 以User结尾类
* 任意类
方法(举例):
addUser 具体方法
* 任意方法
*User 以add结尾方法
add* 以add开头方法
参数(无参):
() 无参
(..) 任意参数
(String,int) 1个String和1个int类型的参数
(int) 1个int类型参数
throws,可省略一般不写
2.springboot中advice的执行顺序
因为版本的不同,执行顺序也不同
3.常用的几种切入点表达式
指示符 | 作用 |
---|---|
bean | 用于匹配指定bean对象的方法 |
within | 用于匹配指定包下所有类内的方法 |
execution | 用于按指定语法规则匹配到具体方法 |
@annotation | 用于匹配指定注解修饰的方法 |
- bean表达式(重点)
bean****表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
bean("*ServiceImpl")指定所有的后缀为ServiceImpl的类。
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的内部的名字应该是spring容器中某个bean的key。
- within表达式(了解)
within****表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
within(“aop.service.*”) 指定当前目录下的所有类的所有方法。
within(“aop.service…*”) 指定当前目录以及子目录中类的所有方法。
- execution表达式(了解)
execution****表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
execution(* aop.service….(…)) 万能配置。
- @annotation表达式(重点)
@annotaion****表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
@annotation(anno.RequiredLog) 匹配有此注解描述的方法。
@annotation(anno.RequiredCache) 匹配有此注解描述的方法。
其中:RequestLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行扩展操作。
编写入门demo
说明:
因为springmvc那一套写aop过程过于繁琐,需要配置xml,索性我就是用springboot直接来写一个aop的实例
基于注解和execution两种表达式
1.新建一个springboot项目
2.导入相关的依赖
<?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.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shaoming</groupId>
<artifactId>springboot-test-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-test-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- springboot整合aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
重点说明
springboot整合aop需要下面这个依赖就可以
<!-- springboot整合aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.模拟一个业务成方法
package com.shaoming.service;
import com.shaoming.annotation.AopAnnotation;
import org.springframework.stereotype.Service;
/**
* @Auther: shaoming
* @Date: 2020/12/31 16:00
* @Description:
*/
@Service
public class MyService {
@AopAnnotation(operModul = "测试方法模块",operType = "测试方法类型",operDesc = "测试方法的操作说明")
public String service(String str){
//模拟出现异常的情况,除数为0的异常(不需要模拟异常,注释即可)
int i = 1/0;
System.out.println("Myservice的Service方法执行业务方法开始执行------------,方法的参数为:\t"+ str);
return "方法的返回值是方法的参数,这个方法的参数是:\t"+str;
}
}
4.写一个切面(execution)
说明:
1.我可能写的有点乱,但是抓住要领就行
@Befor,@After,@AfterReturn,@AfterThrowing,@Round
2.注意execution表达式的书写
package com.shaoming.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import java.lang.reflect.Method;
import java.util.Arrays;
import static java.util.Arrays.*;
/**
* @Auther: shaoming
* @Date: 2020/12/31 15:58
* @Description:
*/
@Order(value = 2)//这个注解表示切面的优先级,数字越小,优先级越大
@Aspect//这个注解表示这个类是一个aop切面
@Component//切面必须交给spring容器管理
public class AopExecution {
@Pointcut("execution(public String com.shaoming.service.MyService.*(String))")
public void point(){
System.out.println("切入点方法的内容不会执行");
}
@Before(value = "point()")
public void doBefore(JoinPoint joinPoint){
System.out.println("前置通知:"+joinPoint);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println("获取请求的类名(去类名:包名+类名)"+joinPoint.getTarget().getClass().getName());
System.out.println("获取方法名的全路径(包名+类名+方法名):"+joinPoint.getTarget().getClass().getName() + "." + signature.getMethod().getName().toString());
System.out.println("获取的方法名称:"+signature.getMethod().getName().toString());
System.out.println("获取参数的名称"+ asList(signature.getParameterNames()));
System.out.println("获取参数的类型"+ asList(signature.getParameterTypes()));
System.out.println("获取方法的返回值的类型:"+signature.getReturnType().toString());
}
@After(value = "point()")
public void doAfter(JoinPoint joinPoint){
System.out.println("后置通知" + joinPoint);
}
@AfterReturning(value = "point()",returning = "res")
public void doAfterReturning(JoinPoint joinPoint,Object res){
System.out.println("返回通知" + joinPoint);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println("====返回通知===获取方法名称"+signature.getMethod().getName());
System.out.println("=====返回通知====获取方法返回值的类型"+signature.getMethod().getReturnType().toString());
System.out.println("=====返回通知中获取方法的返回值:\t" + res);
}
@AfterThrowing(value = "point()",throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint,Exception e){
System.out.println("异常通知:"+joinPoint);
System.out.println("======异常通知===异常信息为:"+e.getMessage());
}
@Around(value = "point()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("=============环绕通知的操作开始获取一些信息================");
System.out.println("环绕通知:" + proceedingJoinPoint);
Object proceed = null;
try {
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("=======环绕通知=====获取异常信息为:"+throwable.getMessage());
}
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
System.out.println("======环绕通知====获取请求的类名(去类名:包名+类名)"+proceedingJoinPoint.getTarget().getClass().getName());
System.out.println("======环绕通知====获取方法名的全路径(包名+类名+方法名):"+proceedingJoinPoint.getTarget().getClass().getName() + "." + signature.getMethod().getName().toString());
System.out.println("======环绕通知====获取的方法名称:"+signature.getMethod().getName().toString());
System.out.println("======环绕通知====获取参数的名称"+ asList(signature.getParameterNames()));
System.out.println("======环绕通知====获取参数的类型"+ asList(signature.getParameterTypes()));
System.out.println("======环绕通知====获取方法的返回值的类型:"+signature.getReturnType().toString());
return proceed;
}
}
5.再写一个切面(注解方式)
- 定义一个自定义注解
package com.shaoming.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Auther: shaoming
* @Date: 2021/1/1 16:13
* @Description:
*/
@Target({ElementType.METHOD})//这个注解表示改方法作用在方法上
@Retention(RetentionPolicy.RUNTIME)//这个注解表示该方法在运行时有效
public @interface AopAnnotation {
String operModul() default "";//操作模块
String operType() default "";//操作类型
String operDesc() default "";//操作说明
}
- 写一个切面
package com.shaoming.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @Auther: shaoming
* @Date: 2021/1/1 16:33
* @Description:
*/
@Order(value = 1)//这个注解表示切面的优先级,数字越小,优先级越大
@Aspect//这个注解表示这是一个切面类
@Component//切面类需要交给spring容器管理
public class AopAnnotation {
@Pointcut("@annotation(com.shaoming.annotation.AopAnnotation)")
public void point(){
System.out.println("切入点方法的内容不会被执行");
}
@Before(value = "point()")
public void doBefore(JoinPoint joinPoint){
System.out.println("前置通知:======(注解方式)" + joinPoint);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取到方法上的注解
com.shaoming.annotation.AopAnnotation annotation =
signature.getMethod().getAnnotation(com.shaoming.annotation.AopAnnotation.class);
System.out.println("获取到的操作模块: \t"+annotation.operModul());
System.out.println("获取到的操作类型: \t"+annotation.operType());
System.out.println("获取到的操作说明: \t"+annotation.operDesc());
System.out.println("=============注解方式==================");
}
}
6.定义切面的优先级
说明:
有时候一个方法对应几个切面,当然就有优先级
当然,这种情况比较少
切面的优先级需要借助@Order
注解进行描述,数字越小优先级越高,默认优先级比较低。例如:
定义日志切面并指定优先级。
@Order(1)
@Aspect
@Component
public class SysLogAspect {
…
}
定义缓存切面并指定优先级:
@Order(2)
@Aspect
@Component
public class SysCacheAspect {
…
}
说明:当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链,其执行分析如图
7.写测试方法进行测试
package com.shaoming;
import com.shaoming.service.MyService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @Auther: shaoming
* @Date: 2020/12/31 16:04
* @Description:
*/
@SpringBootTest
public class AopTest {
@Autowired
private MyService myService;
@Test
public void testAop1() {
String strReturn = myService.service("我是参数的值");
System.out.println(strReturn);
}
}
分两种情况进行打印
- 测试又异常的情况进行打印
前置通知:======(注解方式)execution(String com.shaoming.service.MyService.service(String))
获取到的操作模块: 测试方法模块
获取到的操作类型: 测试方法类型
获取到的操作说明: 测试方法的操作说明
=============注解方式==================
=============环绕通知的操作开始获取一些信息================
环绕通知:execution(String com.shaoming.service.MyService.service(String))
前置通知:execution(String com.shaoming.service.MyService.service(String))
获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
获取的方法名称:service
获取参数的名称[str]
获取参数的类型[class java.lang.String]
获取方法的返回值的类型:class java.lang.String
异常通知:execution(String com.shaoming.service.MyService.service(String))
======异常通知===异常信息为:/ by zero
后置通知execution(String com.shaoming.service.MyService.service(String))
java.lang.ArithmeticException: / by zero
at com.shaoming.service.MyService.service(MyService.java:15)
at -------一栈异常信息
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
=======环绕通知=====获取异常信息为:/ by zero
======环绕通知====获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
======环绕通知====获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
======环绕通知====获取的方法名称:service
======环绕通知====获取参数的名称[str]
======环绕通知====获取参数的类型[class java.lang.String]
======环绕通知====获取方法的返回值的类型:class java.lang.String
null
- 测试又异常的情况进行打印
前置通知:======(注解方式)execution(String com.shaoming.service.MyService.service(String))
获取到的操作模块: 测试方法模块
获取到的操作类型: 测试方法类型
获取到的操作说明: 测试方法的操作说明
=============注解方式==================
=============环绕通知的操作开始获取一些信息================
环绕通知:execution(String com.shaoming.service.MyService.service(String))
前置通知:execution(String com.shaoming.service.MyService.service(String))
获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
获取的方法名称:service
获取参数的名称[str]
获取参数的类型[class java.lang.String]
获取方法的返回值的类型:class java.lang.String
Myservice的Service方法执行业务方法开始执行------------,方法的参数为: 我是参数的值
返回通知execution(String com.shaoming.service.MyService.service(String))
====返回通知===获取方法名称service
=====返回通知====获取方法返回值的类型class java.lang.String
=====返回通知中获取方法的返回值: 方法的返回值是方法的参数,这个方法的参数是: 我是参数的值
后置通知execution(String com.shaoming.service.MyService.service(String))
======环绕通知====获取请求的类名(去类名:包名+类名)com.shaoming.service.MyService
======环绕通知====获取方法名的全路径(包名+类名+方法名):com.shaoming.service.MyService.service
======环绕通知====获取的方法名称:service
======环绕通知====获取参数的名称[str]
======环绕通知====获取参数的类型[class java.lang.String]
======环绕通知====获取方法的返回值的类型:class java.lang.String
方法的返回值是方法的参数,这个方法的参数是: 我是参数的值
8.小总结(踩坑记录)
其他方法和环绕通知混用时候,环绕方法的返回值一定要写
还有声明的执行目标方法(proceed = proceedingJoinPoint.proceed()😉
反正参照我以上的方法就可以,否者会出现问题
否则别的通知将会不生效,只有环绕通知(@Aroud)生效
总结
spring的两大核心(IOC和AOP)一定要灵活运用
AOP要重视起来,因为springboot不少实现都是基于aop的
个人csdn博客网址:https://blog.csdn.net/shaoming314
个人博客网址:www.shaoming.club
oming.service.MyService.service
==环绕通知获取的方法名称:service
==环绕通知获取参数的名称[str]
==环绕通知获取参数的类型[class java.lang.String]
==环绕通知获取方法的返回值的类型:class java.lang.String
方法的返回值是方法的参数,这个方法的参数是: 我是参数的值
### 8.小总结(踩坑记录)
> 其他方法和环绕通知混用时候,环绕方法的返回值一定要写
>
> 还有声明的执行目标方法(proceed = proceedingJoinPoint.proceed();)
>
> 反正参照我以上的方法就可以,否者会出现问题
>
> 否则别的通知将会不生效,只有环绕通知(@Aroud)生效
## 总结
`spring的两大核心(IOC和AOP)一定要灵活运用`
`AOP要重视起来,因为springboot不少实现都是基于aop的`
个人csdn博客网址:https://blog.csdn.net/shaoming314
[外链图片转存中...(img-h7YdQPgJ-1609493237876)]
个人博客网址:www.shaoming.club
![halo](https://img-blog.csdnimg.cn/img_convert/c2c1dd5b4b71e544d16229a48274dce9.png)