前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >切面加自定义注解实现请求调用记录

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

作者头像
每天学Java
发布2020-06-02 10:17:09
1.1K0
发布2020-06-02 10:17:09
举报
文章被收录于专栏:每天学Java每天学Java

前言

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

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

)。

正文

本人小程序项目为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图,可以直观看到哪个模块访问量高。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档