java-使用spring AOP实现自定义注解
创始人
2025-05-28 15:02:34
0

使用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——用于描述注解的使用范围,该注解可以使用在什么地方
在这里插入图片描述
备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上,但是我们在这个注解标志在类上,就会报错

2)@Retention——表明该注解的生命周期
在这里插入图片描述

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核心概念
在这里插入图片描述

  • 切面(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 {@Resourceprivate UserAuthenticationServer userAuthenticationServer;@Resourceprivate AdminAuthenticationServer adminAuthenticationServer;@Resourceprivate 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 = "新增接口")@PreventRepeatpublic Result apiAdd(@RequestBody @Valid ApiAddReq param) {ApiAddDTO apiAddDTO = BeanConvertorUtils.map(param, ApiAddDTO.class);List params = BeanConvertorUtils.copyList(param.getParamAddReqs(), ApiParamAddDTO.class);apiAddDTO.setParams(params);return apiFacade.apiAdd(apiAddDTO);}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...