国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring aop+自定義注解統(tǒng)一記錄用戶行為日志

haobowd / 3122人閱讀

摘要:自定義注解新增日志注解類,注解作用于方法級(jí)別,運(yùn)行時(shí)起作用。自定義注解,聲明一種行為,使配置簡(jiǎn)化,代碼層面更加簡(jiǎn)潔。

寫在前面

本文不涉及過多的Spring aop基本概念以及基本用法介紹,以實(shí)際場(chǎng)景使用為主。

場(chǎng)景

我們通常有這樣一個(gè)需求:打印后臺(tái)接口請(qǐng)求的具體參數(shù),打印接口請(qǐng)求的最終響應(yīng)結(jié)果,以及記錄哪個(gè)用戶在什么時(shí)間點(diǎn),訪問了哪些接口,接口響應(yīng)耗時(shí)多長(zhǎng)時(shí)間等等。這樣做的目的是為了記錄用戶的訪問行為,同時(shí)便于跟蹤接口調(diào)用情況,以便于出現(xiàn)問題時(shí)能夠快速定位問題所在。

最簡(jiǎn)單的做法是這樣的:

    @GetMapping(value = "/info")
    public BaseResult userInfo() {
        //1.打印接口入?yún)⑷罩拘畔?標(biāo)記接口訪問時(shí)間戳
        BaseResult result = mUserService.userInfo();
        //2.打印/入庫 接口響應(yīng)信息,響應(yīng)時(shí)間等
        return result;
    }

這種做法沒毛病,但是稍微比較敏感的同學(xué)就會(huì)發(fā)覺有以下缺點(diǎn):

每個(gè)接口都充斥著重復(fù)的代碼,有沒有辦法提取這部分代碼,做到統(tǒng)一管理呢?答案是使用 Spring aop 面向切面執(zhí)行這段公共代碼。

充斥著 硬編碼 的味道,有些場(chǎng)景會(huì)要求在接口響應(yīng)結(jié)束后,打印日志信息,保存到數(shù)據(jù)庫,甚至要把日志記錄到elk日志系統(tǒng)等待,同時(shí)這些操作要做到可控,有沒有什么操作可以直接聲明即可?答案是使用自定義注解,聲明式的處理訪問日志。

自定義注解

新增日志注解類,注解作用于方法級(jí)別,運(yùn)行時(shí)起作用。

@Target({ElementType.METHOD}) //注解作用于方法級(jí)別
@Retention(RetentionPolicy.RUNTIME) //運(yùn)行時(shí)起作用
public @interface Loggable {

    /**
     * 是否輸出日志
     */
    boolean loggable() default true;

    /**
     * 日志信息描述,可以記錄該方法的作用等信息。
     */
    String descp() default "";

    /**
     * 日志類型,可能存在多種接口類型都需要記錄日志,比如dubbo接口,web接口
     */
    LogTypeEnum type() default LogTypeEnum.WEB;

    /**
     * 日志等級(jí)
     */
    String level() default "INFO";

    /**
     * 日志輸出范圍,用于標(biāo)記需要記錄的日志信息范圍,包含入?yún)ⅰ⒎祷刂档取?     * ALL-入?yún)⒑统鰠? BEFORE-入?yún)? AFTER-出參
     */
    LogScopeEnum scope() default LogScopeEnum.ALL;

    /**
     * 入?yún)⑤敵龇秶禐槿雲(yún)⒆兞棵鄠€(gè)則逗號(hào)分割。不為空時(shí),入?yún)⑷罩緝H打印include中的變量
     */
    String include() default "";

    /**
     * 是否存入數(shù)據(jù)庫
     */
    boolean db() default true;

    /**
     * 是否輸出到控制臺(tái)
     *
     * @return
     */
    boolean console() default true;
}

日志類型枚舉類:

public enum LogTypeEnum {

    WEB("-1"), DUBBO("1"), MQ("2");

    private final String value;

    LogTypeEnum(String value) {
        this.value = value;
    }

    public String value() {
        return this.value;
    }
}

日志作用范圍枚舉類:

public enum LogScopeEnum {

    ALL, BEFORE, AFTER;

    public boolean contains(LogScopeEnum scope) {
        if (this == ALL) {
            return true;
        } else {
            return this == scope;
        }
    }

