利用Spring AOP自定義注解實(shí)現(xiàn)服務(wù)層和controller層日志以及異常記錄功能

項(xiàng)目開發(fā)中我們需要記錄各個(gè)服務(wù)的調(diào)用日志,作為審計(jì)記錄或者供debug查看,或者性能以及使用率分析等等。通過記錄日志和異常,我們能找出,哪些功能在哪個(gè)時(shí)間段被哪些模塊調(diào)用,入?yún)⒍加心男?,反?yīng)時(shí)間多長,這樣我們就能比較快的找出項(xiàng)目問題所在或者優(yōu)化項(xiàng)目。那么如何實(shí)現(xiàn)這種功能,Spring AOP給我們提供了現(xiàn)成的方法。

當(dāng)然實(shí)現(xiàn)的方法有很多,最直接的莫過于在每個(gè)調(diào)用的進(jìn)入和對(duì)出都記錄一天日志(logger),然后通過通過日志的分析,但是每個(gè)方法都需要寫日志記錄的代碼,顯得很臃腫重復(fù),因?yàn)橛涗浫罩镜拇a都是類似的,就是記錄參數(shù)和退出時(shí)間。 而使用AOP我們只需要在一個(gè)記錄編寫記錄日志大代碼,其他地方加上注解就可以了,方便快捷,便于修改。

第一步,添加依賴
主要是在你的pom文件中添加如下依賴

        <properties>
        <org.aspectj-version>1.7.4</org.aspectj-version>
        <cglib.version>3.1</cglib.version>
         </properties>
        ....

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>${cglib.version}</version>
        </dependency>

第二步,創(chuàng)建注解
我們?cè)诿總€(gè)注解的方法上加上描述這樣就更清楚知道這個(gè)方法是干什么的了。

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
    String description()  default "";
}

第三步,實(shí)現(xiàn)注解
需要注意的,本例只是為了說明如何使用AOP實(shí)現(xiàn)日志記錄,所以我們只是在控制臺(tái)打印日志,并沒有記錄到數(shù)據(jù)庫或者文件。實(shí)際項(xiàng)目中我們一般都是記錄到數(shù)據(jù)庫或者日志文件。
在展示具體實(shí)現(xiàn)前,簡要介紹一下幾種通知的作用。

  • 1.前置通知
    *(before advice, 也就是代碼中的 @Before(“serviceAspect()”)):在連接點(diǎn)前面執(zhí)行,對(duì)連接點(diǎn)不會(huì)造成影響(前置通知有異常的話,會(huì)對(duì)后續(xù)操作有影響)

  • 2.正常返回通知
    (after returning advice, 也就是代碼中的 @AfterReturning(pointcut = “serviceAspect()”)):在連接點(diǎn)正確執(zhí)行之后執(zhí)行,如果連接點(diǎn)拋異常,則不執(zhí)行

  • 3.異常返回通知
    (after throw Advice, 也就是代碼中的@AfterThrowing(pointcut = “serviceAspect()”, throwing = “e”)):在連接點(diǎn)拋異常的時(shí)候執(zhí)行

  • 4.返回通知
    (after, 也就是代碼中的@After(“serviceAspect()”)):無論連接點(diǎn)是正確執(zhí)行還是拋異常,都會(huì)執(zhí)行。

具體示例代碼在這里,歡迎加星,fork, 謝謝!

package com.yq.exceptiondemo.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;

/**
 * Simple to Introduction
 * className: SystemLogAspect
 *
 * @author EricYang
 * @version 2018/6/9 19:43
 */

