说明:
SSM项目中的每一个请求都需要进行日志记录操作。一般操作做的思路是:使用springAOP思想,对指定的方法进行拦截。拼装日志信息实体,然后持久化到数据库中。可是仔细想一下会发现:每次的客户端的每一次请求,服务器都会处理两件事情。一个是正常的业务操作;另一个就是我们额外要做的日志数据记录。这样的话,每次请求的“效率”就变得收到影响了,换句话说就是“耦合”了。明明一个请求是干一件特定的事情,你却又给我加上一部分东西。而且这一次请求是必须在额外做的事情做完才能返回。面向切面 编程就是为了“解耦”的。所以想到了日志持久化这个动作使用异步处理方式,不当误真正的请求效率。(这一段写的可能有点luan,大家先将就着看)。
分析:
① 异步消息队列中有【消费者】和【生产者两个角色】。生产者负责产生消息,并放入队列中。消费者负责监听队列,一旦队列中有新的消息了,取出后根据消息的类型选择对应的业务处理操作。
② 消费者在这里是在系统启动的时候,启动一个线程,对redis指定的key进行监听。使用redis的指令brpop阻塞指令进行监听对应的list。
环境:
jdk1.8、maven、idea、jedis3.2、mysql数据库
代码:
自定义注解:
/** * 自定义系统日志注解 * @author 魏正迪 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SysLog { /** * 操作描述 * @return */ String value() default ""; /** * 日志类型 * @return */ short type(); }
AOP切面
/** * @author wzd * @data 2018/03/06 * 系统日志切面 */ @Component @Aspect public class LogAspect { @Autowired private ILogService logService; @Autowired private JedisClientPool jedisClientPool; /** * 自动注册当前线程的request对象 */ @Autowired private HttpServletRequest request; /** * 日志的切点 */ @Pointcut("@annotation(top.oldwei.common.annotation.SysLog)") public void logPoint(){ } /** * 日志采用环绕通知来进行处理 * @param point * @return * @throws Throwable */ @Around("logPoint()") public Object around(ProceedingJoinPoint point)throws Throwable{ // 执行方法之前 UserEntity currentUser = ShiroUtils.getUserEntity(); long start = SystemClock.now(); Object result = point.proceed(); long end = SystemClock.now(); saveSysLog(point,end-start,currentUser); return result; } /** * 保存日志操作 * @param point * @param time * @param userEntity */ private void saveSysLog(ProceedingJoinPoint point,long time ,UserEntity userEntity){ try{ MethodSignature methodSignature = (MethodSignature) point.getSignature(); Method method = methodSignature.getMethod(); LogEntity logEntity = new LogEntity(); logEntity.setId(IdWorker.getId()); SysLog syslog = method.getAnnotation(SysLog.class); if(StringUtils.checkValNotNull(syslog)){ // 注解的value logEntity.setOperation(syslog.value()); // 注解的type logEntity.setType(syslog.type()); } // 调用的方法 logEntity.setMethod(point.getTarget().getClass().getName()+"."+method.getName()+"()"); logEntity.setIp(IpUtils.getIpAddr(request)); logEntity.setTime(time); // 请求参数 Object [] args = point.getArgs(); try{ logEntity.setParams(JSON.toJSON(args[0]).toString()); }catch (Exception e){} if(StringUtils.checkValNotNull(userEntity)){ // 创建人 logEntity.setCreateByCode(userEntity.getUserCode()); logEntity.setCreateByName(userEntity.getUserName()); }else{ // 登录操作时,方法执行后才能获取用户信息 userEntity = ShiroUtils.getUserEntity(); if(StringUtils.checkValNotNull(userEntity)){ logEntity.setCreateByCode(userEntity.getUserCode()); logEntity.setCreateByName(userEntity.getUserName()); }else{ logEntity.setCreateByCode(""); logEntity.setCreateByName(""); } } logEntity.setCreateDate(new Date()); // 使用redis异步队列方式进行保存日志 //logService.save(logEntity); TaskEntity taskEntity = new TaskEntity(); taskEntity.setTaskType(TaskType.LOG_TASK); taskEntity.setData(JSON.toJSONString(logEntity)); jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity)); taskEntity.setTaskType(TaskType.MAIL_TASK); jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity)); }catch (Exception e){ e.printStackTrace(); } } }
消息实体类
/** * 任务实体类 * @author wzd * @date 2018/04/01 */ public class TaskEntity implements Serializable { /** * 任务的唯一性编码 */ private Long id; /** * 任务类型,通过类型找到对应任务处理器进行处理 */ private TaskType taskType; /** * 需要传输的数据 json格式的 */ private String data; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public TaskType getTaskType() { return taskType; } public void setTaskType(TaskType taskType) { this.taskType = taskType; } public String getData() { return data; } public void setData(String data) { this.data = data; } }
消费者:启动注册消费者任务处理器多个。监听队列,取出任务根据任务类型选择对应的 任务处理器进行相应处理。
/** * redis 队列消费者 * 容器启动时加载并启动相应的线程,进行阻塞读取redis * 对应的任务队列。根据任务的类型选择对应的任务处理器进行处理。 * @author wzd * @data 2018/04/01 */ @Component public class TaskConstomer implements InitializingBean, ApplicationContextAware { /** * spring上下文 */ private ApplicationContext applicationContext; /** * 加载所有的任务处理器 */ private Map<TaskType, List<TaskHandler>> config = new HashMap<>(); /** * redis操作 */ @Autowired private JedisClientPool jedisClientPool; @Override public void afterPropertiesSet() throws Exception { // 获取系统所有实现TaskHandler的任务处理器 Map<String,TaskHandler> handlers = applicationContext.getBeansOfType(TaskHandler.class); if(StringUtils.checkValNotNull(handlers)){ for(Map.Entry<String,TaskHandler> entry:handlers.entrySet()){ List<TaskType> supportTaskTypes = entry.getValue().getTaskType(); for(TaskType taskType:supportTaskTypes){ if(!config.containsKey(taskType)){ config.put(taskType,new ArrayList<TaskHandler>()); } config.get(taskType).add(entry.getValue()); } } } // 启动线程 // 构建线程池 ExecutorService executorService = Executors.newCachedThreadPool(); Thread thread = new Thread(new Runnable() { @Override public void run() { while (true){ List<String> task = jedisClientPool.brpop(10, JedisConstants.AYSC_TASK_KEY); if(StringUtils.checkValNotNull(task) && task.size()>1 ){ TaskEntity entity = JSON.parseObject(task.get(1),TaskEntity.class); if(config.containsKey(entity.getTaskType())){ for(TaskHandler handler:config.get(entity.getTaskType())){ handler.doTask(entity); } } } } } }); thread.start(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
任务处理器接口:
/** * @author wzd * 异步任务通用接口 */ public interface TaskHandler { /** * 执行任务 * @param taskEntity */ void doTask(TaskEntity taskEntity); /** * 任务类型 * * @return */ default List<TaskType> getTaskType(){ return new ArrayList<>(); } }
日志任务
/** * 日志处理任务 * @author wzd */ @Component public class LogTaskHandler implements TaskHandler { @Autowired private ILogService logService; @Override public void doTask(TaskEntity taskEntity) { try{ LogEntity logEntity = JSON.parseObject(taskEntity.getData(),LogEntity.class); logService.save(logEntity); }catch (Exception e){} } @Override public List<TaskType> getTaskType() { return Arrays.asList(TaskType.LOG_TASK); } }
发送邮件任务
/** * @author wzd * 发送短信的异步队列任务 */ @Component public class MailTaskHandler implements TaskHandler{ @Autowired private MailMessageHandler mailMessageHandler; @Override public void doTask(TaskEntity taskEntity) { // 进行发送短信的业务逻辑 try{ mailMessageHandler.doSend(null); }catch (Exception e){ e.printStackTrace(); } } @Override public List<TaskType> getTaskType() { return Arrays.asList(TaskType.MAIL_TASK); } }
、、、、、
其他的任务实现接口即可。
特殊说明:以上代码需要重构的地方很多,仅给大家参考思路。也欢迎指正