Target注解说明了该Annotation所修饰的范围。可以传入很多类型,参数为ElementType。例如TYPE,用于描述类、接口或者枚举类;FIELD用于描述属性;METHOD用于描述方法;PARAMETER用于描述参数;CONSTRUCTOR用于描述构造函数;LOCAL_VARIABLE用于描述局部变量;ANNOTATION_TYPE用于描述注解;PACKAGE用于描述包等。
Retention注解定义了该Annotation被保留的时间长短。参数为RetentionPolicy。例如SOURCE表示只在源码中存在,不会在编译后的class文件存在;CLASS是该注解的默认选项。 即存在于源码,也存在于编译后的class文件,但不会被加载到虚拟机中去;RUNTIME存在于源码、class文件以及虚拟机中,通俗一点讲就是可以在运行的时候通过反射获取到。
加上普通注解给需要记录日志的接口加上Log注解。
package spring.aop.log.demo.api.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import spring.aop.log.demo.api.util.Log; /** * HelloController * * @author Lunhao Hu * @date 2019-01-30 15:52 **/ @RestController public class HelloController { @Log @GetMapping("test/{id}") public String test(@PathVariable(name = "id") Integer id) { return "Hello" + id; } }加上之后,每一次调用test/{id}这个接口,都会触发拦截器中的doAfterReturning方法中的代码。
加上带类型注解上面介绍了记录普通日志的方法,接下来要介绍记录特定日志的方法。什么特定日志呢,就是每个接口要记录的信息不同。为了实现这个,我们需要实现一个操作类型的枚举类。代码如下。
操作类型模板枚举新建一个枚举类Type。代码如下。
package spring.aop.log.demo.api.util; /** * Type * * @author Lunhao Hu * @date 2019-01-30 17:12 **/ public enum Type { /** * 操作类型 */ WARNING("警告", "因被其他玩家举报,警告玩家"); /** * 类型 */ private String type; /** * 执行操作 */ private String operation; Type(String type, String operation) { this.type = type; this.operation = operation; } public String getType() { return type; } public String getOperation() { return operation; } } 给注解加上类型给上面的controller中的注解加上type。代码如下。
package spring.aop.log.demo.api.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import spring.aop.log.demo.api.util.Log; /** * HelloController * * @author Lunhao Hu * @date 2019-01-30 15:52 **/ @RestController public class HelloController { @Log(type = "WARNING") @GetMapping("test/{id}") public String test(@PathVariable(name = "id") Integer id) { return "Hello" + id; } } 修改aop类将aop类中的doAfterReturning为如下。
@AfterReturning(returning = "returnValue", pointcut = "operationLog() && @annotation(log)") public void doAfterReturning(JoinPoint point, Object returnValue, Log log) { // 注解中的类型 String enumKey = log.type(); System.out.println(Type.valueOf(enumKey).getOperation()); }加上之后,每一次调用加了@Log(type = "WARNING")这个注解的接口,都会打印这个接口所指定的日志。例如上述代码就会打印出如下代码。
因被其他玩家举报,警告玩家 获取aop拦截的请求参数为每个接口指定一个日志并不困难,只需要为每个接口指定一个类型即可。但是大家应该也注意到了,一个接口日志,只记录因被其他玩家举报,警告玩家这样的信息没有任何意义。
记录日志的人倒不觉得,而最后去查看日志的人就要吾日三省吾身了,被谁举报了?因为什么举报了?我警告的谁?
这样的日志做了太多的无用功,根本没有办法在出现问题之后溯源。所以我们下一步的操作就是给每个接口加上特定的参数。那么大家可能会有问题,如果每个接口的参数几乎都不一样,那这个工具类岂不是要传入很多参数,要怎么实现呢,甚至还要组织参数,这样会大量的侵入业务代码,并且会大量的增加冗余代码。
大家可能会想到,实现一个记录日志的方法,在要记日志的接口中调用,把参数传进去。如果类型很多的话,参数也会随之增多,每个接口的参数都不一样。处理起来十分麻烦,而且对业务的侵入性太高。几乎每个地方都要嵌入日志相关代码。一旦涉及到修改,将会变得十分难维护。
所以我直接利用反射获取aop拦截到的请求中的所有参数,如果我的参数类(所有要记录的参数)里面有请求中的参数,那么我就将参数的值写入参数类中。最后将日志模版中参数预留字段替换成请求中的参数。
流程图如下所示。