java-使用spring AOP实现自定义注解

使用spring AOP实现自定义注解

  • 前言
    • 一、自定义注解
      • 1、自定义注解是什么?
      • 2、元注解(@Target、@Retention、@Inherited、@Documented)
    • 二、Spring AOP详解
    • 三、自定义注解实现
      • 1)annotation
      • 2)aspect
      • 3)@PreventRepeat


前言

参考资料:
Java自定义注解、Spring AOP、使用AOP实现和自定义注解实现日志记录

注解的原理就是通过切点进行动态代理,对原方法进行增强。
而this.XXX这种内部调用方法,调用的是原class的方法,而不是增强后的 proxyClass,所以,自然环绕方法就不执行,注解就不生效。

一、自定义注解

1、自定义注解是什么?

注解是一种能被添加到java源代码中的元数据,单独使用注解,就相当于在类、方法、参数和包上加上一个装饰,什么功能也没有,仅仅是一个标志,然后这个标志可以加上一些自己定义的参数。就像下面这样,创建一个@interface的注解,然后就可以使用这个注解了,加在我们需要装饰的方法上,但是什么功能也没有。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ValidateToken {
    String value() default"";
}

2、元注解(@Target、@Retention、@Inherited、@Documented)

我们上面的创建的注解ValidateToken 上面还有几个注解(@Target、@Retention、@Inherited、@Documented),这四个注解就是元注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的元注解类型,它们被用来提供对其它 注解类型作标志操作(可以理解为最小的注解,基础注解)
1)@Target——用于描述注解的使用范围,该注解可以使用在什么地方
java-使用spring AOP实现自定义注解_第1张图片
备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上,但是我们在这个注解标志在类上,就会报错

2)@Retention——表明该注解的生命周期
java-使用spring AOP实现自定义注解_第2张图片

3)@Inherited——是一个标记注解,其子类也可以使用该注解的功能
@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

4)@Documented——表明该注解标记的元素可以被Javadoc 或类似的工具文档化

二、Spring AOP详解

1)什么是AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面,是Spring的核心思想之一。

2)AOP 实现分类
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,按照 AOP 框架修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

3)AOP核心概念
java-使用spring AOP实现自定义注解_第3张图片

  • 切面(Aspect):切面是通知和切点的结合。
  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 目标对象(Target):目标对象指将要被增强的对象。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
  • 顾问(Advisor):顾问是Advice的一种包装体现,Advisor是Pointcut以及Advice的一个结合,用来管理Advice和Pointcut。

4)AOP源码解析
spring中的aop是通过动态代理实现的,那么具体是如何实现的呢?spring通过一个切面类,在类上加入@Aspect注解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:
1.找到所有的切面类
2.解析出所有的advice并保存
3.创建一个动态代理类
4.调用被代理类的方法时,找到他的所有增强器,并增强当前的方法

5)AOP在工作中的作用

  • 在调用service具体一些业务方法的时候,想在前面打一些日志。
  • 通过前后两次取时间戳来减一下,来统计所有业务方法执行的时间。
  • 在调用某一类业务方法时,判断用户有没有权限。
  • 在一系列业务方法前后加上事务的控制。
  • 比如startTransaction、commitTransaction(模拟事务控制)。

三、自定义注解实现

自定义注解使用范围:
上面总结的注解的定义,但是创建这样一个注解,仅仅是一个标志,装饰类、方法、属性的,并没有功能,要想实现功能,需要我们通过拦截器、AOP切面这些地方获取注解标志,然后实现我们的功能。一般我们可以通过注解来实现一些重复的逻辑,就像封装了的一个方法,可以用在一些权限校验、字段校验、字段属性注入存日志、缓存。

1)annotation

package com.cf.uip.api.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 PreventRepeat {
    /**
     * 锁方法的名称
     *
     * @return
     */
    String key() default "";

    /**
     * 锁方法的名称
     *
     * @return
     */
    int second() default 1;

    /**
     * 状态(1:小程序,2:PC端)
     *
     * @return
     */
    int state() default 2;
}

2)aspect

1.定义Pointcut切面

	/**
     * @Pointcut 注解通过切入点表达式定义切入点
     */
    @Pointcut("@annotation(com.cf.uip.api.annotation.AdminOptLogTitle)")
    public void optLogPointCut() {
        //方法体不需要写任何内容
    }
  • @Pointcut:获取添加自定义注解的方法,获取某些特定的类
  • 方法体中不需要添加任何东西

2.定义环绕通知
1) @Around 注解描述的方法为一个通知方法,在这个方法内部可以通过连接点对象(ProceedingJoinPoint)调用目标方法,并在目标方法对象执行之前或之后添加额外功能。

2) @Around 注解描述的方法有一定要求:

  • 返回值类型为Object
  • 方法参数类型为ProceedingJoinPoint类型
  • 方法抛出throwable异常(建议)

3)joinPoint封装了正要执行的目标方法信息

4)Object result=joinPoint.proceed();可以获取到目标方法执行的结果和时间。

3)@PreventRepeat

package com.cf.uip.api.aspect;

import com.cf.support.authertication.AdminAuthenticationServer;
import com.cf.support.authertication.UserAuthenticationServer;
import com.cf.support.result.Result;
import com.cf.support.result.ResultCodeEnum;
import com.cf.support.utils.RedisUtil;
import com.cf.uip.api.annotation.PreventRepeat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

@Aspect  //切面
@Component //加入到spring容器
@Slf4j
public class CommitAspect {
    @Resource
    private UserAuthenticationServer userAuthenticationServer;
    @Resource
    private AdminAuthenticationServer adminAuthenticationServer;
    @Resource
    private RedisUtil redisUtil;

    @Pointcut("@annotation(com.cf.uip.api.annotation.PreventRepeat)")
    public void commitPointCut() {
    }

    @Around("commitPointCut()")
    public Object around(JoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Class<?> clazz = joinPoint.getTarget().getClass();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = clazz.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        // 获取操作名称
        PreventRepeat annotation = method.getAnnotation(PreventRepeat.class);
        if (ObjectUtils.isNotEmpty(annotation)) {
            Long userId = userAuthenticationServer.getCurrentUser().getUserId();
            if (annotation.state() == 2) {
                userId = adminAuthenticationServer.getCurrentUser().getAdminId();
            }
            if (ObjectUtils.isEmpty(userId)) {
                return Result.buildErrorResult(ResultCodeEnum.NOT_LOGIN.getMsg());
            }
            String lockKey = StringUtils.isNotBlank(annotation.key()) ? annotation.key() : userId + ":" + methodName;
            if (!redisUtil.lock(lockKey, lockKey, annotation.second())) {
                return Result.buildErrorResult(ResultCodeEnum.REPEAT_SUBMIT_EXPIRATION.getMsg());
            }
        }
        //返回值
        return ((ProceedingJoinPoint) joinPoint).proceed();
    }
}
 	@PostMapping("/add")
    @AdminOptLogTitle("新增接口")
    @ApiOperation(value = "新增接口", notes = "新增接口")
    @PreventRepeat
    public Result apiAdd(@RequestBody @Valid ApiAddReq param) {
        ApiAddDTO apiAddDTO = BeanConvertorUtils.map(param, ApiAddDTO.class);
        List<ApiParamAddDTO> params = BeanConvertorUtils.copyList(param.getParamAddReqs(), ApiParamAddDTO.class);
        apiAddDTO.setParams(params);
        return apiFacade.apiAdd(apiAddDTO);
    }

你可能感兴趣的:(#,java,spring,java,spring,boot)