4

SpringBoot中搭配AOP实现自定义注解 - 糖拌西红柿

 1 year ago
source link: https://www.cnblogs.com/TheGCC/p/16954775.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1 springBoot的依赖

确定项目中包含可以注解的依赖

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

2 自定义注解的步骤

在项目中自定义注解的步骤主要有两步,第一步:定义注解类,第二步:定义切面

2.1 定义注解类

直接创建 @interface 的类,使用注解@Target@Retention指定其适用范围及保留时长,如下:

@Target(ElementType.METHOD) // 指定注解的适用范围
@Retention(RetentionPolicy.RUNTIME) //指定运行时
public @interface ApiLog {

    String desc() default "";

    boolean timeSpan() default true;


}

​ 注解类的内容一般很简单,类似于Enum类一样,里面是简单的方法及属性

2.2 定义切面

通过@Aspect注解指定一个类,该类必须实现

@Component
@Aspect
@Slf4j(topic = "ApiLogNote")
public class ElasticSearchExecuteLog {

    @Around("@annotation(com.gcc.ApiLog)")
    public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        //获取被调用方法
        Method method = signature.getMethod();
        //取出被调用方法的注解,方便后续使用注解中的属性
        ApiLog loglinstener = method.getAnnotation(ApiLog.class);
        log.info("----------------------method[{}]start--------------------",method.getName());
        log.info("方法描述:{}",loglinstener.desc());
        log.info("参数 :{}",point.getArgs());
        long startTime = System.currentTimeMillis();
        Object proceed = point.proceed();
        long endTime = System.currentTimeMillis();
        log.info("耗时:{}ss",endTime-startTime);
        log.info("----------------------method[{}] end--------------------\n",method.getName())
        return proceed;
    }
}

2.3 使用注解

因为此例子使用的类型为METHOD即方法级的注解,直接在方法上使用即可:

    @ApiLog
    public JSONObject seachEsData(String indexName, SearchSourceBuilder searchSourceBuilder) {
        JSONObject resultMap = new JSONObject();
        .......
        return resultMap;
    }

3 知识点补充

3.1 关于Target注解补充

注解@Target常常配合枚举类ElementType来指定注解的作用位置,也叫合法位置,即你定义了一个注解,这个注解是类注解还是方法注解还是XX注解等,具体作用的范围,取决于@Target({ElementType.TYPE})中,ElementType的枚举值,在进行自定义枚举时,根据自己的需求,决定定义的注解是哪类层级使用的注解,例如上面的例子中,@ApiLog这个自定义的注解就是方法级的注解

ElementType的枚举值有

枚举值 含义
TYPE 类, 接口 (包括注解类型), 或 枚举 声明
FIELD 字段、包括枚举常量
METHOD 方法声明
PARAMETER 正式的参数声明
CONSTRUCTOR 构造函数的声明
LOCAL_VARIABLE 局部变量的声明
ANNOTATION_TYPE 注解类型的声明
PACKAGE 包声明

3.2 关于Retention注解补充

注解@Retention常常配合枚举类RetentionPolic来指定注解的各种策略,注解的保留时间,也就是何时生效,即你定义了一个注解,这个注解是编译时生效还是仅仅只是在代码中标记等等,具体作用的范围,取决于@Retention({RetentionPolic.TYPE})中,RetentionPolic的枚举值,在进行自定义枚举时,大多数都是使用RUNTIME(编译时生效)

RetentionPolic的枚举值

枚举值 含义
SOURCE 解只在源代码级别保留,编译时被忽略
CLASS 注解将被编译器在类文件中记录 , 但在运行时不需要JVM保留。这是默认的
RUNTIME 注解将被编译器记录在类文件中,在运行时保留VM,也是使用最多的(一般自定义均使用这个)

3.3 关于AOP的一些概念补充

这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程

1|0切面(Aspect)

切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。简单点理解,在SpringBoot中使用了Aspect注解的类就是切面

@Component
@Aspect
public class LogAspect {
}

1|0目标对象(target)

目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

在我们的例子中,即是使用了@ApiLog注解的地方

1|0连接点(JoinPoint)

程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:

  • 方法(表示程序执行点,即在哪个目标方法)
  • 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)

简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。

1|0切入点(PointCut)

切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。

//此处的匹配规则是 com.remcarpediem.test.aop.service包下的所有类的所有函数。
@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void pointcut() {
}

这里切入点的概念其实就是确定对哪些目标对象进行切面插入功能,一开始的例子是采用注解的方式来达到切入**点的作用

 @Around("@annotation(com.gcc.ApiLog)")

1|0通知(Advice)

通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。

// @Before说明这是一个前置通知,log函数中是要前置执行的代码,JoinPoint是连接点,
@Before("pointcut()")
public void log(JoinPoint joinPoint) { 
}

//@After 为后置通知

//@Around 为环绕通知

1|0织入(Weaving)

这里的织入概念是个动作,即Spring将前面的切面、连接点、切入点关联起来并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

1|0增强器(Adviser)

Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor由切入点和Advice组成。 Advisor这个概念来自于Spring对AOP的支撑,在AspectJ中是没有等价的概念的。Advisor就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个Bean表示,并且必须实现一个默认接口。

简单来讲,整个 aspect 可以描述为: 满足 pointcut 规则的 joinpoint 会被添加相应的 advice 操作

将上方通过注解使用切面的方式改写一下:

@Component
@Aspect
@Sl4fj
public class ElasticSearchExecuteLog {
 
  // 不使用注解,而通过基础的规则配置选择切入点,表达式是指com.gcc.controller
 // 包下的所有类的所有方法
 @Pointcut("execution(* com.gcc.controller..*(..))")
 public void aspect() {}
 
  // 通知,在符合aspect切入点的方法前插入如下代码,并且将连接点作为参数传递
 @Before("aspect()")
 public void log(JoinPoint joinPoint) { //连接点作为参数传入
   // 获得类名,方法名,参数和参数名称。
   Signature signature = joinPoint.getSignature();
   String className = joinPoint.getTarget().getClass().getName();
   String methodName = joinPoint.getSignature().getName();
   Object[] arguments = joinPoint.getArgs();
   MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
   String[] argumentNames = methodSignature.getParameterNames();
   StringBuilder sb = new StringBuilder(className + "." + methodName + "(");
   	for (int i = 0; i< arguments.length; i++) {
                  Object argument = arguments[i];
                  sb.append(argumentNames[i] + "->");
                  sb.append(argument != null ? argument.toString() : "null ");
   	}
   sb.append(")");
   log.info(sb.toString());
  }
}

3.4 关于AOP中一些类及函数的使用

1|0JoinPoint对象

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.

方法 作用 返回对象
getSignature() 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 Signature
getArgs() 获取 获取传入目标方法的参数对象 Object[]
getTarget() 获取被代理的对象 Object

1|0ProceedingJoinPoint对象

proceedingJoinPoin对象是JoinPoint的子类,在原本JoinPoint的基础上,放开了Proceeed()的使用,一般在环绕通知@Around时使用:

Object proceed() throws Throwable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK