Spring框架下的 “介面呼叫、MVC請求” 呼叫引數、返回值、耗時資訊輸出

hoojo發表於2016-11-28

主要攔截前端或後天的請求,列印請求方法引數、返回值、耗時、異常的日誌。方便開發除錯,能很快定位到問題出現在哪個方法中。

 

 

前端請求攔截,mvc的攔截器

  1 import java.util.Date;
  2 import java.util.Iterator;
  3 import java.util.Map;
  4 import java.util.Set;
  5 
  6 import javax.servlet.http.HttpServletRequest;
  7 import javax.servlet.http.HttpServletResponse;
  8 
  9 import org.codehaus.jackson.map.ObjectMapper;
 10 import org.springframework.core.NamedThreadLocal;
 11 import org.springframework.web.servlet.HandlerInterceptor;
 12 import org.springframework.web.servlet.ModelAndView;
 13 
 14 import com.xxx.eduyun.sdk.log.ApplicationLogging;
 15 import com.xxx.flipclass.sdk.client.utils.TimeUtil;
 16 
 17 /**
 18  * <b>function:</b> spring mvc 請求攔截器
 19  * @author hoojo
 20  * @createDate 2016-11-24 下午3:19:27
 21  * @file MVCRequestInterceptor.java
 22  * @package com.xxx.eduyun.app.mvc.interceptor
 23  * @project eduyun-app-web
 24  * @blog http://blog.csdn.net/IBM_hoojo
 25  * @email hoojo_@126.com
 26  * @version 1.0
 27  */
 28 public class MVCRequestInterceptor extends ApplicationLogging implements HandlerInterceptor {
 29 
 30     private static final ObjectMapper mapper = new ObjectMapper();
 31     private NamedThreadLocal<Long>  startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-startTimed");  
 32     
 33     @Override
 34     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 35         
 36         
 37         info("##############################【一個MVC完整請求開始】##############################");
 38         
 39         info("*******************MVC業務處理開始**********************");
 40         try {
 41             long timed = System.currentTimeMillis();
 42             startTimeThreadLocal.set(timed);
 43             
 44             String requestURL = request.getRequestURI();
 45             info("當前請求的URL:【{}】", requestURL);
 46             info("執行目標方法: {}", handler);
 47             
 48             Map<String, ?> params = request.getParameterMap();
 49             if (!params.isEmpty()) {
 50                 info("當前請求引數列印:");
 51                 print(request.getParameterMap(), "引數");
 52             }
 53         } catch (Exception e) {
 54             error("MVC業務處理-攔截器異常:", e);
 55         }
 56         info("*******************MVC業務處理結束**********************");
 57         
 58         return true;
 59     }
 60 
 61     @Override
 62     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
 63         
 64         info("*******************一個MVC 檢視渲染開始**********************");
 65         
 66         try {
 67             info("執行業務邏輯程式碼耗時:【{}】", TimeUtil.formatTime(new Date().getTime() - startTimeThreadLocal.get()));
 68             String requestURL = request.getRequestURI();
 69             info("當前請求的URL:【{}】", requestURL);
 70             
 71             if (modelAndView != null) {
 72                 info("即將返回到MVC檢視:{}", modelAndView.getViewName());
 73                 
 74                 if (modelAndView.getView() != null) {
 75                     info("返回到MVC檢視內容型別ContentType:{}", modelAndView.getView().getContentType());
 76                 }
 77                 
 78                 if (!modelAndView.getModel().isEmpty()) {
 79                     
 80                     info("返回到MVC檢視{}資料列印如下:", modelAndView.getViewName());
 81                     print(modelAndView.getModel(), "返回資料");
 82                 }
 83             }
 84         } catch (Exception e) {
 85             error("MVC 檢視渲染-攔截器異常:", e);
 86         }
 87         
 88         info("*******************一個MVC 檢視渲染結束**********************");
 89     }
 90 
 91     @Override
 92     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
 93         
 94         try {
 95             String requestURL = request.getRequestURI();
 96             info("MVC返回請求完成URL:【{}】", requestURL);
 97             info("MVC返回請求完成耗時:【{}】", TimeUtil.formatTime(new Date().getTime() - startTimeThreadLocal.get()));
 98             if (ex != null) {
 99                 info("MVC返回請求發生異常:", ex.getMessage());
100                 error("異常資訊如下:", ex);
101             }
102         } catch (Exception e) {
103             error("MVC完成返回-攔截器異常:", e);
104         }
105         
106         info("##############################【一個MVC完整請求完成】##############################");
107     }
108     
109     private void print(Map<String, ?> map, String prefix) {
110         if (map != null) {
111             Set<String> keys = map.keySet();
112             Iterator<String> iter = keys.iterator();
113             while (iter.hasNext()) {
114                 
115                 String name = iter.next();
116                 if (name.contains("org.springframework.validation.BindingResult")) {
117                     continue;
118                 }
119                 
120                 String value = "";
121                 try {
122                     value = mapper.writeValueAsString(map.get(name));
123                 } catch (Exception e) {
124                     error("轉換引數【{}】發生異常:", name, e);
125                 }
126                 info("{} \"{}\": {}", prefix, name, value);
127             }
128         }
129     }
130 }
View Code

