springBoot整合spring-aop攔截日志


1 創建springboot項目

(ps:本文不做詳細介紹,可以閱讀另一篇博客:https://www.cnblogs.com/liyhbk/p/13572989.html

1.1 添加pom依賴

<dependencies>
        <!--Spring Boot Web 基礎環境-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Spring Boot 測試環境-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.xnx3.util/xnx3-util -->
        <dependency>
            <groupId>com.xnx3.util</groupId>
            <artifactId>xnx3-util</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <!--aop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.5</version>
        </dependency>
        <!--接口文檔-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
    </dependencies>

1.2 配置application.yml文件

# 配置端口
server:
  port: 8084

spring:
  # 配置數據源
  datasource:
    url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  # 配置mapper.xml 文件所在的路徑
  mapper-locations: classpath:mapper/*.xml
  # 配置映射類所在的路徑
  type-aliases-package: com.liyh.entity
  # 開啟駝峰映射
  configuration:
    map-underscore-to-camel-case: true
#打印sql
logging:
  level:
    com.liyh.mapper: debug

1.3 創建logback.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">

    <!-- 彩色日志依賴的渲染類 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!-- Console 輸出設置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 日志輸出級別 -->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

2 配置AOP攔截器

2.1 spring-aop注解攔截順序

2.1.1 正常運行:

 

2.1.2 程序報錯:

2.2 創建控制器切面類 LogAspectj 

package com.liyh.log;

import com.liyh.entity.ExceptionLog;
import com.liyh.entity.SqlLog;
import com.liyh.entity.UserLog;
import com.liyh.service.LogService;
import com.liyh.utils.SqlUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 控制器切面
 * @Author: liyh
 * @Date: 2020/9/17 14:08
 */

@Aspect
@Component
public class LogAspectj {
    @Autowired
    private LogService logService;

    @Autowired
    SqlSessionFactory sqlSessionFactory;

    private static Logger logger = LoggerFactory.getLogger(LogAspectj.class);

    /**
     * @Pointcut : 創建一個切點,方便同一方法的復用。
     * value屬性就是AspectJ表達式,
     */
    @Pointcut("execution(* com.liyh.controller.*.*(..))")
    //@Pointcut("@annotation(com.liyh.log.LogAnno)")
    public void userLog() {
    }

    @Pointcut("execution(* com.liyh.mapper.*.*(..))")
    public void sqlLog() {
    }

    @Pointcut("execution(* com.liyh.controller.*.*(..))")
    public void exceptionLog() {
    }

    //前置通知
    //指定該方法是前置通知,並指定切入點
    @Before("userLog()")
    public void userLog(JoinPoint pj) {
        try {
            UserLog userLog = new UserLog();
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String method = request.getMethod();
            Signature signature = pj.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method targetMethod = methodSignature.getMethod();
            if ("POST".equals(method) || "GET".equals(method)) {
                String ipAddress = getIpAddress(request);
                String requestId = (String) request.getAttribute("requestId");
                // 根據請求參數或請求頭判斷是否有“requestId”,有則使用,無則創建
                if (StringUtils.isEmpty(requestId)) {
                    requestId = "req_" +  System.currentTimeMillis();
                    request.setAttribute("requestId", requestId);
                }
                userLog.setRequestId(requestId);    //請求id
                userLog.setMethodName(targetMethod.getName());        //方法名
                userLog.setMethodClass(signature.getDeclaringTypeName()); //方法所在的類名
                userLog.setRequestUrl(request.getRequestURL().toString());//請求URI
                userLog.setRemoteIp(ipAddress); //操作IP地址
                System.out.println("userLog = " + userLog);
//                logService.saveUserLog(userLog);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    //環繞通知
    @Around("sqlLog()")
    public Object sqlLog(ProceedingJoinPoint pj) throws Throwable {
        // 發送異步日志事件
        long start = System.currentTimeMillis();
        SqlLog sqlLog = new SqlLog();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Signature signature = pj.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        String ipAddress = getIpAddress(request);
        String requestId = (String) request.getAttribute("requestId");
        // 根據請求參數或請求頭判斷是否有“requestId”,有則使用,無則創建
        if (StringUtils.isEmpty(requestId)) {
            requestId = "req_" + System.currentTimeMillis();
            request.setAttribute("requestId", requestId);
        }
        //執行方法
        Object object = pj.proceed();
        //獲取sql
        String sql = SqlUtils.getMybatisSql(pj, sqlSessionFactory);
        //執行時長(毫秒)
        long loadTime = System.currentTimeMillis() - start;
        sqlLog.setRequestId(requestId);    //請求id
        sqlLog.setMethodName(targetMethod.getName());        //方法名
        sqlLog.setMethodClass(signature.getDeclaringTypeName()); //方法所在的類名
        sqlLog.setRequestUrl(request.getRequestURL().toString());//請求URI
        sqlLog.setRemoteIp(ipAddress); //操作IP地址
        sqlLog.setSql(sql);//sql
        sqlLog.setLoadTime(loadTime);//執行時間
        System.out.println("sqlLog = " + sqlLog);
//        logService.saveSqlLog(sqlLog);
        return object;
    }

    //異常通知 用於攔截異常日志
    @AfterThrowing(pointcut = "exceptionLog()", throwing = "e")
    public void exceptionLog(JoinPoint pj, Throwable e) {
        try {
            ExceptionLog exceptionLog = new ExceptionLog();
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Signature signature = pj.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method targetMethod = methodSignature.getMethod();
            String ipAddress = getIpAddress(request);
            String requestId = (String) request.getAttribute("requestId");
            // 根據請求參數或請求頭判斷是否有“requestId”,有則使用,無則創建
            if (StringUtils.isEmpty(requestId)) {
                requestId ="req_" + System.currentTimeMillis();
                request.setAttribute("requestId", requestId);
            }
            exceptionLog.setRequestId(requestId);    //請求id
            exceptionLog.setMethodName(targetMethod.getName());        //方法名
            exceptionLog.setMethodClass(signature.getDeclaringTypeName()); //方法所在的類名
            exceptionLog.setRequestUrl(request.getRequestURL().toString());//請求URI
            exceptionLog.setMessage(e.getMessage()); //異常信息
            exceptionLog.setRemoteIp(ipAddress); //操作IP地址
            System.out.println("exceptionLog = " + exceptionLog);
//            logService.saveExceptionLog(exceptionLog);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    /**
     * 獲取IP地址的方法
     * @param request 傳一個request對象下來
     * @return
     */
    public String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

2.3 定義注解類 LogAnno 

package com.liyh.log;

import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: liyh
 * @Date: 2020/9/17 17:12
 */
@Target({ElementType.METHOD,ElementType.TYPE})  //作用於方法 使用在類,接口
@Retention(RetentionPolicy.RUNTIME)     //運行時有效
public @interface LogAnno {
    @AliasFor("value")
    String[] operating() default {};
    @AliasFor("operating")
    String[] value() default {};
}

2.4 添加json工具類和獲取sql工具類

package com.liyh.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.IOException;

/**
 * jsonUtil工具類
 * @Author: liyh
 * @Date: 2020/9/17 17:12
 */
public class JsonUtil {

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

    private static ObjectMapper mapper = new ObjectMapper();


    /**
     * 對象轉Json格式字符串
     * @param obj 對象
     * @return Json格式字符串
     */
    public static <T> String obj2String(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            LOGGER.warn("Parse Object to String error : {}", e.getMessage());
            return null;
        }
    }

    /**
     * 對象轉Json格式字符串(格式化的Json字符串)
     * @param obj 對象
     * @return 美化的Json格式字符串
     */
    public static <T> String obj2StringPretty(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            LOGGER.warn("Parse Object to String error : {}", e.getMessage());
            return null;
        }
    }

    /**
     * 字符串轉換為自定義對象
     * @param str 要轉換的字符串
     * @param clazz 自定義對象的class對象
     * @return 自定義對象
     */
    public static <T> T jsonToObj(String str, Class<T> clazz){
        if(StringUtils.isEmpty(str) || clazz == null){
            return null;
        }
        try {
            return clazz.equals(String.class) ? (T) str : mapper.readValue(str, clazz);
        } catch (Exception e) {
            LOGGER.warn("Parse String to Object error : {}", e.getMessage());
            return null;
        }
    }


    /**
     * 集合對象與Json字符串之間的轉換
     * @param str 要轉換的字符串
     * @param typeReference 集合類型如List<Object>
     * @param <T> 
     * @return
     */
    public static <T> T jsonToObj(String str, TypeReference<T> typeReference) {
        if (StringUtils.isEmpty(str) || typeReference == null) {
            return null;
        }
        try {
            return (T) (typeReference.getType().equals(String.class) ? str : mapper.readValue(str, typeReference));
        } catch (IOException e) {
            LOGGER.warn("Parse String to Object error", e);
            return null;
        }
    }

    /**
     * 集合對象與Json字符串之間的轉換
     * @param str 要轉換的字符串
     * @param collectionClazz 集合類型
     * @param elementClazzes 自定義對象的class對象
     * @param <T>
     * @return
     */
    public static <T> T string2Obj(String str, Class<?> collectionClazz, Class<?>... elementClazzes) {
        JavaType javaType = mapper.getTypeFactory().constructParametricType(collectionClazz, elementClazzes);
        try {
            return mapper.readValue(str, javaType);
        } catch (IOException e) {
            LOGGER.warn("Parse String to Object error : {}" + e.getMessage());
            return null;
        }
    }
}
jsonUtil工具類
package com.liyh.utils;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;

/**
 * @Author: liyh
 * @Date: 2020/8/27 17:12
 */
public class SqlUtils {

    /**
     * 獲取aop中的SQL語句
     * @param pjp
     * @param sqlSessionFactory
     * @return
     * @throws IllegalAccessException
     */
    public static String getMybatisSql(ProceedingJoinPoint pjp, SqlSessionFactory sqlSessionFactory) throws IllegalAccessException {
        Map<String,Object> map = new HashMap<>();
        //1.獲取namespace+methodName
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        String namespace = method.getDeclaringClass().getName();
        String methodName = method.getName();
        //2.根據namespace+methodName獲取相對應的MappedStatement
        Configuration configuration = sqlSessionFactory.getConfiguration();
        MappedStatement mappedStatement = configuration.getMappedStatement(namespace+"."+methodName,true);
//        //3.獲取方法參數列表名
//        Parameter[] parameters = method.getParameters();
        //4.形參和實參的映射
        Object[] objects = pjp.getArgs(); //獲取實參
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0;i<parameterAnnotations.length;i++){
            Object object = objects[i];
            if (parameterAnnotations[i].length == 0){ //說明該參數沒有注解,此時該參數可能是實體類,也可能是Map,也可能只是單參數
                if (object.getClass().getClassLoader() == null && object instanceof Map){
                    map.putAll((Map<? extends String, ?>) object);
                    //System.out.println("該對象為Map");
                }else{//形參為自定義實體類
                    map.putAll(objectToMap(object));
                    //System.out.println("該對象為用戶自定義的對象");
                }
            }else{//說明該參數有注解,且必須為@Param
                for (Annotation annotation : parameterAnnotations[i]){
                    if (annotation instanceof Param){
                        map.put(((Param) annotation).value(),object);
                    }
                }
            }
        }
        //5.獲取boundSql
        BoundSql boundSql = mappedStatement.getBoundSql(map);
        return showSql(configuration,boundSql);
    }

    /**
     * 解析BoundSql,生成不含占位符的SQL語句
     * @param configuration
     * @param boundSql
     * @return
     */
    private  static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    String[] s =  metaObject.getObjectWrapper().getGetterNames();
                    s.toString();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    /**
     * 若為字符串或者日期類型,則在參數兩邊添加''
     * @param obj
     * @return
     */
    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }
        }
        return value;
    }

    /**
     * 獲取利用反射獲取類里面的值和名稱
     *
     * @param obj
     * @return
     * @throws IllegalAccessException
     */
    private static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
        //System.out.println(clazz);
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = field.get(obj);
            map.put(fieldName, value);
        }
        return map;
    }
}
SqlUtils 工具類

