切面加自定义注解实现请求调用记录

2020-06-02 10:17:09 浏览数 (1)

前言

对于一个已经发布的服务而言,通常我们会记录接口的调用日志,就类似收费接口按调用次数来进行收费,但是它的作用也不仅限于此,比如:在本人的小程序中,通过会通过接口调用次数来分析哪些接口调用次数少,哪些调用的多,进而分析该功能是不是应该下线(此前发布过问题反馈和在线做题的功能,但是上线后使用次数过于低,所以就把这个功能删掉了)。

这个功能可以衍生为链路追踪,但是由于本人小程序接口并不是分布式架构,这个链路追踪也就没有做,所以下一次再带来链路追踪的文章了(用小程序广告费买了几台服务器,代码重构后链路追踪就需要做了,很感谢点广告的小伙伴

)。

正文

本人小程序项目为Maven构建的多模块项目(Maven多模块项目,通过合理的模块拆分,实现代码的复用,便于维护和管理),我拆分模块如下图,其中api就是小程序调用的接口,cache是Redis相关操作,core是基本工具类,mq是RocketMQ的相关操作,orm是操作DB,search是ES搜索的封装(ES还没有完全实现,因为目前用不到ES,只是用于学习)

其中core模块中就实现了关于接口请求记录,实现方式如标题所言,使用切面加上自定义注解来实现。下面了解一下我的实现方式(相信大家也可以有更好的方案)。

首先就是自定义注解了:@Inherited保证能作用到子类上。 关于注解可以看之前的文章

解读Java 注解 (Annotation)

代码语言:javascript复制

package com.xcx.core.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TraceAnnotation {
    String desc();

    String traceID() default "";
}

实现注解后我这里定义了Controller的基类,API模块所有的Controller都继承该类,以此来保证AOP能作用到所有Controller,当然直接将注解写在Controller也是可以的,就是比较麻烦了。

代码语言:javascript复制
package com.xcx.core.common;

import com.alibaba.fastjson.JSONObject;
import com.xcx.core.annotation.TraceAnnotation;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;

/**
 * @Auther: 陈龙
 * @Date: 2018-08-05 15:22
 * @Description:
 */
@TraceAnnotation(desc = "请求记录")
public class BaseController {
    protected JSONObject convertRequestBody() {
        String param = null;
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        try {
            request.setCharacterEncoding("UTF-8");
            BufferedReader br = request.getReader();
            String buffer = null;
            StringBuffer buff = new StringBuffer();

            while ((buffer = br.readLine()) != null) {
                buff.append(buffer   "n");
            }

            br.close();
            param = buff.toString();
            return JSONObject.parseObject(param);
        } catch (Exception var6) {
            return null;
        }
    }
}

自定义注解实现了,下面就是切面了,在记录日志的时候,我并没有使用多线程,因为当时服务器太Low,感觉完全没必要,其中RequestUserInfo就是上一篇文章学弟聊到的ThreadLocal保存的用户信息,如果没有请求没有经过拦截,那就-1,表明该请求不需要记录调用者。需要说明的是Pointcut可以使用execution来匹配切面范围,我这里使用注解来强调切面作用范围。

关于切面也可以看我之前实现AOP的文章。

Spring AOP的简单应用

动手实现AOP

代码语言:javascript复制
package com.xcx.core.trace;


import com.xcx.core.common.RequestUserInfo;
import com.xcx.orm.dao.TraceDao;
import com.xcx.orm.vo.UserVO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @Auther: 陈龙
 * @Date: 2018-11-05 10:28
 * @Description:
 */
@Component("请求追踪记录")
@Aspect
public class RequestTrace {

    @Autowired
    private TraceDao traceDao;
    private final static Logger LOG = LoggerFactory.getLogger(RequestTrace.class);

    @Pointcut("@within(com.xcx.core.annotation.TraceAnnotation)")
    public void trace() {
    }


    @Around("trace()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long st = System.currentTimeMillis();
        Object o = pjp.proceed();
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        String url = request.getRequestURL().toString();
        long et = System.currentTimeMillis();
        String time = (et - st)   "";
        LOG.info("请求URL:{},耗时:{}", url, time);
        UserVO userVO = RequestUserInfo.get();
        traceDao.insertTrace(url, time, userVO == null ? -1 : userVO.getId());
        return o;
    }

}

走到这一步,核心的工作就算完成了,但是由于项目是多模块的,所以API的pom文件中需要引入core的项目,此外我们还需要注意一点,就是需要在启动类上加上如下代码,因为SpringBoot无法自动加载配置类RequestTrace,所以需要我们手动导入(关于SpringBoot自动加载配置类,玩SpringBoot有必要了解下,基本上我面试的时候说到SpringBoot都会聊到自动加载)其中RedisCache是Redis模块的配置类。

代码语言:javascript复制
@Import({RequestTrace.class, RedisCache.class})

最后就是Controller层的类要继承BaseController了。

代码语言:javascript复制
public class XcxArticleController extends BaseController 

启动API使用小程序调用在数据库就会记录每次请求,在小程序的后台会将这些数据解析成chart图,可以直观看到哪个模块访问量高。

0 人点赞