@Aspect
@Component
public class SystemLogAspect {
    //本地異常日志記錄對(duì)象
    private  static  final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);

    @Pointcut("@annotation(com.yq.exceptiondemo.config.SystemLog)")
    public  void serviceAspect() {
        System.out.println("我是一個(gè)切入點(diǎn)");
    }

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

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        //讀取session中的用戶 等其他和業(yè)務(wù)相關(guān)的信息,比如當(dāng)前用戶所在應(yīng)用,以及其他信息, 例如ip
        String ip = request.getRemoteAddr();
        try {
            System.out.println("doBefore enter。 任何時(shí)候進(jìn)入連接點(diǎn)都調(diào)用");
            System.out.println("method requested:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            System.out.println("method description:" + getServiceMethodDescription(joinPoint));
            System.out.println("remote ip:" + ip);
            //日志存入數(shù)據(jù)庫

            System.out.println("doBefore end");
        }  catch (Exception e) {
            logger.error("doBefore exception");
            logger.error("exceptionMsg={}", e.getMessage());
        }
    }

    /**
     * 后通知(After advice) :當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)。
     * @param joinPoint
     */
    @After("serviceAspect()")
    public void after(JoinPoint joinPoint) {
        System.out.println("after  executed. 無論連接點(diǎn)正常退出還是異常退出都調(diào)用");
    }

    /**
     * 后通知(After advice) :當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知。
     * @param joinPoint
     */
    @AfterReturning(pointcut = "serviceAspect()")
    public void AfterReturnning(JoinPoint joinPoint)
    {
        System.out.println("AfterReturning executed。只有當(dāng)連接點(diǎn)正常退出時(shí)才調(diào)用");
        Object[] objs = joinPoint.getArgs();
    }

    /**
     * 異常通知 用于攔截層記錄異常日志
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();

        String ip = request.getRemoteAddr();
        String params = "";
        if (joinPoint.getArgs() !=  null && joinPoint.getArgs().length > 0) {
            for ( int i = 0; i < joinPoint.getArgs().length; i++) {
                params += (joinPoint.getArgs()[i]) + "; ";
            }
        }
        try {
            System.out.println("doAfterThrowing enter。 只有當(dāng)連接點(diǎn)異常退出時(shí)才調(diào)用");
            System.out.println("exception class:" + e.getClass().getName());
            System.out.println("exception msg:" + e.getMessage());
            System.out.println("exception method:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
            System.out.println("method description:" + getServiceMethodDescription(joinPoint));
            System.out.println("remote ip:" + ip);
            System.out.println("method parameters:" + params);
            //日志存入數(shù)據(jù)庫
            System.out.println("doAfterThrowing end");
        }  catch (Exception ex) {
            logger.error("doAfterThrowing exception");
            logger.error("exception msg={}", ex.getMessage());
        }

        logger.error("method={}, code={}, msg={}, params={}",
                joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), params);
    }

    /**
     * 獲取注解中對(duì)方法的描述信息
     *
     * @param joinPoint 切點(diǎn)
     * @return 方法描述
     * @throws Exception
     */
    public  static String getServiceMethodDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

}

第四步,應(yīng)用注解
我們?cè)谛枰涗浫罩镜姆椒ㄉ霞由衔覀兊淖⒔?

    @SystemLog(description = "helloWorld測試")
    @ApiOperation(value = "hello demo", notes = "just for demo")
    @GetMapping(value = "/hello", produces = "text/plain;charset=UTF-8")
    public String hello() {
        ReturnResult ret = new ReturnResult(Constants.QUERY_OK, "Hello World");
        return ret.toString();
    }

    @SystemLog(description = "devide測試")
    @ApiOperation(value = "hello exception demo, service method will throw native exception", notes = "just for demo")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "a", defaultValue = "10", value = "a", required = true, dataType = "int", paramType = "query"),
            @ApiImplicitParam(name = "b", defaultValue = "3", value = "b", required = true, dataType = "int", paramType = "query")
    })
    @GetMapping(value = "/devide", produces = "text/plain;charset=UTF-8")
    public String exceptionDemo(@RequestParam("a") int a, @RequestParam("b") int b) {
        log.info("Enter exceptionDemo a={} devided by b={}", a, b);

        int c= computerSvc.devide(a, b);

        ReturnResult ret = new ReturnResult(Constants.QUERY_OK, null);
        ret.setObj(Integer.valueOf(c));

        log.info("End exceptionDemo ret={}", ret);
        return ret.toString();
    }

第五步,查看效果
啟動(dòng)程序,然后調(diào)用這些方法。 我們的項(xiàng)目使用Swagger,因此我只需要在swagger提供的web頁面上調(diào)用方法就可以了。 特別注意的是為了展示異常的記錄,我們編寫a /b的方法,當(dāng)b等于0就出現(xiàn)異常。

請(qǐng)看效果圖


這里寫圖片描述

效果圖2, 調(diào)用的rest產(chǎn)生異常時(shí)


這里寫圖片描述

具體示例代碼在這里,歡迎加星,fork, 謝謝!

轉(zhuǎn)載自https://blog.csdn.net/russle/article/details/80639839

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容