• 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

  • 相关阅读:
    YARN源码学习(七)-----Task级别GC相关指标的自定义counter添加
    YARN源码学习(七)-----Task级别GC相关指标的自定义counter添加
    YARN源码分析(八)-----Reduce Shuffle过程分析
    YARN源码分析(八)-----Reduce Shuffle过程分析
    【每天一道算法题】整数循环节之和——数字黑洞6174
    getline函数
    设计模式之建造者模式Builder(创建型)
    字符串算法总结
    C++设计模式之单例模式
    Linux下C的线程同步机制
  • 原文地址:https://www.cnblogs.com/liyhbk/p/13706728.html
Copyright © 2020-2023  润新知