設(shè)計(jì)方案-API接口訪問信息設(shè)計(jì)

1. 背景

有時(shí)候?yàn)榱藢ο到y(tǒng)進(jìn)行優(yōu)化责掏,我們需要找出系統(tǒng)中訪問時(shí)間長的那些方法,本文就來演示下,如何實(shí)現(xiàn)這個(gè)功能涡拘。最終實(shí)現(xiàn)的效果是訪問一個(gè)url,列出當(dāng)前系統(tǒng)中所有api接口的訪問信息据德,包括:接口的調(diào)用次數(shù)鳄乏、正常調(diào)用次數(shù)、異常調(diào)用次數(shù)棘利、接口的平均訪問時(shí)間橱野、最大訪問時(shí)間、最小訪問善玫。

2. 思路

  1. 定義一個(gè)攔截器水援,記錄方法的開始時(shí)間和結(jié)束時(shí)間
  2. 定義一個(gè)全局的異常處理器,記錄防范訪問發(fā)生異常
  3. 定義ThreadLocal茅郎,保存方法的訪問信息蜗元。
  4. 為了訪問多線程并發(fā)訪問影響記錄的準(zhǔn)確性,用隊(duì)列把計(jì)算串行化只洒。
  5. 定義結(jié)果輸出界面

3. 設(shè)計(jì)

3.1 設(shè)計(jì)攔截器

@Service
public class AccessInterceptor implements HandlerInterceptor  {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){
            HandlerMethod hm = (HandlerMethod)handler;
            AccessInfo mi = new AccessInfo();
            mi.setHm(hm);
            mi.setStartAt(System.currentTimeMillis());
            mi.setUri(request.getRequestURI());
            AccessHolder.accessStart(mi);//記錄方法開始
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView model)
            throws Exception {
        //可以向view中寫入數(shù)據(jù)
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
            throws Exception {
        if(handler instanceof HandlerMethod){
            AccessHolder.accessEnd();//記錄方法結(jié)束
        }
    }
}

3.2 設(shè)計(jì)全局異常Handler

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(value=Exception.class)  
    public Result<String> allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception{  
        AccessHolder.accessError();//記錄方法異常
        if(exception instanceof BizException){
            exception.printStackTrace();
            BizException biz = (BizException)exception;
            return Result.error(biz.getCodeMsg());
        }else {
            exception.printStackTrace();
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }  
}

3.3 設(shè)計(jì)ThreadLocal對象

@Service
public class AccessHolder {
    
    private static ThreadLocal<AccessInfo> accessHolder = new ThreadLocal<AccessInfo>();//記錄單次訪問信息

    private static ConcurrentHashMap<Class<?>, ControllerAccessInfo> map = new ConcurrentHashMap<Class<?>, ControllerAccessInfo>();//記錄所有的訪問信息

    private static WorkingService<AccessRequest> workingService = new WorkingService<AccessRequest>();//工作隊(duì)列许帐,串行化
    
    static {
        workingService.start();
    }
    
    public static void accessStart(AccessInfo mai) {
        accessHolder.set(mai);
    }
    
    public static AccessInfo getAccessInfo() {
        return accessHolder.get();
    }
    
    public static void accessError() {
        AccessInfo ai = getAccessInfo() ;
        if(ai == null) {
            return;
        }
        ai.setOccurError(true);
    }
    
    public static void accessEnd() {
        AccessInfo ai = getAccessInfo();
        if(ai == null) {
            return;
        }
        ai.setEndAt(System.currentTimeMillis());
        accessHolder.set(null);
        workingService.execute(new AccessRequest(ai), new LazyExecutable<AccessRequest>() {
            @Override
            public void lazyExecute(AccessRequest request) {
                addAccessInfo(request.getAi());
            }
        });
    }
    
    private static void addAccessInfo(AccessInfo ai) {
        HandlerMethod hm = ai.getHm();
        Class<?> controllerClazz = hm.getBeanType();
        if(!AccessAble.class.isAssignableFrom(controllerClazz)) {
            return;
        }
        Method method = hm.getMethod();
        long startAt = ai.getStartAt();
        long endAt = ai.getEndAt();
        boolean occurError = ai.isOccurError();
        long useTime = endAt - startAt;
        String uri = ai.getUri();
        ControllerAccessInfo cai = map.get(controllerClazz);
        if(cai == null) {
            cai = new ControllerAccessInfo();
            cai.setControllerClazz(controllerClazz);
            map.put(controllerClazz, cai);
        }
        List<MethodAccessInfo> mais = cai.getMethodInfos();
        if(mais == null) {
            mais = new ArrayList<MethodAccessInfo>();
            cai.setMethodInfos(mais);
        }
        MethodAccessInfo mai = getMethodAccessInfo(mais, method);
        if(mai == null) {
            mai = new MethodAccessInfo();
            mai.setMethod(method.getName());
            mai.setUri(uri);
            mai.setInvokeCount(1);
            if(occurError) {
                mai.setErrorCount(1);
            }else {
                mai.setSuccessCount(1);
            }
            mai.setMinMillSecond(useTime);
            mai.setMaxMillSecond(useTime);
            mai.setAvgMillSecond(useTime);
            mais.add(mai);
        }else {
            if(useTime < mai.getMinMillSecond()) {
                mai.setMinMillSecond(useTime);
            }
            if(useTime > mai.getMaxMillSecond()) {
                mai.setMaxMillSecond(useTime);
            }
            mai.setInvokeCount(mai.getInvokeCount() + 1);
            if(occurError) {
                mai.setErrorCount(mai.getErrorCount()+1);
            }else {
                mai.setSuccessCount(mai.getSuccessCount()+1);
            }
            mai.setAvgMillSecond((mai.getAvgMillSecond()+useTime)/2);
        }
    }
    
