注意1: spring-aop仅支持在方法层面中的切面,如果想支持他更加细粒化的切面(比如字段)使用AspectJ,官方文档1、官方文档2
注意2: 对于多个切面都指向同一个切点问题,如果切面没有实现Ordered接口或者添加@Order注解定义切面执行优先级的情况下,切面的执行顺序是不固定的,官方文档
官网文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn
关键字作用文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-designators
切点定义简单demo文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples
切点定义详细讲解demo文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-advice
切点定义
//下面两条作用都是一样:都是匹配代表controller包下类中的方法切点
execution(* top.linruchang.springbooottest.controller.*.*(..))
within(top.linruchang.springbooottest.controller..*)
//&&用法:必须是controller包下的方法 且 必须是TestController里面的testDict2方法
execution(* top.linruchang.springbooottest.controller.*.*(..)) && execution(public * top.linruchang.springbooottest.controller.TestController.testDict2(..))
官网文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj-advice-params
ControllerAop.java
@Aspect
@Component
public class ControllerAop {
@Pointcut("! bean(basicErrorController)")
public void pointcut4(){}
@Around(value = "bean(*Controller) && pointcut4()")
public Object aroundMethod(JoinPoint proceedingJoinPoint) throws Throwable {
Console.log("\n=====================");
Console.log("切点执行前");
Signature signature = proceedingJoinPoint.getSignature();
Console.log("被ControllerAop横切的方法名:{},{}",proceedingJoinPoint.getTarget().getClass().getName(),signature.getName());
Object result = ((ProceedingJoinPoint)proceedingJoinPoint).proceed();
Console.log("切点执行后");
return result;
}
}
文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
SpringBoootTestApplication.java
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass=true,exposeProxy=true) //支持cglib代理以及当前线程获取获取当前的代理类(AOPContext)
public class SpringBoootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoootTestApplication.class, args);
}
}
TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("testDict")
public Object testDict(HttpServletRequest httpServletRequest) {
Console.log("TestController的testDict被执行");
return Dict.create()
.set("name", "lrc")
.set("year", 20);
}
@GetMapping("testDict2")
public Object testDict2(HttpServletRequest httpServletRequest) {
Console.log("TestController的testDict2被执行");
return Dict.create()
.set("name", "lrc2")
.set("year", 202);
}
}
切点定义详细讲解demo文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-advice
ControllerAop.java
@Aspect
@Component
public class ControllerAop {
@Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
public void pointcut() {}
@Around("pointcut()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Console.log("切点执行前");
Object result = proceedingJoinPoint.proceed();
Console.log("切点执行后");
return result;
}
}
//=================上下两种切点定义是一样的,都是同样的效果===================================
@Aspect
@Component
public class ControllerAop {
@Around("execution(* top.linruchang.springbooottest.controller.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Console.log("切点执行前");
Signature signature = proceedingJoinPoint.getSignature();
Console.log("被ControllerAop横切的方法名:{}",signature.getName());
Object result = proceedingJoinPoint.proceed();
Console.log("切点执行后");
return result;
}
}
此demo的切点: 必须是controller包下的方法 且 必须是TestController里面的testDict2方法
@Aspect
@Component
public class ControllerAop {
@Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
public void pointcut() {}
@Pointcut("execution(public * top.linruchang.springbooottest.controller.TestController.testDict2(..))")
public void pointcut2() {}
@Pointcut("pointcut() && pointcut2()")
public void pointcut3(){}
//下面三个切点定义都是一样,如果不想定义任何其他切点方法,直接使用第二种即可,当然还可直接将前面的三个pointcut、pointcut2、pointcut3方法直接删掉即可
// @Around("pointcut2() && pointcut()")
// @Around("execution(* top.linruchang.springbooottest.controller.*.*(..)) && execution(public * top.linruchang.springbooottest.controller.TestController.testDict2(..))")
@Around("pointcut3()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Console.log("\n=====================");
Console.log("切点执行前");
Signature signature = proceedingJoinPoint.getSignature();
Console.log("被ControllerAop横切的方法名:{}",signature.getName());
Object result = proceedingJoinPoint.proceed();
Console.log("切点执行后");
return result;
}
}
注意: 如果不知道某个类在Spring容器中的Bean名字,可以使用org.springframework.beans.factory.ListableBeanFactory#getBeanNamesForType(java.lang.Class>)进行查看获取
此demo的切点: Spring管理的Bean名是Controller结尾的类所有方法 且 不是BasicErrorController的方法,注意BasicErrorController在Spring容器中的名字的basicErrorController而不是BasicErrorController
@Aspect
@Component
public class ControllerAop {
@Pointcut("! bean(basicErrorController)")
public void pointcut4(){}
@Around("bean(*Controller) && pointcut4()")
// @Around("bean(*Controller) && !bean(basicErrorController)")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Console.log("\n=====================");
Console.log("切点执行前");
Signature signature = proceedingJoinPoint.getSignature();
Console.log("被ControllerAop横切的方法名:{},{}",proceedingJoinPoint.getTarget().getClass().getName(),signature.getName());
Object result = proceedingJoinPoint.proceed();
Console.log("切点执行后");
return result;
}
}
注意: 个人不建议使用那么细粒化的注解,由于注解时机太多。建议使用@around万金油注解代表任意切面时机,进行替代
TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("testDict2")
public Object testDict2(@RequestParam(defaultValue = "1") Integer num, HttpServletRequest httpServletRequest) {
Console.log("===TestController的testDict2被执行");
int num2 = 1 / num;
return Dict.create()
.set("name", "lrc")
.set("year", 20);
}
}
ControllerAop2.java
@Aspect
@Component
public class ControllerAop2 {
@Pointcut("! bean(basicErrorController)")
public void pointcut4(){}
@Pointcut("bean(*Controller) && pointcut4()")
public void pointcut5(){}
public String classMethodName(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
if(signature instanceof MethodSignature) {
String declaringTypeName = signature.getDeclaringTypeName();
String name = signature.getName();
return StrUtil.format("{}.{}",declaringTypeName,name);
}
return null;
}
/**
* 切点执行前
* @param joinPoint
*/
@Before("pointcut5()")
public void before(JoinPoint joinPoint) {
Console.log("\n=====================");
Console.log("【{}】before:切点执行前", classMethodName(joinPoint));
}
/**
* 切点正常执行完
* @param joinPoint
* @param pointResultValue 切点执行过程中导致切点执行终止的异常
*/
@AfterReturning(value = "pointcut5()", returning = "pointResultValue")
public void afterReturning(JoinPoint joinPoint, Object pointResultValue) {
Console.log("【{}】afterReturning:切点正常执行结束【{}】" , classMethodName(joinPoint),pointResultValue);
}
/**
* 切点不正常执行完(有异常抛出)
* @param joinPoint
* @param pointThrowException
*/
@AfterThrowing(value = "pointcut5()",throwing = "pointThrowException")
public void afterThrowing(JoinPoint joinPoint,Throwable pointThrowException) {
Console.log("【{}】afterThrowing:切点执行发生异常【{}】", classMethodName(joinPoint),ExceptionUtil.stacktraceToOneLineString(pointThrowException));
}
/**
* 处理完:@afterReturning、@afterThrowing的增强方法后执行的逻辑
* @param joinPoint
*/
@After("pointcut5()")
public void after(JoinPoint joinPoint) {
Console.log("【{}】after:切点最终执行结束", classMethodName(joinPoint));
}
}
当前demo切面:controller包下的方法 且 切点方法第一个参数是Integer类型
ControllerAop3.java
@Aspect
@Component
public class ControllerAop3 {
@Pointcut("execution(* top.linruchang.springbooottest.controller.TestController.testDict2(..))")
public void pointcut6(){}
@Around(value = "pointcut6() && args(num2,..)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, Integer num2) throws Throwable {
Console.log("\n==================");
Console.log("around执行切面方法前:切面方法其中一个入参值(来自方法参数注入):num[{}]", num2);
Console.log("around执行切面方法前:切面方法入参值(来自切面信息获取):", proceedingJoinPoint.getArgs());
Object proceedResult = proceedingJoinPoint.proceed();
Console.log("around执行切面方法结束");
return proceedResult;
}
}
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("testDict2")
public Object testDict2(@RequestParam(defaultValue = "1") Integer num, HttpServletRequest httpServletRequest) {
Console.log("===TestController的testDict2被执行");
int num2 = 1 / num;
return Dict.create()
.set("name", "lrc")
.set("year", 20);
}
}
LogInfo.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogInfo {
/**
* 日志内容
* @return
*/
String value() default "";
}
当前demo切面:controller包下的方法 且 切点方法被@Loginfo注解修饰
ControllerAop4.java
@Aspect
@Component
public class ControllerAop4 {
// @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
@Pointcut("within(top.linruchang.springbooottest.controller..*)")
public void pointcut6(){}
@Around(value = "pointcut6() && @annotation(logInfo)")
// @Around(value = "@annotation(logInfo)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo) throws Throwable {
Console.log("\n==================");
Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
String logInfoValue = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(LogInfo.class).value();
Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
Object proceedResult = proceedingJoinPoint.proceed();
Console.log("around执行切面方法结束");
return proceedResult;
}
}
TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@LogInfo("功能:测试AOP")
@GetMapping("testDict")
public Object testDict(@RequestParam(defaultValue = "1") Integer num, HttpServletRequest httpServletRequest) {
Console.log("TestController的testDict被执行");
int num2 = 1 / num;
return Dict.create()
.set("name", "lrc")
.set("year", 20);
}
}
LogInfo.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogInfo {
/**
* 日志内容
* @return
*/
String value() default "";
}
当前demo切面:controller包下的方法 且 切点方法必须含有至少两个入参同时切点方法被@Loginfo注解修饰
ControllerAop5
@Aspect
@Component
public class ControllerAop5 {
@Pointcut("! bean(basicErrorController)")
public void pointcut4(){}
@Pointcut("bean(*Controller) && pointcut4()")
public void pointcut5(){}
public String classMethodName(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
if(signature instanceof MethodSignature) {
String declaringTypeName = signature.getDeclaringTypeName();
String name = signature.getName();
return StrUtil.format("{}.{}",declaringTypeName,name);
}
return null;
}
// @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
@Pointcut("within(top.linruchang.springbooottest.controller..*)")
public void pointcut6(){}
@Around(value = "pointcut6() && args(num4,num5,..) && @annotation(logInfo)")
// @Around(value = "@annotation(logInfo)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo, Object num4, Object num5) throws Throwable {
Console.log("\n==================");
Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
Console.log("around执行切面方法前:切面方法的入参(来自方法参数注入):{},{}", num4 ,num5);
String logInfoValue = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(LogInfo.class).value();
Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
Console.log("around执行切面方法前:切面方法的入参(来自切面信息获取):", proceedingJoinPoint.getArgs());
Object proceedResult = proceedingJoinPoint.proceed();
Console.log("around执行切面方法结束");
return proceedResult;
}
}
TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@LogInfo("功能:测试AOP3")
@GetMapping("testDict3")
public Object testDict3(@RequestParam(defaultValue = "1") Integer num, @RequestParam(defaultValue = "1") Integer num2, HttpServletRequest httpServletRequest) {
Console.log("===TestController的testDict2被执行");
int newNum = 1 / num;
return Dict.create()
.set("name", "lrc")
.set("year", 20);
}
}
ControllerAop6.java
@Aspect
@Component
public class ControllerAop6 {
// @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
@Pointcut("within(top.linruchang.springbooottest.controller..*)")
public void pointcut6(){}
@Around(value = "pointcut6() && target(targetBean) && this(thisBean)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, Object targetBean, Object thisBean) throws Throwable {
Console.log("\n==================");
Console.log("around执行切面方法前:切面方法的所在的对象实例(来自方法参数注入):{}========={}",targetBean.getClass(),targetBean);
Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自方法参数注入):{}========={}",thisBean.getClass(),thisBean);
Console.log("around执行切面方法前:切面方法的所在的对象实例(来自切面信息获取):{}========={}", proceedingJoinPoint.getTarget().getClass(),proceedingJoinPoint.getTarget());
Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):{}========={}", proceedingJoinPoint.getThis().getClass(),proceedingJoinPoint.getThis());
Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):targetBean与proceedingJoinPoint.getTarget()是否是同一个对象实例========={}", proceedingJoinPoint.getTarget() == targetBean);
Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):thisBean与proceedingJoinPoint.getThis()是否是同一个对象实例========={}", proceedingJoinPoint.getThis() == thisBean);
Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):thisBean与targetBean是否是同一个对象实例========={}", targetBean == thisBean);
Object proceedResult = proceedingJoinPoint.proceed();
Console.log("around执行切面方法结束");
return proceedResult;
}
}
TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@LogInfo("功能:测试AOP4")
@GetMapping("testDict4")
public Object testDict4(@RequestParam(defaultValue = "1") Integer num) {
Console.log("===TestController的testDict4被执行");
int newNum = 1 / num;
return Dict.create()
.set("name", "lrc")
.set("year", 20);
}
}
EnhaceAnnotationUtil.java
package top.linruchang.springbooottest.utils;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ModifierUtil;
import cn.hutool.core.util.ReflectUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author LinRuChang
* @version 1.0
* @date 2022/09/14
* @since 1.8
**/
public class EnhaceAnnotationUtil extends AnnotationUtil {
/**
* 注解的值转成Map
*
* @param currentAnnotation 注解实例
* @return
*/
public static Map<String, Object> getAnnotationValueMap(Annotation currentAnnotation) {
return Optional.ofNullable(currentAnnotation)
.map(annotation -> {
Method[] objectMethods = ReflectUtil.getMethods(Object.class);
final List<Method> methods = ReflectUtil.getPublicMethods(annotation.getClass(), t -> {
if (ArrayUtil.isEmpty(t.getParameterTypes())) {
// 只读取无参方法
final String name = t.getName();
return (false == "hashCode".equals(name))
&& (false == "toString".equals(name))
&& (false == "annotationType".equals(name))
&& (false == ArrayUtil.contains(objectMethods, t));
}
return false;
});
final HashMap<String, Object> result = new HashMap<>(methods.size(), 1);
for (Method method : methods) {
result.put(method.getName(), ReflectUtil.invoke(annotation, method));
}
return result;
}).orElse(new HashMap<>());
}
}
TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@LogInfo("功能:测试AOP9")
@GetMapping("testDict9")
public Object testDict9(Article article, HttpServletRequest httpServletRequest) {
Console.log("===TestController的testDict9被执行");
return article;
}
}
Article.java
@Data
@ToString(callSuper=true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("article")
public class Article extends BaseDB implements Serializable {
private static final long serialVersionUID = -4714261187453073302L;
/** 文章标题 */
private String title;
/** 文章内容 */
private String content;
/** 缩略文章,最大200字符,从content抽出的纯文本内容 参考{@link Article#ABBREVIATION_CONTENT_CHAR_MAX}*/
private String abbreviationContent;
/** 文章被查看次数 */
private Integer checkNum;
/** 当前文章被点赞数 */
private Integer likeNum;
/** 当前文章不喜欢数 */
private Integer notLikeNum;
/** 文章是否违规:0未违规 1违规 - 违规不可显示 */
private Integer isViolation;
/** 创建者 - 冗余字段 - user表的nickName */
private String createBy;
/**列表文章的图片显示 - 如果指定则使用指定的图片(功能未做),没有指定则使用文章第一张图片,在没有则使用文章中有图标则使用目录分裂的图片*/
private String imgUrl;
/**文章内部图片*/
private String imgUrls;
/**文章内容类型:1富文本 2Markdown 3留空*/
private String type;
/**文章状态;-1违规 0草稿 1发布且公开 2发布且私密*/
private String status;
//================常亮======================
//文章状态
public final static String STATUS_VIOLATION = "-1";
public final static String STATUS_DRAFT = "0";
public final static String STATUS_PUBLISH_PUBLIC = "1";
public final static String STATUS_PUBLISH_PRIVATE = "2";
//文章编辑类型
public final static String TYPE_MARKDOWN = "1";
public final static String TYPE_RICH = "2";
public final static Integer ABBREVIATION_CONTENT_CHAR_MAX = 200;
//=====================================
/**文章标签ID*/
@TableField(exist = false)
private String tagIds;
/**文章所属用户*/
@TableField(exist = false)
private String userId;
/**文章所属用户昵称*/
@TableField(exist = false)
private String nickName;
/** 是否点赞该文章 1点赞 0不点赞 */
@TableField(exist = false)
private Integer isLike;
/** 是否踩这篇文章 1踩文章 0不睬文章 */
@TableField(exist = false)
private Integer isNotLike;
/** 评论数 */
@TableField(exist = false)
private Integer commentNum;
/** 文章创作者-头像图片地址 */
@TableField(exist = false)
private String headUrl;
}
当前demo切面:controller包下的方法 且 切点方法第一个参数的类型Class被@TableName修饰,且切点方法被@LogInfo注解修饰
ControllerAop9.java
@Aspect
@Component
public class ControllerAop9 {
@Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
// @Pointcut("within(top.linruchang.springbooottest.controller..*)")
public void pointcut6() {
}
@Around(value = "@annotation(logInfo) && @args(tableName,..)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo, TableName tableName) throws Throwable {
Console.log("\n==================");
Console.log("around执行切面方法前");
Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
Method currentJointPointMethod = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
String logInfoValue = currentJointPointMethod.getAnnotation(LogInfo.class).value();
Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自方法参数注入):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(tableName));
TableName firstArgsClassTableName = AnnotationUtil.getAnnotation(proceedingJoinPoint.getArgs()[0].getClass(), TableName.class);
Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自切面信息获取):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(firstArgsClassTableName));
Object proceedResult = proceedingJoinPoint.proceed();
Console.log("around执行切面方法结束");
return proceedResult;
}
}
注意: @target、@within功能差不多,都是取切点类上的注解。但是@within可以取目标类的父类注解,所以建议使用@within,因为覆盖范围更广
Article.java
@Data
@ToString(callSuper=true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("article")
public class Article extends BaseDB implements Serializable {
private static final long serialVersionUID = -4714261187453073302L;
/** 文章标题 */
private String title;
/** 文章内容 */
private String content;
/** 缩略文章,最大200字符,从content抽出的纯文本内容 参考{@link Article#ABBREVIATION_CONTENT_CHAR_MAX}*/
private String abbreviationContent;
/** 文章被查看次数 */
private Integer checkNum;
/** 当前文章被点赞数 */
private Integer likeNum;
/** 当前文章不喜欢数 */
private Integer notLikeNum;
/** 文章是否违规:0未违规 1违规 - 违规不可显示 */
private Integer isViolation;
/** 创建者 - 冗余字段 - user表的nickName */
private String createBy;
/**列表文章的图片显示 - 如果指定则使用指定的图片(功能未做),没有指定则使用文章第一张图片,在没有则使用文章中有图标则使用目录分裂的图片*/
private String imgUrl;
/**文章内部图片*/
private String imgUrls;
/**文章内容类型:1富文本 2Markdown 3留空*/
private String type;
/**文章状态;-1违规 0草稿 1发布且公开 2发布且私密*/
private String status;
//================常亮======================
//文章状态
public final static String STATUS_VIOLATION = "-1";
public final static String STATUS_DRAFT = "0";
public final static String STATUS_PUBLISH_PUBLIC = "1";
public final static String STATUS_PUBLISH_PRIVATE = "2";
//文章编辑类型
public final static String TYPE_MARKDOWN = "1";
public final static String TYPE_RICH = "2";
public final static Integer ABBREVIATION_CONTENT_CHAR_MAX = 200;
//=====================================
/**文章标签ID*/
@TableField(exist = false)
private String tagIds;
/**文章所属用户*/
@TableField(exist = false)
private String userId;
/**文章所属用户昵称*/
@TableField(exist = false)
private String nickName;
/** 是否点赞该文章 1点赞 0不点赞 */
@TableField(exist = false)
private Integer isLike;
/** 是否踩这篇文章 1踩文章 0不睬文章 */
@TableField(exist = false)
private Integer isNotLike;
/** 评论数 */
@TableField(exist = false)
private Integer commentNum;
/** 文章创作者-头像图片地址 */
@TableField(exist = false)
private String headUrl;
}
Impact.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//特别注意:这里很关键有没这个注解,关系到@within是否AOP可以匹配到父类的@Impact上
@Inherited
public @interface Impact {
String value() default "";
}
TestController.java
@RestController
@RequestMapping("/test")
@Impact("作用:用于测试的控制器")
public class TestController {
@LogInfo("功能:测试AOP9")
@GetMapping("testDict9")
public Object testDict9(Article article, HttpServletRequest httpServletRequest) {
Console.log("===TestController的testDict9被执行");
return article;
}
}
Test2Controller.java
@Impact("作用:公共的控制器")
public class BaseController {
}
@RestController
@RequestMapping("/test2")
public class Test2Controller extends BaseController {
@LogInfo("功能:测试AOP9")
@GetMapping("testDict9")
public Object testDict9(Article article, HttpServletRequest httpServletRequest) {
Console.log("===Test2Controller的testDict9被执行");
return article;
}
}
当前demo切面:controller包下的方法 且 切点方法第一个参数的类型Class被@TableName修饰,且切点方法被@LogInfo注解修饰,同时切点所在的目标类被@Impact注解修饰
ControllerAop10.java
@Aspect
@Component
public class ControllerAop10 {
@Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
public void pointcut6() {
}
// @Around(value = "@annotation(logInfo) && @args(tableName,..) && @target(impact)") //当前类无目标注解但继承的类有目标注解不会匹配
@Around(value = "@annotation(logInfo) && @args(tableName,..) && @within(impact)") //继承的类有目标注解(@Impact必须有@Inherited修饰才行)也行
public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo, TableName tableName, Impact impact) throws Throwable {
Console.log("\n==================");
Console.log("around执行切面方法前");
Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
Method currentJointPointMethod = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
String logInfoValue = currentJointPointMethod.getAnnotation(LogInfo.class).value();
Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自方法参数注入):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(tableName));
TableName firstArgsClassTableName = AnnotationUtil.getAnnotation(proceedingJoinPoint.getArgs()[0].getClass(), TableName.class);
Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自切面信息获取):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(firstArgsClassTableName));
Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自方法参数注入):impact内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(impact));
Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自切面信息获取):impact内容[{}]", AnnotationUtil.getAnnotationValueMap(proceedingJoinPoint.getTarget().getClass(),Impact.class));
Object proceedResult = proceedingJoinPoint.proceed();
Console.log("around执行切面方法结束");
return proceedResult;
}
}
测试2:@within形式 == 代码不用动,只要将注解Impact的@Inherited删掉即可
测试3:@target形式 == 将ControllerAop10的注释的切点放开,关闭原先切点的即可
文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-schema
注意: 有需要的自行去官网学习,用XML配置Bean、AOP已经淘汰,遇到在去官网看即可,SpringBoot只要会基于注解方式的AOP即可