spring-mvc.xml增加配置內容

 1 <mvc:interceptors>
 2     <mvc:interceptor>
 3         <mvc:mapping path="/**"/>
 4         <mvc:exclude-mapping path="/exceltemplate/**" />
 5         <mvc:exclude-mapping path="/statics/**" />
 6         <mvc:exclude-mapping path="/global/**" />
 7         <mvc:exclude-mapping path="/denied/**" />
 8         <mvc:exclude-mapping path="/favicon.ico" />
 9         <mvc:exclude-mapping path="/index.jsp" /> 
10         <bean class="com.xxx.eduyun.app.mvc.interceptor.MVCRequestInterceptor"/>
11     </mvc:interceptor>
12 </mvc:interceptors>
View Code

過濾靜態資源,一些靜態資源不需要攔截,在這裡配置黑名單不讓它進入攔截器。

 

下面是sdk介面攔截器,用到spirng的aop的MethodIntercept

  1 package com.xxx.flipclass.sdk.framework.aop;
  2 
  3 import java.lang.reflect.Method;
  4 import java.util.Date;
  5 
  6 import org.aopalliance.intercept.MethodInterceptor;
  7 import org.aopalliance.intercept.MethodInvocation;
  8 import org.apache.logging.log4j.LogManager;
  9 import org.apache.logging.log4j.Logger;
 10 import org.codehaus.jackson.map.ObjectMapper;
 11 
 12 import com.xxx.flipclass.sdk.client.utils.ParameterNameUtils;
 13 import com.xxx.flipclass.sdk.client.utils.TimeUtil;
 14 
 15 /**
 16  * <b>function:</b> Spring 介面呼叫攔截器,主要攔截com.xxx.*.sdk.client對外介面
 17  * @author hoojo
 18  * @createDate 2016-11-24 下午5:39:57
 19  * @file ExecutionApiLogMethodInterceptor.java
 20  * @package com.xxx.eduyun.sdk.framework
 21  * @project eduyun-sdk-service
 22  * @blog http://blog.csdn.net/IBM_hoojo
 23  * @email hoojo_@126.com
 24  * @version 1.0
 25  */
 26 public class ExecutionApiLogMethodInterceptor implements MethodInterceptor {
 27 
 28     private Logger log = LogManager.getLogger(ExecutionApiLogMethodInterceptor.class);
 29     private static final ObjectMapper mapper = new ObjectMapper();
 30     
 31     @Override
 32     public Object invoke(MethodInvocation invocation) throws Throwable {
 33 
 34         info("************************************【介面呼叫攔截開始】*************************************");
 35         String targetName = invocation.getThis().getClass().getSimpleName();
 36         Method method = invocation.getMethod();
 37         String methodName = method.getName();
 38         
 39         info("系統開始執行方法:{}.{}", targetName, methodName);
 40         
 41         info("【{}.{}】方法引數列印如下:", targetName, methodName);
 42         Object[] args = invocation.getArguments();
 43         
 44         //printArgs(args, method, invocation.getThis().getClass());
 45         printArgs(args, method);
 46         
 47         try {
 48             long timed = System.currentTimeMillis();
 49             
 50             Object result = invocation.proceed();
 51 
 52             info("【{}.{}】方法執行完成,耗時:【{}】", targetName, methodName, TimeUtil.formatTime(new Date().getTime() - timed));
 53             info("【{}.{}】方法執行返回結果:{}", targetName, methodName, result);
 54             
 55             info("【{}.{}】方法返回資料列印如下:", targetName, methodName);
 56             printResult(result);
 57             info("************************************【介面呼叫攔截結束*】************************************");
 58             
 59             return result;
 60         } catch (Throwable throwable) {
 61             error("外部介面呼叫方法【{}.{}】異常:", targetName, methodName, throwable);
 62             
 63             info("************************************【介面異常攔截結束】*************************************");
 64             throw throwable;
 65         }
 66     }
 67     
 68     private void printArgs(Object[] args, Method method) {
 69         try {
 70             
 71             String[] argNames = null;
 72             try {
 73                 argNames = ParameterNameUtils.getMethodParamNames(method);
 74             } catch (Exception e) {
 75                 error("獲取引數名稱異常:", e);
 76             }
 77             
 78             if (args != null) {
 79                 for (int i = 0; i < args.length; i++) {
 80                     String argName = "";
 81                     if (argNames != null && argNames.length >= i) {
 82                         argName = argNames[i];
 83                     }
 84                     
 85                     if (args[i] != null) {
 86                         String value = "";
 87                         try {
 88                             value = mapper.writeValueAsString(args[i]);
 89                         } catch (Exception e) {
 90                             error("轉換引數 \"{}\" 發生異常:", argName, e);
 91                         }
 92                         info("【引數 \"{}\" 】:({})", argName, value);
 93                     } else {
 94                         info("引數 \"{}\":NULL", argName);
 95                     }
 96                 }
 97             }
 98         } catch (Exception e) {
 99             error("【介面呼叫攔截器】列印方法執行引數異常:", e);
100         }
101     }
102     
103     private void printResult(Object result) {
104         if (result != null) {
105             try {
106                 info("【返回資料】:({})", mapper.writeValueAsString(result));
107             } catch (Exception e) {
108                 error("返回資料列印異常:", e);
109             }
110         } else {
111             info("【返回資料】:NULL");
112         }
113     }
114     
115     protected final void error(String msg, Object... objects) {
116         log.error(msg, objects);
117     }
118 
119     protected final void info(String msg, Object... objects) {
120         log.info(msg, objects);
121     }
122 }
View Code

