Spring boot AOP 之 @Aspect 注解使用

AOP相关术语

  • 切面(Aspect):是指横切多个对象的关注点的一个模块化,事务管理就是J2EE应用中横切关注点的很好示例。在Spring AOP中,切面通过常规类(基本模式方法)或者通过使用了注解@Aspect的常规类来实现。

  • 连接点(Joint point):是指在程序执行期间的一个点,比如某个方法的执行或者是某个异常的处理。在Spring AOP中,一个连接点往往代表的是一个方法执行。

  • 通知(Advice):是指切面在某个特殊连接点上执行的动作。通知有不同类型,包括"around","before"和"after"通知。许多AOP框架包括Spring,将通知建模成一个拦截器,并且围绕连接点维持一个拦截器链。

  • 切入点(Pointcut):是指匹配连接点的一个断言。通知是和一个切入点表达式关联的,并且在任何被切入点匹配的连接点上运行(举例,使用特定的名字执行某个方法)。AOP的核心就是切入点表达式匹配连接点的思想。Spring默认使用AspectJ切入点表达式语言

  • 引入(Introduction):代表了对一个类型额外的方法或者属性的声明。Spring AOP允许引入新接口到任何被通知对象(以及一个对应实现)。比如,可以使用一个引入去使一个bean实现IsModified接口,从而简化缓存机制。(在AspectJ社区中,一个引入也称为一个inter-type declaration类型间声明) 目标对象(Target object):是指被一个或多个切面通知的那个对象。也指被通知对象("advised object"),由于Spring AOP是通过运行时代理事项的,这个目标对象往往是一个代理对象。

  • AOP 代理(AOP proxy):是指通过AOP框架创建的对象,用来实现切面合约的(执行通知方法等等)。在Spring框架中,一个AOP代理是一个JDK动态代理或者是一个CGLIB代理。

  • 织入(Weaving):将切面和其他应用类型或者对象连接起来,创骗一个被通知对象。这些可以在编译时(如使用AspectJ编译器)、加载时或者运行时完成。Spring AOP,比如其他纯Java AOP框架一般是在运行时完成织入。

AOP Advice相关术语

  • 前置通知(Before advice):在一个连接点之前执行的通知。但这种通知不能阻止连接点的执行流程(除非它抛出一个异常)

  • 后置返回通知(After returning advice):在一个连接点正常完成后执行的通知(如,如果一个方法没有抛出异常的返回)

  • 后置异常通知(After throwing advice):在一个方法抛出一个异常退出时执行的通知。

  • 后置(最终)通知(After(finally) advice):在一个连接点退出时(不管是正常还是异常返回)执行的通知。

  • 环绕通知(Around advice):环绕一个连接点的通知,比如方法的调用。这是一个最强大的通知类型。环绕通知可以在方法调用之前和之后完成自定义的行为。也负责通过返回自己的返回值或者抛出异常这些方式,选择是否继续执行连接点或者简化被通知方法的执行。

spring boot aop jar 包引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Spring AOP 在通用方法执行切面逻辑 代码示例

  • 定义切面
@Aspect
@Component
public class LogAspect1 {

  // com.pangugle.testspringboot.controller 包中所有的类的所有方法切面
  // @Pointcut("execution(public * com.pangugle.testspringboot.controller.*.*(..))")

  // 只针对 HelloController 类切面
  // @Pointcut("execution(public *
  // com.pangugle.testspringboot.controller.HelloController.*(..))")

  // 统一切点,对com.pangugle.testspringboot.controller及其子包中所有的类的所有方法切面
  //@Pointcut("execution(public * com.pangugle.testspringboot.controller..*.*(..))")

    @Pointcut("execution(*  com.pangugle.testspringboot.service.*.*(..))")
    public void pointcut() {}


    @Before("pointcut()")
    public void printParam(JoinPoint joinPoint){
        //获取请求的方法
        Signature sig = joinPoint.getSignature();
        String method = joinPoint.getTarget().getClass().getName() + "." + sig.getName();

        //获取请求的参数
        Object[] args = joinPoint.getArgs();
        //fastjson转换
        String params = FastJsonHelper.jsonEncode(args);

        //打印请求参数
        System.out.println(method + " : " + params);
    }

}

