SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)

文章目录

    • 3. Spring-AOP
      • 文档入口
      • 概述
        • 概念
        • 执行时机(Advice)
        • 切点表达式的关键字
        • 参数内置可实例化的类
      • 用法
        • 方式1:基于注解形式
          • 使用方式
            • 简单切点
            • 复杂切点(&&)
            • 切点(Spring-AOP特有关键字)- Bean(Bean在Spring容器中的名字或ID)
            • 更细粒化的AOP执行时机注解- @Before、@AfterReturning、@AfterThrowing、@After
            • 执行时机的方法注入切面方法的入参 - args(当前advice方法中的参数名,..)
            • 执行时机的方法注入修饰切点方法的注解 - @annotation(当前advice方法中的注解类型的参数名,..)
            • 执行时机的方法注入修饰切点方法的注解、以及切点方法的入参
            • 切点表达式的target、this关键字使用(很少用不想看的直接忽略即可)
            • 执行时机的方法注入修饰切点方法入参Bean的Class上的注解 - @args(advice方法入参名字)
            • 执行时机的方法注入修饰切点方法所在的Class上的注解 - @target(advice方法入参名字)、@within(advice方法入参名字)
        • 方式2:基于XML形式(淘汰)

3. Spring-AOP

注意1: spring-aop仅支持在方法层面中的切面,如果想支持他更加细粒化的切面(比如字段)使用AspectJ,官方文档1、官方文档2

注意2: 对于多个切面都指向同一个切点问题,如果切面没有实现Ordered接口或者添加@Order注解定义切面执行优先级的情况下,切面的执行顺序是不固定的,官方文档

文档入口

官网文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第1张图片

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第2张图片

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第3张图片

概述

文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn

使用AOP方式
1. 基于XML形式(spring独有):schema-based approach
2. 基于注解方式(AspectJ包的注解):@AspectJ annotation style

概念

概念
Join point(连接点):需要被横切的方法位置定义(比如某个controller方法、含有某个注解的业务的方法或者需要被日志记录的方法等等)
pointcut(切点):其实就是用什么【 切点表达式】来表示【连接点】==可以理解成连接点、切点是一个东西
@annotation(logInfo):某个业务方法含有自定义的@LogInfo注解
execution(* com.xyz.myapp.service.*.*(..)):service包下的类某个业务方法
Aspect(切面):增强的内容具体实现类:比如自定义的LogAop类 == 很像Spring的拦截器HandlerInterceptor
Advice(通知):增强的内容在【连接点】的执行时机 == 很像Spring的拦截器HandlerInterceptor的方法比如preHandle、postHandle
Introduction(引入):其实就是【Advice执行时机】里面的具体的增强内容,可以简单理解成跟【Advice】一样的东西
Weaving(织入):其实就是将增强的类(切面)与被增强的业务方法(切点)在编译时将两者结合生成一个新的类(代理类)== 代码层两者分开,运行时两者已经结合在一起

执行时机(Advice)

建议用这个
通知(Advice)类别 == 增强的时机
Before:在被增强的业务方法(切点)之前执行 == 执行时机顺序1
After returning:在被增强的业务方法(切点)【正常执行完】之后(不抛出任何异常)== 执行时机顺序2.1
After throwing:在被增强的业务方法(切点)执行过程【某种原因导致抛出异常】后 == 执行时机顺序2.2
After:不管被增强的业务方法(切点)是正常执行结束或者抛出异常导致执行结束之后 == 执行时机顺序3
Around:囊括前面的四种执行时机(Advice)

切点表达式的关键字

关键字作用文档: 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

常用
很少用
很少用
较少用
demo1
demo2
切点表达式关键字
execution:匹配某些业务方法
within:在spring-aop中仅用匹配某些业务方法(跟execution很像)
this:advice方法的入参注入当前切面代理对象实例 == 建议使用org.aspectj.lang.JoinPoint#getThis替代
target:advice方法的入参注入被代理对象实例 == 建议使用org.aspectj.lang.JoinPoint#getTarget替代
args:advice方法的入参注入当前切面方法的入参 == 建议使用org.aspectj.lang.JoinPoint#getArgs替代
bean(spring管理的Bean名字或id):spring-aop特有的关键字
bean(basicErrorController)
bean(*Controller)
@target:切面方法所在的目标类的注解==仅以当前类的注解为准(父类有但当前类没有也匹配不到)
@within:切面方法所在的目标类或者目标类的父类有目标注解(如果是在父类上,目标注解必须有@Inherited修饰才行)
@args:切面方法入参的Class被修饰的注解 == 说的比较抽象直接看下面的demo就懂了
@annotation:匹配含有某个注解的目标方法
通配符
*:任意类名、方法名
&&:and
||:or
!:否定
.:下一层层级
..:任意层级

切点定义

//下面两条作用都是一样:都是匹配代表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

接口
JoinPoint的继承接口
接口
自动注入切面方法参数(一个切面方法只能填其中一个,多个则报错)
JoinPoint:切点信息
ProceedingJoinPoint:切点信息
JoinPoint.StaticPart:切点信息

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第4张图片

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;
    }

}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第5张图片

用法

方式1:基于注解形式

文档: 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;
    }

}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第6张图片

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第7张图片

复杂切点(&&)

此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;
    }

}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第8张图片

切点(Spring-AOP特有关键字)- Bean(Bean在Spring容器中的名字或ID)

注意: 如果不知道某个类在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;
    }

}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第9张图片

更细粒化的AOP执行时机注解- @Before、@AfterReturning、@AfterThrowing、@After

注意: 个人不建议使用那么细粒化的注解,由于注解时机太多。建议使用@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));
    }

}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第10张图片

执行时机的方法注入切面方法的入参 - args(当前advice方法中的参数名,…)

当前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);
    }
}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第11张图片

执行时机的方法注入修饰切点方法的注解 - @annotation(当前advice方法中的注解类型的参数名,…)


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);
    }
}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第12张图片

执行时机的方法注入修饰切点方法的注解、以及切点方法的入参


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);
    }
}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第13张图片

切点表达式的target、this关键字使用(很少用不想看的直接忽略即可)

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);
    }

}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第14张图片

执行时机的方法注入修饰切点方法入参Bean的Class上的注解 - @args(advice方法入参名字)

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;
    }
}

SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第15张图片

执行时机的方法注入修饰切点方法所在的Class上的注解 - @target(advice方法入参名字)、@within(advice方法入参名字)

注意: @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;
    }
}


测试1:@within形式
SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第16张图片


测试2:@within形式 == 代码不用动,只要将注解Impact的@Inherited删掉即可
SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第17张图片


测试3:@target形式 == 将ControllerAop10的注释的切点放开,关闭原先切点的即可
SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)_第18张图片

方式2:基于XML形式(淘汰)

文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-schema

注意: 有需要的自行去官网学习,用XML配置Bean、AOP已经淘汰,遇到在去官网看即可,SpringBoot只要会基于注解方式的AOP即可

你可能感兴趣的:(SpringBoot,Java,SpringBoot,aop,切点表达式,关键字,demo演示)