在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在项目中遇到要记录用户操作的需求,分析应该使用切面编程的方式实现。下面实现代码。
日志类Log
/** * @author shipc 2019/4/24 17:09 * @version 1.0 */ public class Log { private static final long serialVersionUID = -7943478965732892796L; private String title; // 日志标题 private String type; // 日志类型(info:入库,error:错误) private String method; // request 方法 post/put/delete/get private String url; // 请求路径 private String params; // 请求参数 private String response; // 响应信息 private String exception; // 异常信息 private Long time; // 执行时长 private String ip; // ip地址 @JsonSerialize(using = JsonDateTimeSerializer.class) private Date createTime; }
先定义一个注解类SysLog
import java.lang.annotation.*; /** * 系统日志注解类 * @author shipc */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { String value() default ""; // 操作类型 }
之后编写切面
/** * * @author shipc 2019/5/5 10:53 * @version 1.0 */ @Aspect @Component public class SysLogAspect { private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class); private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal beginTime"); private static final ThreadLocal<Log> logThreadLocal = new NamedThreadLocal<>("ThreadLocal log"); private static final ThreadLocal<LoginUserOutDTO> currentUser = new NamedThreadLocal<>("ThreadLocal user"); @Autowired private HttpServletRequest request; @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; // 这里用到了线程池 @Autowired private SysLogService sysLogService; /** * Controller层切点 注解拦截 */ @Pointcut("@annotation(这是切点SysLog)") public void logPointCut() { // 注解拦截 } @Before(value = "logPointCut()") public void doBefore() { logger.info("SysLogAspect:记录用户操作日志开始"); Date beginTime = new Date(); beginTimeThreadLocal.set(beginTime); // 记录开始时间 LoginUserOutDTO user = ShiroUtils.getUser(); // 这里是用Shiro 做的,获取当前登录的用户 currentUser.set(user); } @After(value = "logPointCut()") public void doAfter(JoinPoint joinPoint) { LoginUserOutDTO user = currentUser.get(); if ( user == null) { user = ShiroUtils.getUser(); } Object[] args = joinPoint.getArgs(); List<Object> argsList = new ArrayList<>(); // 不记录 多媒体文件,Request,Response for (Object arg: args) { if (!(arg instanceof MultipartFile || arg instanceof ServletRequest || arg instanceof ServletResponse)) { argsList.add(arg); } } String title = ""; String type = "info"; String remoteAddr = IPUtils.getIpAddr(request); // 请求IP String requestUri = this.request.getRequestURI(); // 请求URI String method = this.request.getMethod(); // 请求方法类型 try { title = getLogValue(joinPoint); } catch (Exception e) { logger.error(e.getMessage()); } long beginTime = beginTimeThreadLocal.get().getTime(); // 得到线程绑定的局部变量(开始时间) long endTime = System.currentTimeMillis(); // 结束时间 // 设置字段 Log log = logThreadLocal.get(); if (log == null) { log = new Log(); } log.setUuid(UuidUtils.getUuid()); log.setTitle(title); log.setType(type); log.setUrl(requestUri); log.setMethod(method); log.setIp(remoteAddr); String jsonString = JSON.toJSONString(argsList); log.putParams(jsonString); log.setCreateTime(beginTimeThreadLocal.get()); log.setTime(endTime - beginTime); log.setCreateUser(user == null ? "" : user.getUserName()); logThreadLocal.set(log); } @AfterReturning(value = "logPointCut()", returning = "result") public void doReturn(Object result) { Log log = logThreadLocal.get(); if (log == null) { log = new Log(); } if (result instanceof DataResponse) { DataResponse response = new DataResponse(); DataResponse dataResponse = (DataResponse) result; response.setCode(dataResponse.getCode()); String message = dataResponse.getMessage(); response.setMessage(message.length() < 200 ? message : message.substring(0,200)); log.putResponse(JSON.toJSONString(response)); } else { String jsonString = JSON.toJSONString(result); log.putResponse(jsonString); } threadPoolTaskExecutor.execute(new SaveLogThread(log, sysLogService)); logThreadLocal.remove(); currentUser.remove(); beginTimeThreadLocal.remove(); } @AfterThrowing(pointcut = "logPointCut()", throwing = "e") public void doAfterThrowing(Throwable e) { Log log = logThreadLocal.get(); if (log != null) { log.setType("error"); log.setException(e.getClass().getSimpleName()); threadPoolTaskExecutor.execute(new UpdateLogThread(log, sysLogService)); } logThreadLocal.remove(); currentUser.remove(); beginTimeThreadLocal.remove(); } /** * 获取注解中对方法的描述信息 * @param joinPoint 切点 * @return 用户操作 */ private static String getLogValue(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = method.getAnnotation(SysLog.class); return sysLog.value(); } /** * 保存日志线程 */ private static class SaveLogThread implements Runnable { private Log log; private SysLogService logService; private Logger logger = LoggerFactory.getLogger(this.getClass()); SaveLogThread(Log log, SysLogService logService) { this.log = log; this.logService = logService; } @Override public void run() { try { logService.saveLog(log); } catch (Exception e) { logger.info(e.getMessage()); } } } /** * 更新日志线程 */ private class UpdateLogThread implements Runnable { private Log log; private SysLogService logService; private Logger logger = LoggerFactory.getLogger(this.getClass()); UpdateLogThread(Log log, SysLogService sysLogService) { this.log = log; this.logService = sysLogService; } @Override public void run() { try { this.logService.updateById(log); } catch (Exception e) { logger.info(e.getMessage()); } } } }
使用在controller方法中使用
@SysLog("登录") public DataResponse<LoginInfoOutDTO> login(@RequestBody @Validated DataRequest<LoginInfoInDTO> request) { // 方法体 }