这是我参与8月更文挑战的第6天,活动详情查看: 8月更文挑战
前言
在工作中常常遇到需要持久化日志的情况,特别是那种做平台的,别人的数据送进来,你再给别人,出问题找不到日志,那不是屎也是屎了
当然具体的日志持久化方案和架构、设计、业务有关,不管怎么样,执行这一部分代码的耗时是无法忽视的
问题的出现
假设需求是用户登录后台系统,要保存登录日志,持久化的方式是存到数据库的一张表中,很简单的一个需求,按照正常的同步代码逻辑可能是这样写的
LoginServiceImpl
@Override
public String login(String username, String password) {
//生成token,刷新token,缓存token等逻辑
String token = "生成的新Token并刷新Token";
//持久化日志到数据库
LoginLogEntity loginLog = getLoginLog(username, password);
loginLogDao.save(loginLog);
return token;
}
这样做有两个缺点:
- 由于这个日志持久化逻辑是同步的,假如这个保存日志的逻辑失败,用户登录的时候就报异常了
- 如果这个日志保存的逻辑再复杂一点,用户的每次登录都要等待这一段代码的执行时间
使用AOP实现异步日志
使用AOP将原本同步的操作改为异步
为了便于维护,我们需要创建一个自定义注解,如果不使用注解的话,维护的同事可能并不知道你这个方法织入了AOP,可能造成代码逻辑被忽略,徒增维护成本,另一个原因是注解的形式可以增加复用性;通过自定义注解我们可以实现自动记录日志(入参、出参、响应耗时等等) ,非业务核心代码的分离等等
需要引入Aspectj的依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
创建一个自定义注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginLog {
//定义一个注解参数,用来标识登录不同的系统
String system();
}
创建这个注解的实现:
@Aspect
@Component
@Slf4j
public class LoginLogAspect {
@Pointcut("@annotation(com.suckmydisk.mapstructdemo.annotation.LoginLog)")
public void pointCut() {
//织入点设置为自定义注解
}
/** 在注解的方法运行后会调用以下的代码 */
@AfterReturning(value = "pointCut() && @annotation(loginLog)", returning = "token")
public void loginLog(JoinPoint joinPoint, String token, LoginLog loginLog) {
Object[] args = joinPoint.getArgs();
String username = args[0].toString();
String password = args[1].toString();
String loginSystem = loginLog.system();
log.info("用户名:{}, 密码:{}, token:{},登录的系统是:{}", username, password, token, loginSystem);
}
}
实际使用:
在UserServiceImpl类的登录方法上加上日志注解:
@Service
public class UserServiceImpl implements UserService {
@Override
@LoginLog(system = "后台管理系统")
public String login(String username, String password) {
return "生成的新Token";
}
}
在LoginController中调用
@PostMapping("/login")
public void login(@RequestParam String username,
@RequestParam String password) {
userService.login(username, password);
}
调用登录接口后,控制台输出:
总结
- 在
LoginLogAspect.java的loginLog()方法中我们配置的是@AfterReturning,这是在被注解的方法返回了值后loginLog()才会被触发 - 同样是
loginLog()方法,由于使用了&& @annotation(loginLog)来获取注解中的属性值,所以方法中要加上参数LoginLog loginLog,并且自定义注解上要加@Retention(RetentionPolicy.RUNTIME),不然启动时会报错 - 由于AOP的原理是基于动态代理实现的,真实对象之间的调用会使AOP失效,所以编程时要特别注意这点
- 多写注释,少留坑,给新来的小伙伴留下最后温柔