最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

支持SpEL表達式的自定義日志注解@SysLog介紹

 更新時間:2022年02月17日 10:39:17   作者:小p同學90  
這篇文章主要介紹了支持SpEL表達式的自定義日志注解@SysLog,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

序言

之前封裝過一個日志注解,打印方法執(zhí)行信息,功能較為單一不夠靈活,近來興趣來了,想重構(gòu)下,使其支持表達式語法,以應對靈活的日志打印需求。

該注解是方法層面的日志打印,如需更細的粒度,還請手擼log.xxx()。

預期

通過自定義注解,靈活的語法表達式,攔截自定義注解下的方法并打印日志

日志要支持以下內(nèi)容:

  • 方法執(zhí)行時間
  • 利用已知信息(入?yún)ⅰ⑴渲?、方法),書寫靈活的日志SpEL表達式
  • 打印方法返回結(jié)果
  • 按照指定日志類型打印日志

思路

定義自定義注解

攔截自定義注解方法完成以下動作

  • a. 計算方法執(zhí)行時間
  • b. 解析特定類型的表達式(這里不僅限于SpEL表達式)
  • c. 獲取返回結(jié)果
  • d. 按照日志類型進行打印

特定類型表達式方案

  • a. 屬性解析表達式(如:mybatis對屬性的解析,xxx${yyy.aaa}zzz或xxx#{yyy.bbb}zzz書寫方式 )
  • b. SpEL表達式(如:${xxx}、#{‘xxx’+#yyy.ppp+aaa.mmm()})

問題:選屬性解析表達式、還是SpEL表達式

屬性解析表達式:

  • a. 優(yōu)點:直觀、配置簡單
  • b. 缺點:需要自行處理屬性為待解析對象(容易翻車)

SpEL表達式:

  • a. 優(yōu)點:解析強大,性能優(yōu)良
  • b. 缺點:配置復雜不直觀

過程

定義自定義注解@SysLog 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    /**
     * 日志描述
     *
     * @return 返回日志描述信息
     */
    String value();
    /**
     * 日志等級(info、debug、trace、warn、error)
     *
     * @return 返回日志等級
     */
    String level() default "info";
    /**
     * 打印方法返回結(jié)果
     *
     * @return 返回打印方法返回結(jié)果
     */
    boolean printResult() default false;
}

該類包含以下信息:

  • 日志信息(支持動態(tài)表達式)
  • 日志級別(info、debug、trace、warn、error)
  • 是否打印方法返回的結(jié)果

走過的彎路1(PropertyParser)

采用MyBatis對XML解析的方式進行解析,需要把攔截到的入?yún)ean內(nèi)的屬性轉(zhuǎn)換為Properties的方式進行parse,遇到復雜對象就容易出錯,屬性無法進行動態(tài)解析,具體就不詳細描述了,感興趣的可以看下這個類org.apache.ibatis.parsing.PropertyParser

走過的彎路2(ParserContext)

比使用MyBatis更加友好一丟丟,使用Spring自帶的ParserContext設(shè)定解析規(guī)則,結(jié)合解析類ExpressionParser進行解析,也沒有解決上面遇到的問題,不用引用其它jar包或手擼解析規(guī)則,具體就不詳細描述了,感興趣的可以看下這個類

org.springframework.expression.ParserContext

最后的定型方案:

切面攔截方法前后的入?yún)?、出參、異常?/p>

SpEL表達式解析,根據(jù)表達式去動態(tài)解析,語法比預想中強大;

為了確認性能損耗,最后還做了個性能壓測

自定義注解切面類SysLogAspect(最終選型SpEL表達式方式)

/**
 * SysLog方法攔截打印日志類
 *
 * @author lipengfei
 * @version 1.0
 * @since 2019/3/29 10:49 AM
 */