    @Override
    public String toString() {
        String str = "";
        switch (this) {
            case ALL:
                break;
            case BEFORE:
                str = "REQUEST";
                break;
            case AFTER:
                str = "RESPONSE";
                break;
            default:
                break;
        }
        return str;
    }
}

相關(guān)說明已在代碼中注釋,這里不再說明。

使用 Spring aop 重構(gòu)

引入依賴:

    
            org.aspectj
            aspectjweaver
            1.8.8
        
        
            org.aspectj
            aspectjrt
            1.8.13
        
        
            org.javassist
            javassist
            3.22.0-GA
    

配置文件啟動(dòng)aop注解,基于類的代理,并且在 spring 中注入 aop 實(shí)現(xiàn)類。




    
    
    

    
    
    
     
    

新增 WebLogAspect 類實(shí)現(xiàn)

/**
 * 日志記錄AOP實(shí)現(xiàn)
 * create by zhangshaolin on 2018/5/1
 */
@Aspect
@Component
public class WebLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);

    // 開始時(shí)間
    private long startTime = 0L;

    // 結(jié)束時(shí)間
    private long endTime = 0L;

    /**
     * Controller層切點(diǎn)
     */
    @Pointcut("execution(* *..controller..*.*(..))")
    public void controllerAspect() {
    }

    /**
     * 前置通知 用于攔截Controller層記錄用戶的操作
     *
     * @param joinPoint 切點(diǎn)
     */
    @Before("controllerAspect()")
    public void doBeforeInServiceLayer(JoinPoint joinPoint) {
    }

    /**
     * 配置controller環(huán)繞通知,使用在方法aspect()上注冊(cè)的切入點(diǎn)
     *
     * @param point 切點(diǎn)
     * @return
     * @throws Throwable
     */
    @Around("controllerAspect()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        // 獲取request
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();

        //目標(biāo)方法實(shí)體
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        boolean hasMethodLogAnno = method
                .isAnnotationPresent(Loggable.class);
        //沒加注解 直接執(zhí)行返回結(jié)果
        if (!hasMethodLogAnno) {
            return point.proceed();
        }

        //日志打印外部開關(guān)默認(rèn)關(guān)閉
        String logSwitch = StringUtils.equals(RedisUtil.get(BaseConstants.CACHE_WEB_LOG_SWITCH), BaseConstants.YES) ? BaseConstants.YES : BaseConstants.NO;

        //記錄日志信息
        LogMessage logMessage = new LogMessage();

        //方法注解實(shí)體
        Loggable methodLogAnnon = method.getAnnotation(Loggable.class);
        
        //處理入?yún)⑷罩?        handleRequstLog(point, methodLogAnnon, request, logMessage, logSwitch);
        
        //執(zhí)行目標(biāo)方法內(nèi)容,獲取執(zhí)行結(jié)果
        Object result = point.proceed();
        
        //處理接口響應(yīng)日志
        handleResponseLog(logSwitch, logMessage, methodLogAnnon, result);
        return result;
    }
    
    /**
     * 處理入?yún)⑷罩?     *
     * @param point           切點(diǎn)
     * @param methodLogAnnon  日志注解
     * @param logMessage      日志信息記錄實(shí)體
     */
    private void handleRequstLog(ProceedingJoinPoint point, Loggable methodLogAnnon, HttpServletRequest request,
                                 LogMessage logMessage, String logSwitch) throws Exception {

        String paramsText = "";
        //參數(shù)列表
        String includeParam = methodLogAnnon.include();
        Map methodParamNames = getMethodParamNames(
                point.getTarget().getClass(), point.getSignature().getName(), includeParam);
        Map params = getArgsMap(
                point, methodParamNames);
        if (params != null) {
            //序列化參數(shù)列表
            paramsText = JSON.toJSONString(params);
        }
        logMessage.setParameter(paramsText);
        //判斷是否輸出日志
        if (methodLogAnnon.loggable()
                && methodLogAnnon.scope().contains(LogScopeEnum.BEFORE)
                && methodLogAnnon.console()
                && StringUtils.equals(logSwitch, BaseConstants.YES)) {
            //打印入?yún)⑷罩?            LOGGER.info("【{}】 接口入?yún)⒊晒?, 方法名稱:【{}】, 請(qǐng)求參數(shù):【{}】", methodLogAnnon.descp().toString(), point.getSignature().getName(), paramsText);
        }
        startTime = System.currentTimeMillis();
        //接口描述
        logMessage.setDescription(methodLogAnnon.descp().toString());
        
        //...省略部分構(gòu)造logMessage信息代碼
    }

    /**
     * 處理響應(yīng)日志
     *
     * @param logSwitch         外部日志開關(guān),用于外部動(dòng)態(tài)開啟日志打印
     * @param logMessage        日志記錄信息實(shí)體
     * @param methodLogAnnon    日志注解實(shí)體
     * @param result           接口執(zhí)行結(jié)果
     */
    private void handleResponseLog(String logSwitch, LogMessage logMessage, Loggable methodLogAnnon, Object result) {
        endTime = System.currentTimeMillis();
        //結(jié)束時(shí)間
        logMessage.setEndTime(DateUtils.getNowDate());
        //消耗時(shí)間
        logMessage.setSpendTime(endTime - startTime);
        //是否輸出日志
        if (methodLogAnnon.loggable()
                && methodLogAnnon.scope().contains(LogScopeEnum.AFTER)) {
            //判斷是否入庫
            if (methodLogAnnon.db()) {
                //...省略入庫代碼
            }
            //判斷是否輸出到控制臺(tái)
            if (methodLogAnnon.console() 
                    && StringUtils.equals(logSwitch, BaseConstants.YES)) {
                //...省略打印日志代碼
            }
        }
    }
    /**
     * 獲取方法入?yún)⒆兞棵?     *
     * @param cls        觸發(fā)的類
     * @param methodName 觸發(fā)的方法名
     * @param include    需要打印的變量名
     * @return
     * @throws Exception
     */
    private Map getMethodParamNames(Class cls,
                                                    String methodName, String include) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(cls));
        CtMethod cm = pool.get(cls.getName()).getDeclaredMethod(methodName);
        LocalVariableAttribute attr = (LocalVariableAttribute) cm
                .getMethodInfo().getCodeAttribute()
                .getAttribute(LocalVariableAttribute.tag);

        if (attr == null) {
            throw new Exception("attr is null");
        } else {
            Map paramNames = new HashMap<>();
            int paramNamesLen = cm.getParameterTypes().length;
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
            if (StringUtils.isEmpty(include)) {
                for (int i = 0; i < paramNamesLen; i++) {
                    paramNames.put(attr.variableName(i + pos), i);
                }
            } else { // 若include不為空
                for (int i = 0; i < paramNamesLen; i++) {
                    String paramName = attr.variableName(i + pos);
                    if (include.indexOf(paramName) > -1) {
                        paramNames.put(paramName, i);
                    }
                }
            }
            return paramNames;
        }
    }

    /**
     * 組裝入?yún)ap
     *
     * @param point       切點(diǎn)
     * @param methodParamNames 參數(shù)名稱集合
     * @return
     */
    private Map getArgsMap(ProceedingJoinPoint point,
                           Map methodParamNames) {
        Object[] args = point.getArgs();
        if (null == methodParamNames) {
            return Collections.EMPTY_MAP;
        }
        for (Map.Entry entry : methodParamNames.entrySet()) {
            int index = Integer.valueOf(String.valueOf(entry.getValue()));
            if (args != null && args.length > 0) {
                Object arg = (null == args[index] ? "" : args[index]);
                methodParamNames.put(entry.getKey(), arg);
            }
        }
        return methodParamNames;
    }
}
使用注解的方式處理接口日志

