spring AOP为Rabbitmq记录日志功能

1这个项目经过很多人迭代,期间Rabbitmq的监听方法也是各式各样的,到我手上需要解决一个问题,第三方应用将消息推送到我们的Rabbitmq中处理,可能存在各种原因导致消息处理失败,这个时候需要将处理失败的消息记录日志并重新处理,以前消息量少的时候只需要人工将消息体重新打入队列即可,但是现在消息多了这个方法已经淘汰了,于是我做了这个切面日志.

1.首先,用注解的方式作为日志切点

创建一个自定义注解(我没有办法从切面传递一些必须参数,所以通过注解的方式传递每个方法的参数)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiPromoteMessageLog {

    // 服务名称
    String app_name() default "";

    // 队列名称
    String queue_name() default "";

    // 交换机名称
    String exchange_name() default "";

    // 路由名称
    String routing_key() default "";

}

2.创建切面类

@Component
@Aspect
@EnableAsync
@Slf4j
public class AspectPromoteMessageLog {

    @Resource
    private ObjectMapper objectMapper;

    /**
     * 消息进接收到(未处理)
     */
    private static final Integer MSG_RES_NO = 0;
    /**
     * 消息处理成功
     */
    private static final Integer MSG_RES_OK = 1;
    /**
     * 消息处理异常
     */
    private static final Integer MSG_RES_FAIL = 2;
    /**
     * 异常消息二次处理成功
     */
    private static final Integer MSG_RES_FAIL_OK = 3;


    @Pointcut("@annotation(com.xxx.xxx.log.aop.ApiPromoteMessageLog)")
    public void point() {
    }

    /**
     * 日志前置通知
     *
     * @param jp 切点参数
     */
    @Before("point()")
    public void before(JoinPoint jp) {

        Object[] params = jp.getArgs();
        Object param = params[0];
        JSONObject jsonObject = objectMapper.convertValue(param, JSONObject.class);

        //获取消息信息
        HashMap<String, String> traceMap = new HashMap<>();
        traceMap.put("class", jp.getTarget().getClass().getName());
        traceMap.put("method", jp.getSignature().getName());
        String dataKey = jsonObject.getString("dataKey");

        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        ApiPromoteMessageLog annotation = method.getAnnotation(ApiPromoteMessageLog.class);
        String appName = annotation.app_name();
        String queueName = annotation.queue_name();
        String exchangeName = annotation.exchange_name();
        String routingKet = annotation.routing_key();

        // 日志记录
        BossPromoteMessageLog messageLog =
                new BossPromoteMessageLog(appName, queueName, exchangeName, routingKet, dataKey);

        messageLog.setBody(JSONObject.toJSONString(param));
        // 默认未执行通过
        messageLog.setStatus(MSG_RES_NO);
        messageLog.setTrace(JSONObject.toJSONString(traceMap));
        messageLog.setCreateTime(LocalDateTime.now());
        messageLog.setUpdateTime(LocalDateTime.now());
        log.info("消息处理对象{}", messageLog);
        logFeign.saveLog(messageLog);
    }

    /**
     * 日志返回通知
     * <p>
     * 整个方法执行成功完成
     *
     * @param jp 切点参数
     */
    @AfterReturning("point()")
    public void afterReturning(JoinPoint jp) {

   ...
    }

    /**
     * 日志异常通知
     * <p>
     * 方法执行过程中存在异常执行
     *
     * @param jp 切点参数
     */
    @AfterThrowing(pointcut = "point()", throwing = "e")
    public void afterThrowing(JoinPoint jp, Exception e) {

        log.error("队列消息处理异常信息{}", e.getMessage());
        e.printStackTrace();

       ...

        throw new MessageConversionException("消息消费失败,移出消息队列,不再试错");
    }

}
 1.在切面类中要先创建一个切入点

也就是

@Pointcut("@annotation(com.xxx.xxx.log.aop.ApiPromoteMessageLog)")
    public void point() {
    }

2.其中com.xxx.xxx.log.aop.ApiPromoteMessageLog就是自定义注解的路径
3.后续写的各种通知中的切点直接调用@Pointcut注解的方法即可,避免重复写一样的路径过于麻烦
4.JoinPoint

除了环绕通知外其他的通知都有JoinPoint参数,我们可以通过这个参数拿到自定义注解标记的方法的入参,详细自己看代码或者自己尝试,毕竟我也是一个一个获取看的

注意点!!!

Rabbitmq的方法入参我碰到的有两种,有的人用的是Map接收,有的人用的是JSONObject接收,队列中的数据都是json格式所以他们都能拿到所有参数,但是但是但是,我们从消息体获取数据的时候会受到影响,具体什么样的建议你自己试试,所以我这里直接用了spring提供的ObjectMapper进行反序列化成JSONObject.

5.异常通知

异常通知的入参除了JoinPoint还有一个异常类,需要注意的是@AfterThrowing(pointcut = "point()", throwing = "e")这里的e需要与方法入参的异常名一致.

6.抛出MessageConversionException异常

异常日志记录完毕之后我们需要手动抛出MessageConversionException异常,这样消息队列中的消息就会被手动消费掉,而不会一直卡在那里一直报错

3.使用

    @ApiPromoteMessageLog(app_name = "web-server", queue_name = "testQueue", exchange_name = "testExchange", routing_key = "promote.test")
    @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "testQueue"), exchange = @Exchange(value = "testExchange", type = ExchangeTypes.TOPIC), key = "promote.test"))
    public void testRabbitMq(Map<String, String> params) {
        log.info("消费数据:{}", params);
        try {
            String dataKey = String.valueOf(params.get("dataKey"));
            int num = Integer.parseInt(String.valueOf(params.get("num")));
            log.info("dataKey:{}", dataKey);
            log.info("num:{}", 100 / num);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

我们在这里try catch不会影响切面的异常通知,需要注意的是这个异常捕获之后必须抛出原异常,不能new 一个新的异常抛出,否则异常通知记录不到真实异常位置,记录的是抛出新异常的位置.

用这种方式解决消息队列错误消息日志问题,最后的处理没有给出来,我觉得逻辑还存在一些问题,

方案是写一个定时任务,定时处理日志状态为2也就是处理错误的日志,处理完成之后会修改状态,我用了这几个注解

@Async
@Scheduled(cron = "0 0/10 * * * ?")
@Transactional(rollbackFor = Exception.class)

第一个异步处理

第二个定时逻辑(每十分钟执行一次,详细自查)

第三个事务注解

最后:try catch的异常捕获处理比aop的异常通知先进行.

实习快完事了,慢慢学习混日子,拜拜喽

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值