参考资料:
Java自定义注解、Spring AOP、使用AOP实现和自定义注解实现日志记录
注解的原理就是通过切点进行动态代理,对原方法进行增强。
而this.XXX这种内部调用方法,调用的是原class的方法,而不是增强后的 proxyClass,所以,自然环绕方法就不执行,注解就不生效。
注解是一种能被添加到java源代码中的元数据,单独使用注解,就相当于在类、方法、参数和包上加上一个装饰,什么功能也没有,仅仅是一个标志,然后这个标志可以加上一些自己定义的参数。就像下面这样,创建一个@interface的注解,然后就可以使用这个注解了,加在我们需要装饰的方法上,但是什么功能也没有。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ValidateToken {
String value() default"";
}
我们上面的创建的注解ValidateToken 上面还有几个注解(@Target、@Retention、@Inherited、@Documented),这四个注解就是元注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的元注解类型,它们被用来提供对其它 注解类型作标志操作(可以理解为最小的注解,基础注解)
1)@Target——用于描述注解的使用范围,该注解可以使用在什么地方
备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上,但是我们在这个注解标志在类上,就会报错
3)@Inherited——是一个标记注解,其子类也可以使用该注解的功能
@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
4)@Documented——表明该注解标记的元素可以被Javadoc 或类似的工具文档化
1)什么是AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面,是Spring的核心思想之一。
2)AOP 实现分类
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,按照 AOP 框架修改源代码的时机,可以将其分为两类:
4)AOP源码解析
spring中的aop是通过动态代理实现的,那么具体是如何实现的呢?spring通过一个切面类,在类上加入@Aspect注解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:
1.找到所有的切面类
2.解析出所有的advice并保存
3.创建一个动态代理类
4.调用被代理类的方法时,找到他的所有增强器,并增强当前的方法
5)AOP在工作中的作用
自定义注解使用范围:
上面总结的注解的定义,但是创建这样一个注解,仅仅是一个标志,装饰类、方法、属性的,并没有功能,要想实现功能,需要我们通过拦截器、AOP切面这些地方获取注解标志,然后实现我们的功能。一般我们可以通过注解来实现一些重复的逻辑,就像封装了的一个方法,可以用在一些权限校验、字段校验、字段属性注入存日志、缓存。
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;
}
1.定义Pointcut切面
/**
* @Pointcut 注解通过切入点表达式定义切入点
*/
@Pointcut("@annotation(com.cf.uip.api.annotation.AdminOptLogTitle)")
public void optLogPointCut() {
//方法体不需要写任何内容
}
2.定义环绕通知
1) @Around 注解描述的方法为一个通知方法,在这个方法内部可以通过连接点对象(ProceedingJoinPoint)调用目标方法,并在目标方法对象执行之前或之后添加额外功能。
2) @Around 注解描述的方法有一定要求:
3)joinPoint封装了正要执行的目标方法信息
4)Object result=joinPoint.proceed();可以获取到目标方法执行的结果和时间。
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);
}