前言1

之前已经开过一个文章描述过在java中使用注解的大概流程。我们可以看到在java中的注解使用是相当繁琐的,需要自己去定义注解,然后在用一个外部方法来获取反射并获取具体的执行流程。那么我们肯定是希望能够以一种尽量简单快捷的方式来进行注解功能的开发。于是便有了aspect+注解的双剑合璧。

前言2

其实aspect并不是注解的特有的,aspect的功能本质上就是spring所倡导的依赖反射(AOP)的一种使用。只不过注解是一种很适合作一段代码的公用逻辑的锚点,然后在从某个方法中注入一个公共逻辑。但实际上,要用使用AOP并不需要使用到注解,直接用aspect方法对某个包下的部分或所有方法都共用所有方法也是可以的。但是这个例子将会以注解的方式来讲解,达到触类旁通的效果。
前言说完了,接下来就实践吧。

实践

依赖

现在spring-aop实际上不包含在springboot的starter包中,需要额外进行引用。

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

声明一个注解

结合之前的文章大家都知道如何声明一个注解了,这里就给大家再复制一次

package com.study.springcloud.config.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataCheck {
    String[] params() default {};
}

在这里我们将注解的类型定义为method级别并且将运行时定义为runtime,因为我们是是需要在运行期间进行注入的。

声明一个注入

先看代码

package com.study.springcloud.config.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class DataCheckAspect {
    @Pointcut("@annotation(com.study.springcloud.config.annotation.DataCheck)")
    public void pointc() {};
    @Before("pointc()")
//    @Before("*")
    public void  dataCheckInter(JoinPoint joinPoint){
        Object[] obj = joinPoint.getArgs();
        String[] params = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        System.out.println(obj[0].toString());
        System.out.println("hello annotation");
    }
    @Around("pointc()")
    public String dataCheckAround(ProceedingJoinPoint pjd) throws Throwable {
        Object objt = pjd.proceed();
        System.out.println(objt.toString());
        Object[] obj = pjd.getArgs();
        System.out.println("around");
        return objt.toString();
    }
}

上面代码中,@Component,@Slf4j都是我们的老朋友了,就不再赘述。@Aspect就是声明这是一个Aspect方法。
然后在代码执行中我们看到一个@Pointcut注解,这里是声明我们要将我们的方法注入到哪里。这里我们注入到的地方是我们刚才声明到的注解,并且声明一个空方法,这个也是一个类似于锚点的功能,给下面的@Before,@Around等通知方法的一个入口。

通知

这个部分其实aspect方法中的一个精华所在点,或者说其实整个aspect的实现就是靠通知来实现的。apsect有5中通知点

  1. @Before 在执行方法前执行
  2. @After 在执行方法后,但在return前执行
  3. @AfterRunning 在执行方法后,且在返回结果后执行
  4. @AfterTrhowing 在方法抛出异常后执行
  5. @Around 环绕执行

从名字都能很简单的理解1,2,3,4种的执行方式,但唯独对第5中环绕执行不知道是什么鬼。我知道你很急,但总之先别急。我们先回到上面定义的@Before和@After的代码中

@Before()
public void  dataCheckInter(JoinPoint joinPoint){}
@Around()
public String dataCheckAround(ProceedingJoinPoint pjd){}

我们看到,在@Before中我们传进了一个JoinPoint,其实这个就是Aspect的切点类,他会将我们传入的切面内容作为一个类来给到我们,然后我们可以通过内部的getArgs等方法来活得对应的参数。然后在写入具体的逻辑。
好,我们来看到@Around中,我们传入的是一个ProceedingJoinPoint,和上面的JoinPoint相比多一个Proceeding的修饰词,表达的是这个JoinPoint是可以被执行的。这个也道出了@Around 环绕方式的一个最核心的不同点。环绕通知的方式其实把整个切面都劫持了。像@Before我们只能控制方法执行前的部分,但是@Around我们是可以整个方法的声明周期。在这方法中,如果我们不调用ProceedingJoinPoint.proceed()方法,方法会不运行。如果我们不return,上层方法也会取不到返回值。
所以我们说Around是最灵活的一种注入,但其实也是最危险的一种注入。因为用这个注解我们完全劫持目标方法来执行我们定义的逻辑。
大家具体可以参考切点表达式中关于每个切点类型的具体应用。

收尾

惯例我们写一个controller来看看效果

package com.study.springcloud.controller;

import com.study.springcloud.config.annotation.DataCheck;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AnnotationController {
    @PostMapping("/annotation")
    @DataCheck
    public String ant(@RequestBody String request){

        System.out.println("hello");
        return "hello";
    }
}

控制台输出

hello annotation
hello
hello
around

我们可以很清楚的看到,我们注入的输出已经成功的打印到出控制台了。收工