Spring框架下的 “接口調用、MVC請求” 調用參數、返回值、耗時信息輸出,springmvc
主要攔截前端或後天的請求,打印請求方法參數、返回值、耗時、異常的日志。方便開發調試,能很快定位到問題出現在哪個方法中。


前端請求攔截,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
[email protected]
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
[email protected]
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