上面使用到了方法引數獲取的工具類,程式碼如下:

  1 package com.xxx.flipclass.sdk.client.utils;
  2 
  3 import java.io.InputStream;
  4 import java.lang.reflect.Method;
  5 import java.lang.reflect.Modifier;
  6 import java.util.Arrays;
  7 
  8 import org.springframework.asm.ClassReader;
  9 import org.springframework.asm.ClassVisitor;
 10 import org.springframework.asm.ClassWriter;
 11 import org.springframework.asm.Label;
 12 import org.springframework.asm.MethodVisitor;
 13 import org.springframework.asm.Opcodes;
 14 import org.springframework.asm.Type;
 15 
 16 /**
 17  * <b>function:</b> 獲取方法參加名稱
 18  * @createDate 2016-11-25 下午3:40:33
 19  * @file ParameterNameUtils.java
 20  * @package com.xxx.flipclass.sdk.client.utils
 21  * @project flipclass-sdk-client
 22  * @version 1.0
 23  */
 24 public abstract class ParameterNameUtils {
 25 
 26     /**
 27      * 獲取指定類指定方法的引數名
 28      *
 29      * @param clazz 要獲取引數名的方法所屬的類
 30      * @param method 要獲取引數名的方法
 31      * @return 按引數順序排列的引數名列表,如果沒有引數,則返回null
 32      */
 33     public static String[] getMethodParamNames(Class<?> clazz, final Method method) throws Exception {
 34         
 35         try {
 36             
 37             final String[] paramNames = new String[method.getParameterTypes().length];
 38             String className = clazz.getName();
 39             
 40             int lastDotIndex = className.lastIndexOf(".");
 41             className = className.substring(lastDotIndex + 1) + ".class";
 42             InputStream is = clazz.getResourceAsStream(className);
 43             
 44             final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 45             ClassReader cr = new ClassReader(is);
 46             
 47             cr.accept(new ClassVisitor(Opcodes.ASM4, cw) {
 48                 @Override
 49                 public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
 50                     final Type[] args = Type.getArgumentTypes(desc);
 51                     // 方法名相同並且引數個數相同
 52                     if (!name.equals(method.getName()) || !sameType(args, method.getParameterTypes())) {
 53                         return super.visitMethod(access, name, desc, signature, exceptions);
 54                     }
 55                     MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
 56                     return new MethodVisitor(Opcodes.ASM4, v) {
 57                         @Override
 58                         public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
 59                             int i = index - 1;
 60                             // 如果是靜態方法,則第一就是引數
 61                             // 如果不是靜態方法,則第一個是"this",然後才是方法的引數
 62                             if (Modifier.isStatic(method.getModifiers())) {
 63                                 i = index;
 64                             }
 65                             if (i >= 0 && i < paramNames.length) {
 66                                 paramNames[i] = name;
 67                             }
 68                             super.visitLocalVariable(name, desc, signature, start, end, index);
 69                         }
 70                         
 71                     };
 72                 }
 73             }, 0);
 74             return paramNames;
 75         } catch (Exception e) {
 76             throw e;
 77         }
 78     }
 79     
 80     /**
 81      * 比較引數型別是否一致
 82      * @param types asm的型別({@link Type})
 83      * @param clazzes java 型別({@link Class})
 84      * @return
 85      */
 86     private static boolean sameType(Type[] types, Class<?>[] clazzes) {
 87         // 個數不同
 88         if (types.length != clazzes.length) {
 89             return false;
 90         }
 91 
 92         for (int i = 0; i < types.length; i++) {
 93             if (!Type.getType(clazzes[i]).equals(types[i])) {
 94                 return false;
 95             }
 96         }
 97         return true;
 98     }
 99 