2.5 創建logController

package com.liyh.controller;

import com.liyh.entity.User;
import com.liyh.service.LogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: liyh
 * @Date: 2020/9/12 14:12
 */

@RestController
@RequestMapping("/log")
public class LogController {

    @Autowired
    private LogService logService;

    Logger logger = LoggerFactory.getLogger(LogController.class);


    @RequestMapping("/query/{id}")
    public void query(@PathVariable("id") int id) {
        User user = logService.query(id);
        logger.debug("這個是debug測試來的數據");
        logger.info("這個是info測試來的數據");
        logger.warn("這個是warn測試來的數據");
        logger.error("這個是error測試來的數據");
        System.out.println(user.getName());
    }

    @RequestMapping("/test")
    public void test() {
        int a = 2;
        int b= 0;
        logger.debug("這個是debug測試來的數據");
        logger.info("這個是info測試來的數據");
        logger.warn("這個是warn測試來的數據");
        logger.error("這個是error測試來的數據");
        System.out.println(a/b);
    }

}

3 訪問接口,查看控制台打印日志

3.1 正常情況:

測試地址:http://127.0.0.1:8084/log/query/1

控制台:

userLog = UserLog(id=null, requestId=req_1600680898027, methodName=query, methodClass=com.liyh.controller.LogController, requestUrl=http://127.0.0.1:8084/log/query/1, remoteIp=127.0.0.1)
sqlLog = SqlLog(id=null, requestId=req_1600680898027, sql=select * from t_user where id = ?, methodName=query, methodClass=com.liyh.mapper.LogMapper, requestUrl=http://127.0.0.1:8084/log/query/1, remoteIp=127.0.0.1, loadTime=8)

3.2 異常情況:

測試地址:http://127.0.0.1:8084/log/test

控制台:

userLog = UserLog(id=null, requestId=req_1600681075412, methodName=test, methodClass=com.liyh.controller.LogController, requestUrl=http://127.0.0.1:8084/log/test, remoteIp=127.0.0.1)
exceptionLog = ExceptionLog(id=null, requestId=req_1600681075412, methodName=test, methodClass=com.liyh.controller.LogController, requestUrl=http://127.0.0.1:8084/log/test, message=/ by zero, remoteIp=127.0.0.1)

3.3 測試結果:

通過測試攔截,獲取到用戶操作日志,sql語句,異常日志成功在控制台打印日志信息,我在攔截得配置是攔截得某一個包,也可以通過切點攔截某一個方法。但是通過aop攔截日志這種方法效率比較慢,注意使用場景!!!

另外,獲取得日志信息,可以創建數據庫,從而把日志保存到數據庫。

請關注我的后續博客,實現把攔截得日志上傳到阿里雲日志服務!!!(阿里日志是收費的)

3.4 項目地址:

 https://gitee.com/liyhGitee/springboot/tree/master/springboot_aop


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM