Springboot使用AOP+自定义注解方式实现日志记录

Springboot使用AOP+自定义注解方式实现日志记录

在Spring框架中,可以使用AOP配合自定义注解可以方便的实现用户操作的监控,来简化我们的代码。

什么是自定义注解?

了解自定义注解之前必须了解四个元注解,什么是元注解?元注解指作用于注解之上的元数据或者元信息,简单通俗的讲,元注解就是注解的注解。

Java5.0定义的元注解(java.lang.annotation)
  • 1.@Target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、
类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:
    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

示例:注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {

}
  • 2.@Retention
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;
编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class
在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。

示例:Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
  • 3.@Documented
1
2
3
4
5
6
7
8
9
10
11
12
13
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
Documented是一个标记注解,没有成员。

示例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
  • 4.@Inherited
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,
则这个annotation将被用于该class的子类。

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。
如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的
annotation类型被发现,或者到达类继承结构的顶层。

示例:
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
自定义注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。
在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。
方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。
可以通过default来声明参数的默认值。

定义注解格式:
  public @interface 注解名 {定义体}

注解参数的可支持数据类型:

    1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
    2.String类型
    3.Class类型
    4.enum类型
    5.Annotation类型
    6.以上所有类型的数组

示例:
/**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}

使用AOP+自定义注解方式实现日志记录

1、定义一个方法级别的@Log注解,用于标注需要监控的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.pconline.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @Description 日志注解
* @Author jie.zhao
* @Date 2019/8/7 15:47
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
2、定义切面和切点

定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知。

关于日志表保存根据自己的业务实现即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package cn.pconline.common.aspect;

import cn.pconline.common.utils.HttpContextUtil;
import cn.pconline.common.utils.IpUtil;
import cn.pconline.config.authentication.JwtUtil;
import cn.pconline.config.properties.AuthProperties;
import cn.pconline.modules.sys.entity.SysLog;
import cn.pconline.modules.sys.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
* @Description AOP 记录用户操作日志
* @Author jie.zhao
* @Date 2019/8/7 15:47
*/
@Slf4j
@Aspect
@Component
public class LogAspect {

@Autowired
private AuthProperties authProperties;

@Autowired
private SysLogService logService;

@Pointcut("@annotation(cn.pconline.common.annotation.Log)")
public void pointcut() {
// do nothing
}

@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object result;
long beginTime = System.currentTimeMillis();
// 执行方法
result = point.proceed();
// 获取 request
HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
// 设置 IP 地址
String ip = IpUtil.getIpAddr(request);
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
if (authProperties.isOpenAopLog()) {
// 保存日志
String token = (String) SecurityUtils.getSubject().getPrincipal();
String username = "";
if (StringUtils.isNotBlank(token)) {
username = JwtUtil.getUsername(token);
}

SysLog log = new SysLog();
log.setUsername(username);
log.setIp(ip);
log.setTime(time);
logService.saveLog(point, log);
}
return result;
}
}
3、使用方法
1
2
3
4
5
6
7
@RestController
public class TestController {

@Log("新增用户")
@GetMapping("/one")
public void addUser(String name) { }
}

参考文档:

https://mrbird.cc/Spring-Boot-AOP%20log.html

https://www.cnblogs.com/peida/archive/2013/04/24/3036689.html


PS: 如果只会写作文式Coding而不会使用高级特性的人,只会是个码农。

-------------已经触及底线 感谢您的阅读-------------

本文标题:Springboot使用AOP+自定义注解方式实现日志记录

文章作者:趙小傑~~

发布时间:2019年08月13日 - 15:27:18

最后更新:2019年09月02日 - 19:51:44

原始链接:https://cnsyear.com/posts/2e9d58d3.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%