100     /**
101      * 獲取方法的引數名
102      * @param Method
103      * @return argsNames[]
104      */
105     public static String[] getMethodParamNames(final Method method) throws Exception {
106         
107         return getMethodParamNames(method.getDeclaringClass(), method);
108     }
109 
110     public static void main(String[] args) throws Exception {
111         Class<ParameterNameUtils> clazz = ParameterNameUtils.class;
112         
113         Method method = clazz.getDeclaredMethod("getMethodParamNames", Method.class);
114         String[] parameterNames = ParameterNameUtils.getMethodParamNames(method);
115         System.out.println(Arrays.toString(parameterNames));
116         
117         method = clazz.getDeclaredMethod("sameType", Type[].class, Class[].class);
118         parameterNames = ParameterNameUtils.getMethodParamNames(method);
119         System.out.println(Arrays.toString(parameterNames));
120     }
121 }
View Code

最後需要新增配置,攔截哪些介面或是實現類,具體看個人業務

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:aop="http://www.springframework.org/schema/aop"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6                         http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 7                         http://www.springframework.org/schema/aop 
 8                          http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
 9                         
10 
11     <bean id="externalApiMethodInterceptor" class="com.xxx.flipclass.sdk.framework.aop.ExecutionApiLogMethodInterceptor" />
12 
13     <aop:config proxy-target-class="true">
14         <aop:pointcut id="externalApiMethodPointcut" expression="!execution(* com.xxx.flipclass.sdk.client.interfaces..*.loginInfoService.*(..)) and (execution(* com.xxx.*.sdk.client.interfaces..*.*Client*.*(..)) || execution(* com.xxx.*.sdk.client.interfaces..*.*Service*.*(..)))" />
15         <aop:advisor advice-ref="externalApiMethodInterceptor" pointcut-ref="externalApiMethodPointcut" />
16     </aop:config>
17 </beans>
View Code

 

相關文章