接口改造如下:

    @Loggable(descp = "用戶個(gè)人資料", include = "")
    @GetMapping(value = "/info")
    public BaseResult userInfo() {
        return mUserService.userInfo();
    }

可以看到,只添加了注解@Loggable,所有的web層接口只需要添加@Loggable注解就能實(shí)現(xiàn)日志處理了,方便簡(jiǎn)潔!最終效果如下:

訪問入?yún)ⅲ憫?yīng)日志信息:

用戶行為日志入庫部分信息:

簡(jiǎn)單總結(jié)

編寫代碼時(shí),看到重復(fù)性代碼應(yīng)當(dāng)立即重構(gòu),杜絕重復(fù)代碼。

Spring aop 可以在方法執(zhí)行前,執(zhí)行時(shí),執(zhí)行后切入執(zhí)行一段公共代碼,非常適合用于公共邏輯處理。

自定義注解,聲明一種行為,使配置簡(jiǎn)化,代碼層面更加簡(jiǎn)潔。

最后
更多原創(chuàng)文章會(huì)第一時(shí)間推送公眾號(hào)【張少林同學(xué)】,歡迎關(guān)注!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/72848.html

相關(guān)文章

  • Dubbo定義日志攔截器

    摘要:前言上一篇文章自定義注解統(tǒng)一記錄用戶行為日志記錄了層中通過自定義注解配合自動(dòng)記錄用戶行為日志的過程。那么按照分布式架構(gòu)中服務(wù)層的調(diào)用過程是否也可以實(shí)現(xiàn)統(tǒng)一記錄日志自定義日志攔截器可以實(shí)現(xiàn)這個(gè)需求。 showImg(https://segmentfault.com/img/remote/1460000017824155?w=1024&h=768); 前言 上一篇文章 Spring ao...

    meteor199 評(píng)論0 收藏0
  • 手把手教你如何優(yōu)雅的使用Aop記錄帶參數(shù)的復(fù)雜Web接口日志

    摘要:幾乎每一個(gè)接口被調(diào)用后,都要記錄一條跟這個(gè)參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。我最終采用了的方式,采取攔截的請(qǐng)求的方式,來記錄日志。所有打上了這個(gè)注解的方法,將會(huì)記錄日志。那么如何從眾多可能的參數(shù)中,為當(dāng)前的日志指定對(duì)應(yīng)的參數(shù)呢。 前言 不久前,因?yàn)樾枨蟮脑颍枰獙?shí)現(xiàn)一個(gè)操作日志。幾乎每一個(gè)接口被調(diào)用后,都要記錄一條跟這個(gè)參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。舉個(gè)例子,就比如禁言操作,日志中需要記...

    Loong_T 評(píng)論0 收藏0
  • 慕課網(wǎng)_《Spring入門篇》學(xué)習(xí)總結(jié)

    摘要:入門篇學(xué)習(xí)總結(jié)時(shí)間年月日星期三說明本文部分內(nèi)容均來自慕課網(wǎng)。主要的功能是日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等。 《Spring入門篇》學(xué)習(xí)總結(jié) 時(shí)間:2017年1月18日星期三說明:本文部分內(nèi)容均來自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)示例源碼:https://github.com/zccodere/s...個(gè)人學(xué)習(xí)源碼:https://git...

    Ververica 評(píng)論0 收藏0
  • Spring體系常用項(xiàng)目一覽

    摘要:的面向的異常遵從通用的異常層次結(jié)構(gòu)。比如以前常用的框架,現(xiàn)在常用的框架包含許多項(xiàng)目,下面挑一些最常用的出來總結(jié)一下。狀態(tài)是流程中事件發(fā)生的地點(diǎn),在流程中通過轉(zhuǎn)移的方式從一個(gè)狀態(tài)到另一個(gè)狀態(tài),流程的當(dāng)前狀況稱為流程數(shù)據(jù)。 如今做Java尤其是web幾乎是避免不了和Spring打交道了,但是Spring是這樣的大而全,新鮮名詞不斷產(chǎn)生,學(xué)起來給人一種凌亂的感覺,我就在這里總結(jié)一下,理順頭緒...

    OnlyLing 評(píng)論0 收藏0
  • Spring【DAO模塊】就是這么簡(jiǎn)單

    摘要:連接對(duì)象執(zhí)行命令對(duì)象執(zhí)行關(guān)閉值得注意的是,對(duì)數(shù)據(jù)庫連接池是有很好的支持的。給我們提供了事務(wù)的管理器類,事務(wù)管理器類又分為兩種,因?yàn)榈氖聞?wù)和的事務(wù)是不一樣的。 前言 上一篇Spring博文主要講解了如何使用Spring來實(shí)現(xiàn)AOP編程,本博文主要講解Spring的DAO模塊對(duì)JDBC的支持,以及Spring對(duì)事務(wù)的控制... 對(duì)于JDBC而言,我們肯定不會(huì)陌生,我們?cè)诔鯇W(xué)的時(shí)候肯定寫過非...

    NSFish 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<