@Aspect
public class SysLogAspect {
    private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class);
    private static final DefaultParameterNameDiscoverer DEFAULT_PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    private static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext();
    private static final ThreadLocal<StandardEvaluationContext> StandardEvaluationContextThreadLocal = new ThreadLocal<>();
    /**
     * 開始時間
     */
    private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
    @Pointcut("@annotation(net.zongfei.core.log.SysLog)")
    public void sysLogPointCut() {
    }
    /**
     * 處理完請求后執(zhí)行
     *
     * @param joinPoint 切點
     */
    @SuppressWarnings("unused")
    @Before("sysLogPointCut()")
    public void doBeforeReturning(JoinPoint joinPoint) {
        // 設(shè)置請求開始時間
        START_TIME.set(System.currentTimeMillis());
    }
    /**
     * 處理完請求后執(zhí)行
     *
     * @param joinPoint 切點
     */
    @AfterReturning(
            pointcut = "sysLogPointCut()",
            returning = "result"
    )
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        printLog(joinPoint, result, null);
    }
    /**
     * 攔截異常操作
     *
     * @param joinPoint 切點
     * @param e         異常
     */
    @AfterThrowing(
            pointcut = "sysLogPointCut()",
            throwing = "e"
    )
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        printLog(joinPoint, null, e);
    }
    /**
     * 打印日志
     *
     * @param point  切點
     * @param result 返回結(jié)果
     * @param e      異常
     */
    protected void printLog(JoinPoint point, Object result, Exception e) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        String className = ClassUtils.getUserClass(point.getTarget()).getName();
        String methodName = point.getSignature().getName();
        Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
        Method method;
        try {
            method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
            return;
        }
        // 獲取注解相關(guān)信息
        SysLog sysLog = method.getAnnotation(SysLog.class);
        String logExpression = sysLog.value();
        String logLevel = sysLog.level();
        boolean printResult = sysLog.printResult();
        // 解析日志中的表達式
        Object[] args = point.getArgs();
        String[] parameterNames = DEFAULT_PARAMETER_NAME_DISCOVERER.getParameterNames(method);
        Map<String, Object> params = new HashMap<>();
        if (parameterNames != null) {
            for (int i = 0; i < parameterNames.length; i++) {
                params.put(parameterNames[i], args[i]);
            }
        }
        // 解析表達式
        String logInfo = parseExpression(logExpression, params);
        Long costTime = null;
        // 請求開始時間
        Long startTime = START_TIME.get();
        if (startTime != null) {
            // 請求耗時
            costTime = System.currentTimeMillis() - startTime;
            // 清空開始時間
            START_TIME.remove();
        }
        // 如果發(fā)生異常,強制打印錯誤級別日志
        if(e != null) {
            log.error("{}#{}(): {}, exception: {}, costTime: {}ms", className, methodName, logInfo, e.getMessage(), costTime);
            return;
        }
        // 以下為打印對應級別的日志
        if("info".equalsIgnoreCase(logLevel)){
            if (printResult) {
                log.info("{}#{}(): {}, result: {}, costTime: {}ms", className, methodName, logInfo, result, costTime);
            } else {
                log.info("{}#{}(): {}, costTime: {}ms", className, methodName, logInfo, costTime);
            }
        } else if("debug".equalsIgnoreCase(logLevel)){
            if (printResult) {
                log.debug("{}#{}(): {}, result: {}, costTime: {}ms", className, methodName, logInfo, result, costTime);
            } else {
                log.debug("{}#{}(): {}, costTime: {}ms", className, methodName, logInfo, costTime);
            }
        } else if("trace".equalsIgnoreCase(logLevel)){
            if (printResult) {
                log.trace("{}#{}(): {}, result: {}, costTime: {}ms", className, methodName, logInfo, result, costTime);
            } else {
                log.trace("{}#{}(): {}, costTime: {}ms", className, methodName, logInfo, costTime);
            }
        } else if("warn".equalsIgnoreCase(logLevel)){
            if (printResult) {
                log.warn("{}#{}(): {}, result: {}, costTime: {}ms", className, methodName, logInfo, result, costTime);
            } else {
                log.warn("{}#{}(): {}, costTime: {}ms", className, methodName, logInfo, costTime);
            }
        } else if("error".equalsIgnoreCase(logLevel)){
            if (printResult) {
                log.error("{}#{}(): {}, result: {}, costTime: {}ms", className, methodName, logInfo, result, costTime);
            } else {
                log.error("{}#{}(): {}, costTime: {}ms", className, methodName, logInfo, costTime);
            }
        }
    }
    private String parseExpression(String template, Map<String, Object> params) {
        // 將ioc容器設(shè)置到上下文中
        ApplicationContext applicationContext = SpringContextUtil.getContext();
        // 線程初始化StandardEvaluationContext
        StandardEvaluationContext standardEvaluationContext = StandardEvaluationContextThreadLocal.get();
        if(standardEvaluationContext == null){
            standardEvaluationContext = new StandardEvaluationContext(applicationContext);
            standardEvaluationContext.addPropertyAccessor(new BeanFactoryAccessor());
            StandardEvaluationContextThreadLocal.set(standardEvaluationContext);
        }
        // 將自定義參數(shù)添加到上下文
        standardEvaluationContext.setVariables(params);
        // 解析表達式
        Expression expression = EXPRESSION_PARSER.parseExpression(template, TEMPLATE_PARSER_CONTEXT);
        return expression.getValue(standardEvaluationContext, String.class);
    }
}

該類按照上面思路中的邏輯進行開發(fā),沒有特別復雜的邏輯

為了提高性能和線程安全,對一些類加了static和ThreadLocal

結(jié)果

使用方式:

