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

  • 相关阅读:
    1>/dev/null 2>&1的含义
    rpm常用命令及rpm参数介绍
    linux按位运算
    关于比较运算符的一个例子
    js屏蔽效果
    jquery异步提交无刷新
    常用js验证
    获取输入字符的首字母(中文为拼音首字母)
    SQL查询合并字符串
    获取鼠标点击的坐标处理
  • 原文地址:https://www.cnblogs.com/wenbronk/p/6888568.html
Copyright © 2011-2022 走看看