使用spring AOP实现自定义注解
前言
参考资料:
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 {
@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);
}
本文介绍了如何使用SpringAOP和自定义注解来实现防止重复提交的功能。首先讲解了自定义注解的概念,包括元注解@Target、@Retention、@Inherited和@Documented的作用。接着深入探讨了SpringAOP的核心概念,如切面、通知、织入等,并分析了AOP在实际工作中的应用场景,如日志记录和权限控制。最后,通过创建名为@PreventRepeat的自定义注解,并编写相应的切面来实现在方法执行前后进行防重复提交的逻辑。
1256

被折叠的 条评论
为什么被折叠?