切点作用范围

  • 通过通配符

    @Pointcut("execution(public * com.pangugle.testspringboot.controller.*.*(..))")
    
  • 可通过通配符,利用 ..

    @Pointcut("execution(public * com.pangugle.testspringboot.controller..*.*(..))")
    
    和上面的区别是 ..
    
  • 只针对 特定类切面、 特定方法

    @Pointcut("execution(public * com.pangugle.testspringboot.controller.HelloController.*(..))")
    

代码解释

  • 我们在 LogAspect1 类上 添加了 @Aspect 注解, @Component 注解

    @Aspect 注解 在这里表示 LogAspect1 被定义为 aop的一个切面
    
    @Component 注解 表示 让spring 来管理 LogAspect1
    
  • @Pointcut("execution(* com.pangugle.testspringboot.service..(..))")

    1. @Pointcut 注解在 pointcut 方法上, 表明 poincut 为一个切点
    
    2. execution(*  com.pangugle.testspringboot.service.*.*(..))
    
        这个表示 在 com.pangugle.testspringboot.service 中的所有类都执行这个切点
    
  • @Before("pointcut()")

    1. @Before("pointcut()") 注解在 printParam 方法上
        表示 在 com.pangugle.testspringboot.service 中的所有类 方法执行之前先 执行
        printParam() 这个方法, 这个也叫 前置通知!
    

注意:

在使用过程中 com.pangugle.testspringboot.service 这个包,请替换成你们自己的包名,或着具体的类!

Spring AOP 在特定方法执行切面逻辑 代码示例

这个方式要 自定义 注解

  • 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

定义一个 MyLog 注解

  • 定义切面
@Aspect
@Component
public class LogAspect2 {

	// 针对特定方法
	@Pointcut("@annotation(myLog)")
	public void myPointcut(MyLog myLog) {

	}

	// @Around:环绕通知
	@Around("myPointcut(myLog)")
	public Object Around(ProceedingJoinPoint pjp, MyLog myLog) throws Throwable {
		System.out.println("around执行方法之前");
		Object object = pjp.proceed();
		System.out.println("around执行方法之后--返回值:" + object);
		return object;
	}

	// 前置通知
	@Before("myPointcut(myLog)")
	public void beforeMethod(JoinPoint joinPoint, MyLog myLog) {
		System.out.println("调用了前置通知");
	}

	// @AfterThrowing: 异常通知
	@AfterThrowing(value = "myPointcut(myLog)", throwing = "e")
	public void afterReturningMethod(JoinPoint joinPoint, MyLog myLog, Exception e) {
		System.out.println("调用了异常通知");
	}

	// @After: 后置通知
	@After("myPointcut(myLog)")
	public void afterMethod(JoinPoint joinPoint, MyLog myLog) {
		 System.out.println("调用了后置通知");
	}

	// @AfterRunning: 返回通知 rsult为返回内容
	@AfterReturning(value = "myPointcut(myLog)", returning = "result")
	public void afterReturningMethod(JoinPoint joinPoint, Object result, MyLog myLog) {
		System.out.println("调用了返回通知");
	}

}

代码解释

  • @Aspect 和 @Component 的作用 和 Spring AOP 在通用方法执行切面逻辑 代码示例 一样,这里不重复解释

  • @Pointcut("@annotation(myLog)")

    @Pointcut("@annotation(myLog)") 这个注解在 myPointcut 方法上:
    表示 myPointcut 方法是一个切点,并且有一个参数,这个要注意,在添加切面逻辑的时候一定带上这个参数
    
  • 剩下的就是切面各个生命周期的具体切点逻辑了

这里一定要注意:

注解带参数,名称一定要对应起来,

我们在上面 定义切点带上 参数 myLog, 所以各个切点逻辑要带上 MyLog myLog 这个参数!

这次说明,不要忘记参数! 不要忘记参数! 不要忘记参数!