说明:
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);
}
}
、、、、、
其他的任务实现接口即可。
特殊说明:以上代码需要重构的地方很多,仅给大家参考思路。也欢迎指正
【Redis】redis异步消息队列+Spring自定义注解+AOP方式实现系统日志持久化的更多相关文章
-
ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存
基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...
-
Spring Cloud(7):事件驱动(Stream)分布式缓存(Redis)及消息队列(Kafka)
分布式缓存(Redis)及消息队列(Kafka) 设想一种情况,服务A频繁的调用服务B的数据,但是服务B的数据更新的并不频繁. 实际上,这种情况并不少见,大多数情况,用户的操作更多的是查询.如果我们缓 ...
-
php和redis怎么实现消息队列
把瞬间服务器的请求处理换成异步处理,缓解服务器的压力,实现数据顺序排列获取.本文主要和大家分享php和redis如何实现消息队列,希望能帮助到大家. redis实现消息队列步骤如下: 1).redis ...
-
Redis+php-resque实现消息队列
服务器硬件配置 Dell PowerEdge R310英特尔单路机架式服务器 Intel Xeon Processor X3430 2.4GHz, 8MB Cache 8GB内存(2 x 4GB) ...
-
如何使用NODEJS+REDIS开发一个消息队列
作者: RobanLee 原创文章,转载请注明: 萝卜李 http://www.robanlee.com MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应 ...
-
Delayer 基于 Redis 的延迟消息队列中间件
Delayer 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP.Golang 等多种语言客户端. 参考 有赞延迟队列设计 中的部分设计,优化后实现. 项目链接:http ...
-
八.利用springAMQP实现异步消息队列的日志管理
经过前段时间的学习和铺垫,已经对spring amqp有了大概的了解.俗话说学以致用,今天就利用springAMQP来完成一个日志管理模块.大概的需求是这样的:系统中有很多地方需要记录操作日志,比如登 ...
-
C#实现异步消息队列
原文:C#实现异步消息队列 拿到新书<.net框架设计>,到手之后迅速读了好多,虽然这本书不像很多教程一样从头到尾系统的讲明一些知识,但是从项目实战角度告诉我们如何使用我们的知识,从这本书 ...
-
异步消息队列Celery
Celery是异步消息队列, 可以在很多场景下进行灵活的应用.消息中包含了执行任务所需的的参数,用于启动任务执行, suoy所以消息队列也可以称作 在web应用开发中, 用户触发的某些事件需要较长事件 ...
随机推荐
-
CharacterEncodingFilter-Spring字符编码过滤器
通过源码可以看到在web.xml配置CharacterEncodingFilter 时,可以配置两个参数:encoding和forceEncoding : encoding:编码格式: forceEn ...
- Matlab函数 meshgrid
-
LoadRunner 脚本学习 -- 读取文件内容
随便创建个txt文档 输入点内容,例如 读取文件内前N个字符: Action() { long myfile; ; ]; char *filename = "E:\\kkk.txt&quo ...
-
U-boot 之TFTP服务器配置
一.PC端配置1.关闭防火墙 [root@gliethttp root]# /etc/init.d/iptables stop2.使用setup启动tftp [root@gliethttp roo ...
-
uva 12086 树状数组
树状数组 #include <cstdio> #include <cstdlib> #include <cmath> #include <map> #i ...
-
Stateless Iterators
As the name implies, a stateless iterator is an iterator that does not keep any state by itself. The ...
-
MYSQL C API 记录
一.环境与条件 MySQL AB 提供了C API,能够提供低等级界面,负责完毕涉及SQLserver交互的大多数常规任务:数据库连接 .查询.结果集处理和错误处置.C API通过两个组件实现: 头文 ...
-
End up with More Teams UVA - 11088
End up with More Teams Time Limit: 3000MS Memory Limit: Unknown 64bit IO Format: %lld & %llu ...
-
上海MVP见面会
很愉快,很有收获的一次见面!
-
kafka 清除topic数据脚本
原 kafka 清除topic数据脚本 2018年07月25日 16:57:13 pete1223 阅读数:1028 #!/bin/sh param=$1 echo " ...