zoukankan      html  css  js  c++  java
  • springboot-23-aspectj日志记录及threadlocal内存泄漏

    对于请求参数的处理和响应, 如果在代码中体现日志会显得很繁琐, 普遍的解决方案是使用spring的切面方案去解决. 

    这儿使用的是springboot的切面: http://www.cnblogs.com/wenbronk/p/6848984.html

    最开始的aspectj切面解决: 

    package com.iwhere.easy.travel.aspect;
    
    import java.sql.Date;
    import java.text.SimpleDateFormat;
    import java.util.Enumeration;
    import java.util.UUID;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    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 com.alibaba.fastjson.JSONObject;
    
    @Aspect
    @Component
    public class ControllerAspect {
        
        protected final Logger logger = LoggerFactory.getLogger(this.getClass());  
        
        private String name = "easy-travel-server";
        
        @Pointcut("execution(public * com.wenbronk.controller.*.*(..))")
        public void controllerLog(){}
        
        @Pointcut("execution(public * com.wenbronk.service.*.*(..))")
        public void serviceLog(){}
        
        private ThreadLocal<Long> startTime = new ThreadLocal<>();
        
        private ThreadLocal<String> requestId = new ThreadLocal<>();
        
        private ThreadLocal<String> interfaceName = new ThreadLocal<>();
        
        private ThreadLocal<String> param = new ThreadLocal<>();
        
        private SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
        
    
        @Before("controllerLog()")
        public void doBefore(JoinPoint joinPoint) throws Throwable {
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 设置请求开始时间
            startTime.set(System.currentTimeMillis());
            Date stTimeDate = new Date(startTime.get());
            String dateStr = dataFormat.format(stTimeDate);
            // 设置请求标识
            String requestIdStr = UUID.randomUUID().toString();
            requestId.set(requestIdStr);
            // 提取全部参数  paramJson
            Enumeration<String> paramNames = request.getParameterNames();
            JSONObject paramJson = new JSONObject();
            while(paramNames.hasMoreElements()){
                String paramName = paramNames.nextElement();
                paramJson.put(paramName, request.getParameter(paramName));
            }
            
            // 提取接口标识(url中截取)
            String requestUrl = request.getRequestURL().toString();
            int start = requestUrl.lastIndexOf("/")+1;
            String interfaceNameStr = null;
            if (requestUrl.contains("?")){
                interfaceNameStr = requestUrl.substring(start, requestUrl.indexOf("?"));
            } else {
                interfaceNameStr = requestUrl.substring(start);
            }
            param.set(paramJson.toJSONString());
            interfaceName.set(interfaceNameStr);
            // 将requst的唯一标识放置在request中,在其他环节可以穿起来
            request.setAttribute("requestId", requestId.get());
        }
        
        @AfterReturning(returning="rvt",pointcut="controllerLog()")
        public void doAfterReturning(JoinPoint joinPoint,Object rvt) throws Throwable {
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            logger.info("finished" + " " + name + " " + interfaceName.get() + " " + requestId.get() + " " 
                     + request.getRequestURL().toString() + " " + param.get()
                     + (System.currentTimeMillis() - startTime.get())
                     + " " + rvt.toString());
        }
        
        @AfterThrowing(throwing="ex", pointcut="controllerLog()")
        public void doAfterThrowing(JoinPoint joinPoint, Throwable ex) throws Throwable {
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
             // 发生地点
             int lineNum = 0;
             String className = null;
             String methodName = null;
             StackTraceElement[] st = ex.getStackTrace();
             for (StackTraceElement stackTraceElement : st) {
                 lineNum = stackTraceElement.getLineNumber();
                 className = stackTraceElement.getClassName();
                 methodName = stackTraceElement.getMethodName();
                System.out.println("[类:" + className + "]调用"
                + methodName + "时在第" + lineNum
                + "行代码处发生异常!异常类型:" + ex.getClass().getName());
                break;
             }
             String exceptionMessage = "[类:" + className + "]调用"+ methodName + "时在第" + lineNum + "行代码处发生异常!异常类型:" + ex.getClass().getName();
            logger.info("exception" + " " + name + " " + interfaceName.get() + " " + requestId.get() + " "
                     + request.getRequestURL().toString() + " " + param.get()
                     + " " + exceptionMessage);
        }
    }

    可见这个里面有一个before和after, 然后还有一个异常处理的方法

    附: joinpoint的简要api

    AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法: 
    1)JoinPoint 
     java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
     Signature getSignature() :获取连接点的方法签名对象; 
     java.lang.Object getTarget() :获取连接点所在的目标对象; 
     java.lang.Object getThis() :获取代理对象本身; 
    2)ProceedingJoinPoint 
    ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: 
     java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; 
     java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。 

    偶然间看到这个博客

    http://blog.csdn.net/lhqj1992/article/details/52451136
    https://my.oschina.net/xpbug/blog/113444
    https://segmentfault.com/a/1190000000537475

    由于此项目采用的是线程池, 所以可能存在内存一直上涨, 一直到线程池max之后达到一个稳定态, 也就发生了我们认为的内存泄漏

    之后改成这个方法: 

    package com.iwhere.scrapy.aspect;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.commons.lang3.ArrayUtils;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import com.alibaba.fastjson.JSON;
    
    /**
     * 日志记录
     * @author wenbronk
     * @Date 上午9:33:47
     */
    @Aspect
    @Configuration
    public class LogAspect {
        private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
    
        // 定义切点 Pointcut
        @Pointcut("execution(* com.iwhere.scrapy.controller.*Controller.*(..))")
        public void excudeService() {}
    
        @Around("excudeService()")
        public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
            
            Long startTime = System.currentTimeMillis();
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            HttpServletRequest request = sra.getRequest();
    
            String url = request.getRequestURL().toString();
            String method = request.getMethod();
            String uri = request.getRequestURI();
            String queryString = request.getQueryString();
            
    //        Object target = pjp.getTarget();
    //        String name = target.getClass().getName();
            Signature signature = pjp.getSignature();
            String className = signature.getDeclaringTypeName();
            String methodName = signature.getName();
            
            
            LOGGER.info("请求开始, {}#{}() URI: {}, method: {}, URL: {}, params: {}",className, methodName, uri, method, url, queryString);
    
            // result的值就是被拦截方法的返回值
            Object result = pjp.proceed();
            Long endTime = System.currentTimeMillis();
            LOGGER.info("请求结束, {}#{}(), URI: {}, method: {}, URL: {}, time: {}, result: {} ", className, methodName, uri, method, url, (endTime - startTime), JSON.toJSONString(result));
            return result;
        }
        
        
    //    @AfterThrowing(throwing="ex", pointcut="excudeService()")
    //    public String doAfterThrowing(JoinPoint joinPoint, Throwable ex) throws Throwable {
    //        // 接收到请求,记录请求内容
    //        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    //        HttpServletRequest request = attributes.getRequest();
    //         // 发生地点
    //         int lineNum = 0;
    //         String className = null;
    //         String methodName = null;
    //         StackTraceElement[] st = ex.getStackTrace();
    //         if (ArrayUtils.isNotEmpty(st)) {
    //             lineNum = st[0].getLineNumber();
    //             className = st[0].getClassName();
    //             methodName = st[0].getMethodName();
    //         }
    //        LOGGER.info("Exception: {}#{}() 在第{}行发生{}异常!!!", className, methodName, lineNum, ex.getClass().getName());
    //        return "exception";
    //    }
        
    }

    在里面处理异常, 还是会抛出, 所以单独出一个异常处理

    然后还需要加入一个全局异常处理框架: 

    http://www.cnblogs.com/wenbronk/p/6850785.html

    具体效果等待进一步测试

     推荐一个好的博客, 关于aspect的 : http://blog.csdn.net/lemon1003657090/article/details/52431584

  • 相关阅读:
    mysql常用基本命令
    mysql8.0.13下载与安装图文教程
    k8s ingress 增加跨域配置
    Jenkins 备份恢复插件 thinBackup 使用
    k8s HA master 节点宕机修复
    nginx 跨域问题解决
    mongodb 3.4.24 主从复制
    k8s 线上安装 jenkins并结合 jenkinsfile 实现 helm 自动化部署
    k8s helm 运用与自建helm仓库chartmuseum
    centos6 源码安装 unzip
  • 原文地址:https://www.cnblogs.com/wenbronk/p/6888568.html
Copyright © 2011-2022 走看看