springboot系列——自定义注解
项目需要实现对登录用户的实时监控,对用户部分相关操作进行埋点统计。
这里我使用了自定义注解,对需要监控的接口使用注解进行切点切入,然后处理相关逻辑即可。
自定义注解类:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperLog {
/**
* 介绍
*/
String title();
/**
* 日志类型
*/
//String operation();
}
@Target——定义注解使用范围
Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)
在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
1.TYPE——用于描述类、接口(包括注解类型) 或enum声明
2.FIELD——用于字段声明(包括枚举常量)
3.METHOD——用于方法声明
4.PARAMETER——用于参数声明
5.CONSTRUCTOR——用于构造函数声明
6.LOCAL_VARIABLE——用于本地变量声明
7.ANNOTATION_TYPE——用于注解类型声明
8.PACKAGE——用于包声明
9.TYPE_PARAMETER—— 用于类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
10.TYPE_USE——JavaSE8引进,此类型包括类型声明和类型参数声明,是为了方便设计者进行类型检查,例如,如果使用@Target(ElementType.TYPE_USE)对@NonNull进行标记,则类型检查器可以将@NonNull class C {...} C类的所有变量都视为非null
ps. 如果一个注解没有指定@Target注解,则此注解可以用于除了TYPE_PARAMETER和TYPE_USE以外的任何地方。
ElementType类型字典:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention——定义注解保留阶段
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用
RetentionPolicy类型字典:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
示例中 @Retention(RetentionPolicy.RUNTIME)注解表明 Test_Retention注解将会由虚拟机保留,以便它可以在运行时通过反射读取。
@Documented
注解标记的元素,Javadoc工具会将此注解标记元素的注解信息包含在javadoc中。默认,注解信息不会包含在Javadoc中。
自定义注解使用,本文栗子是举的接口调用日志,需要记录接口的入参和返回值,这里使用到spring的AOP
@Aspect
@Component
@Slf4j
public class SysLogAspect {
/**
* 定义切点 @Pointcut
* 在注解的位置切入代码
*/
@Pointcut("@annotation( xxx.xxx.xxx.xxx.xxx.OperLog)")
public void logPointCut() {
}
/**
* 环绕通知,获取接口调用前后入参及返回值数据
* @param proceedingJoinPoint
* @return
*/
@Around("logPointCut()")
public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint) {
try {
//保存日志
SysOperLog operLog = new SysOperLog();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
OperLog myLog = method.getAnnotation(OperLog.class);
if (myLog != null) {
operLog.setTitle(myLog.title());//保存获取的操作
operLog.setBusinessType(6);
}
//获取请求的类名
String className = proceedingJoinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
operLog.setMethod(className + "." + methodName+"()");
//请求的参数
Object[] args = proceedingJoinPoint.getArgs();
//将参数所在的数组转换成json
String params = JSON.toJSONString(args);
JSONArray jsonArray = JSON.parseArray(params);
operLog.setOperParam(params);
UserInfo user = JSON.toJavaObject(jsonArray.getJSONObject(0),UserInfo.class);
operLog.setOperName(user != null ? user.getUserName() != null ? user.getUserName() : user.getMobile() : "");
operLog.setOperTime(new Date());
//获取用户名
//获取用户ip地址
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
operLog.setOperIp(IpUtils.getIpAddr(request));
operLog.setOperUrl(request.getRequestURI());
//调用service保存SysLog实体类到数据库
// 异步保存日志数据到数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
Object result = proceedingJoinPoint.proceed();
return result;
} catch (Throwable e) {
log.error(e.getMessage());
}
return Result.failed( "系统异常");
}
}
@Pointcut("@annotation( xxx.xxx.xxx.xxx.xxx.OperLog)")
,使用springaop,对接口使用了@OperLog
进行切点切入,记录接口的调用与返回数据。
@OperLog(title = "账号密码登陆")
@PostMapping(value = "/login")
@ApiOperation(value = "账号密码登陆", notes = "登陆成功返回token")
public Result<String> login(@RequestBody @Valid UsernameAndPasswordReq usernameAndPasswordReq) {
final Result<String> accessToken = authService.login(usernameAndPasswordReq);
return accessToken;
}
使用注解@OperLog(title='xxx')
,即可完成对接口的调用切点切入。
ps,本文只是对自定义注解使用的大致演示,只提供了部分核心代码。如果有其他疑问,可留言,作者会耐心解答