    private static MethodAccessInfo getMethodAccessInfo(List<MethodAccessInfo> mais, Method method) {
        for(MethodAccessInfo mai : mais) {
            if(method.getName().equals(mai.getMethod())) {
                return mai;
            }
        }
        return null;
    }
    
    public static Map<String, Object> getAllAccessInfo() {
        Map<String, Object> result = new HashMap<String, Object>();
        for(Map.Entry<Class<?>, ControllerAccessInfo> entry : map.entrySet()) {
            ControllerAccessInfo cai = entry.getValue();
            result.put(cai.getControllerClazz().getSimpleName(), cai.getMethodInfos());
        }
        return result;
    }
}

3.4 實(shí)現(xiàn)EndPoint接口

public class AccessEndPoint implements Endpoint<Map<String, Object>> {

    public String getId() {
        return "access";
    }
    
    public boolean isEnabled() {
        return true;
    }
    
    public boolean isSensitive() {
        return false;
    }
    
    public Map<String, Object> invoke() {
        return AccessHolder.getAllAccessInfo();
    }
}

3.5 配置configuration

@Configuration
public class EndPointAutoConfig {
    @Bean
    public AccessEndPoint myEndPoint() {
        return new AccessEndPoint();
    }
}

4. 總結(jié)

實(shí)現(xiàn)對接口的信息記錄方案其實(shí)很多,有的時(shí)候可以手寫AOP,然后基于切面proceed后的對象進(jìn)行操作毕谴,也可以直接用攔截器進(jìn)行成畦。說到底,一切切面型的工作涝开,都需要深入理解AOP的思想循帐,善于總結(jié)和歸納。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舀武,一起剝皮案震驚了整個(gè)濱河市拄养,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌银舱,老刑警劉巖瘪匿,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寻馏,居然都是意外死亡棋弥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門诚欠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顽染,“玉大人漾岳,你說我怎么就攤上這事》勰” “怎么了尼荆?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唧垦。 經(jīng)常有香客問我捅儒,道長,這世上最難降的妖魔是什么振亮? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任野芒,我火速辦了婚禮,結(jié)果婚禮上双炕,老公的妹妹穿的比我還像新娘狞悲。我一直安慰自己,他們只是感情好妇斤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布摇锋。 她就那樣靜靜地躺著,像睡著了一般站超。 火紅的嫁衣襯著肌膚如雪荸恕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天死相,我揣著相機(jī)與錄音融求,去河邊找鬼。 笑死算撮,一個(gè)胖子當(dāng)著我的面吹牛生宛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肮柜,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陷舅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了审洞?” 一聲冷哼從身側(cè)響起莱睁,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芒澜,沒想到半個(gè)月后仰剿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痴晦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年南吮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阅酪。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旨袒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出术辐,到底是詐尸還是另有隱情砚尽,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布辉词,位于F島的核電站必孤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瑞躺。R本人自食惡果不足惜敷搪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幢哨。 院中可真熱鬧赡勘,春花似錦、人聲如沸捞镰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岸售。三九已至践樱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凸丸,已是汗流浹背拷邢。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屎慢,地道東北人瞭稼。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像腻惠,于是被迫代替她去往敵國和親弛姜。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在妖枚,面了一些公司廷臼,掛了不少,但最終還是拿到小米绝页、百度荠商、阿里、京東续誉、新浪莱没、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,246評論 11 349
  • 人潮涌動(dòng) 被墨鏡染色的天空 獨(dú)自失控 于無邊黑夜盡情放縱 我曾想象過人們口中的霓虹 也試圖尋找記憶中的迷宮 希望帶...
    歲臨海閱讀 344評論 3 7
  • 最后一天的作業(yè)是寫《刻意練習(xí)》的讀書筆記酷鸦∈味悖可是憋了一天硬是沒想好要寫什么牙咏。越是著急越是寫不出半個(gè)字。最后索性隨便寫...
    喜之郎碎碎念閱讀 685評論 0 1
  • 昆明的夜是寒冷的嘹裂,也是溫暖的妄壶, 昆明的冷,冷在皮膚的表層寄狼, 昆明的暖暖在人心 在寒冷的晚上總能看到幸福的笑容 在寒...
    寶閱佳閱讀 449評論 0 0
  • 我又來分享了泊愧。 遇到喜歡的勇敢點(diǎn)伊磺,女人花點(diǎn)心思追求自己的幸福。
    橙子的世界你不懂閱讀 601評論 0 2