一、背景
最近接手一个新的项目,做了两个多月的修修补补,简直无语到哭。总结起来有一下几点:
- 日志打印:每个类中都定义了操作日志入库的方法,每个方法中都同步将操作日志记录入库。有时候一个简单的一行调用service接口的controller代码,加上操作日志入库的代码,竟然多了十几行代码(我就想知道,你们以前是按代码行数算钱的吗)。更重要的是没有一行注释,同步执行入库操作,他不增加耗时吗?
- 系统日志:系统日志打印完全没有什么级别之分,全凭个人喜好,想用debug就用debug,想用info,就是info。很任性。
- 分页查询:low到无法想象。全部都是将所有数据全部搂到没存中,然后荣list中分割,等到查询下一页的时候,我再全部搂出来,如法炮制。(没办法,老子内存大,任性!)
- 。。。
多余不在吐槽,槽点太多。今天是实在忍无可忍。开始优化第一步,首先干掉大部分操作日志代码。这些属于边缘代码,删掉不会影响原有逻辑调用,况且,这部分代码也是最多的。
我采用的方式是Spring 的AOP环绕通知+自定义注解+SpringBoot的异步方法实现。下面具体介绍实现过程。
二、主要源码
1、定义调用方法类型枚举类
/**
* desc: 方法类型
* author: xuebin
* date: 2020/03/20 17:51
* blog: www.cxyk.net
* 公众号: cxyknet
*/
public enum MethodType {
ADD("add", "增加"),
DELETE("delete", "删除"),
UPDATE("update", "修改"),
EXPORT("export", "导出"),
SELECT("query", "查询");
private String type;
private String name;
MethodType(String type, String name) {
this.type = type;
this.name = name;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
public static String getName(String type){
for (MethodType modelType : MethodType.values()) {
if (modelType.getType().equals(type))
return modelType.getName();
}
return null;
}
public static String getType(String name){
for (MethodType modelType : MethodType.values()) {
if (modelType.getName().equals(name))
return modelType.getType();
}
return "-1";
}
}
2、自定义注解(凡是有该注解的方法都要将操作日志入库)
/**
* desc: 操作日志注解类
* author: xuebin
* date: 2020/03/20 15:45
* blog: www.cxyk.net
* 公众号: cxyknet
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
// 日志是否存入数据库
boolean intoDB() default false;
// 操作类型, 默认是查询
MethodType methodType() default MethodType.SELECT;
// 接口描述
String description() default "";
}
3、定义切面类、切点和标有相关注解的拦截方法。
这里采用了环绕通知,可以处理目标方法前、目标方法后的相关逻辑。但是主要,别漏了调用目标方法。
需求简单,可以选择使用别的通知。这个下边汇总一下。
/**
* desc: 日志记录切面
* author: xuebin
* date: 2020/03/20 15:48
* blog: www.cxyk.net
* 公众号: cxyknet
*/
@Aspect
@Component
public class LogAspect {
private Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Resource
private TestLogToDB testLogToDB;
@Resource
private HttpServletRequest request;
/**
* 定义切点
*/
@Pointcut("execution(* com.sitech.open.operate..*.*(..))")
public void log() {}
@Around(value = "log() && @annotation(logAnnotation)")
public Object around(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
// 目标方法执行前。这里可以加上自己的逻辑
logger.info("目标方法执行前,可以添加自己的逻辑"+JSON.toJSONString(request.getParameterMap()));
// 执行目标接口
Object object = joinPoint.proceed();
// 目标方法执行后
logger.info("目标方法执行后,可以添加自己的逻辑"+JSON.toJSONString(object));
// 调用异步方法
testLogToDB.intoDB(request.getParameterMap(), object);
return object;
}
}
4、异步方法实现
/**
* desc: 定义接口
* author: xuebin
* date: 2020/03/26 22:12
* blog: www.cxyk.net
* 公众号: cxyknet
*/
public interface TestLogToDB {
void intoDB(Map<String, String[]> parameterMap, Object object);
}
/**
* desc:
* author: xuebin
* date: 2020/03/26 22:13
* blog: www.cxyk.net
* 公众号: cxyknet
*/
@Service
@Transactional
public class TestLogToDBImpl implements TestLogToDB {
private Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Async
@Override
public void intoDB(Map<String, String[]> parameterMap, Object object) {
// 这里只做示例,不展示具体逻辑。使用者可自行实现各种效果
logger.info("step inot async intoDB, 我是异步方法,我被调用了");
}
}
5、注意
使用异步方法时,需要在Spring的Application启动类上加上@EnableAsync,标记开启异步方法。
6、使用,测试类
在需要使用日志入库的方法上加上我们定义的注解,如下:
/**
* desc: 测试
* author: xuebin
* date: 2020/03/26 22:29
* blog: www.cxyk.net
* 公众号: cxyknet
*/
@RestController
@RequestMapping("/test")
public class TestController {
Logger logger = LoggerFactory.getLogger(TestController.class);
@LogAnnotation(description = "测试")
@GetMapping("/test")
public String test(@RequestParam String id, @RequestParam String userName){
logger.info("step into test. id:{}; userName: {}", id, userName);
return "success";
}
}
7、测试,访问我们的controller中test接口
7.1、请求示例
7.2、相应结果
7.3、后台日志打印
INFO :2020-03-26 22:39:40,817 [ com.LogAspect: ] - 目标方法执行前,可以添加自己的逻辑{"id":["我是id"],"userName":["我是userName"]}
INFO :2020-03-26 22:39:40,820 [ com.TestController: ] - step into test. id:我是id; userName: 我是userName
INFO :2020-03-26 22:39:40,821 [ com.LogAspect: ] - 目标方法执行后,可以添加自己的逻辑"success"
INFO :2020-03-26 22:39:40,875 [ com.LogAspect: ] - step inot async intoDB, 我是异步方法,我被调用了
7.4、结果
至此,我们基于注解实现的日志打印就已经初步完成。由于是样例,代码比较简陋。大家可以根据需要自己在方法内部实现需要的业务逻辑。
8、advices的类型
- Before advice:在
join point
之前执行的advice
,不能阻止程序的继续运行。 - After returning advice:在
join point
完成之后执行的advice
。 - After throwing advice:在执行的方法抛出异常之后执行。
- After advice:在执行的
join point
退出之后执行不论正常退出或者抛出了异常。 - Around advice:可以在方法调用之前或之后执行自定义行为,并且还可以选择继续执行
join point
或者执行另外的方法。
三、最后
个人经验,有不足之处,欢迎评论指出。谢谢。
欢迎关注我的个人公众号(cxyknet),不定期分享技术热点、框架原理源码阅读、资料干货。