@SysLog(value = “#{‘用戶登錄'}”)
@SysLog(value = “#{'用戶登錄: method: ' + #loginRequest.username}”, printResult = true)
@SysLog(value = “#{'用戶登錄: method: ' + #loginRequest.username + authBizService.test()}”, printResult = true)
…

更多書寫方式參考SpEL表達式即可

	/**
     * 用戶登錄接口
     *
     * @param loginRequest 用戶登錄輸入?yún)?shù)類
     * @return 返回用戶登錄結(jié)果輸出類
     */
    @ApiOperation("用戶登錄接口")
    @PostMapping(value = "/login")
    @SysLog(value = "#{'用戶登錄: username: ' + #loginRequest.username + authBizService.test()}", level = "debug", printResult = true)
    @Access(type = AccessType.LOGIN, description = "用戶登錄")
    public LoginResponse login(
            @ApiParam(value = "用戶登錄參數(shù)") @RequestBody @Valid LoginRequest loginRequest
    ) {
		// 業(yè)務代碼
	}

結(jié)果打?。?/strong>

2021-09-01 22:04:05.713 ERROR 98511 CRM [2cab21fdd2469b2e--2cab21fdd2469b2e] [nio-8000-exec-2] n.z.m.a.SysLogAspect                     : net.zongfei.crm.api.AuthController#login(): 用戶登錄: username: lipengfei90@live.cn method: this is test method(), exception: [用戶模塊] - 用戶名或密碼錯誤, costTime: 261ms

壓測下來性能損耗較低(可忽略不計)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java并發(fā)(Runnable+Thread)實現(xiàn)硬盤文件搜索功能

    Java并發(fā)(Runnable+Thread)實現(xiàn)硬盤文件搜索功能

    這篇文章主要介紹了Java并發(fā)(Runnable+Thread)實現(xiàn)硬盤文件搜索,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • SpringBoot向容器注冊bean的方法詳解

    SpringBoot向容器注冊bean的方法詳解

    這篇文章主要利用示例為大家詳細介紹了SpringBoot如何向容器注冊bean(即:將對象加入容器)的四種方法,文中的示例代碼講解詳細,需要的可以參考一下
    2022-05-05
  • logback關(guān)閉某個包的日志操作

    logback關(guān)閉某個包的日志操作

    這篇文章主要介紹了logback關(guān)閉某個包的日志操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Mybatis-plus批量插入的2種方式總結(jié)

    Mybatis-plus批量插入的2種方式總結(jié)

    這篇文章主要給大家總結(jié)介紹了關(guān)于Mybatis-plus批量插入的2種方式,Mybatis-Plus提供了多種方式進行批量插入優(yōu)化,文中通過代碼示例將實現(xiàn)的方法介紹的非常詳細,需要的朋友可以參考下
    2023-08-08
  • java編寫一個花名隨機抽取器的實現(xiàn)示例

    java編寫一個花名隨機抽取器的實現(xiàn)示例

    這篇文章主要介紹了java編寫一個花名隨機抽取器的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • @Transactional遇到try catch失效的問題

    @Transactional遇到try catch失效的問題

    這篇文章主要介紹了@Transactional遇到try catch失效的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • SpringBoot+Security 發(fā)送短信驗證碼的實現(xiàn)

    SpringBoot+Security 發(fā)送短信驗證碼的實現(xiàn)

    這篇文章主要介紹了SpringBoot+Security 發(fā)送短信驗證碼的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • 簡單的用java實現(xiàn)讀/寫文本文件的示例

    簡單的用java實現(xiàn)讀/寫文本文件的示例

    同時也展示了如果從輸入流中讀出來內(nèi)容寫入輸出流中(僅限文本流) 三個例子可以獨立存在,所以根據(jù)需要只看其中一個就行了。
    2008-07-07
  • Spring依賴注入的三種方式詳解

    Spring依賴注入的三種方式詳解

    這篇文章主要給大家介紹了三種Spring依賴注入的方式,?settter方法注入,構(gòu)造器注入以及變量(filed)?注入這三種方式,文章通過代碼示例給大家介紹的非常詳細,需要的朋友可以參考下
    2023-11-11
  • spring集成redis cluster詳解

    spring集成redis cluster詳解

    這篇文章主要介紹了spring集成redis cluster詳解,分享了maven依賴,Spring配置,增加connect-redis.properties 配置文件等相關(guān)內(nèi)容,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11

最新評論

靖安县| 台前县| 于都县| 盈江县| 莫力| 延川县| 栖霞市| 锡林郭勒盟| 莲花县| 垦利县| 延川县| 锡林郭勒盟| 新邵县| 凤庆县| 江都市| 江安县| 根河市| 穆棱市| 临泉县| 察雅县| 农安县| 财经| 木里| 桃园市| 长顺县| 雷州市| 浏阳市| 东乡县| 大城县| 平陆县| 南岸区| 郯城县| 湟中县| 武定县| 惠州市| 阜新市| 普安县| 奉化市| 江津市| 旬邑县| 集贤县|