主要攔截前端或後天的請求,列印請求方法引數、返回值、耗時、異常的日誌。方便開發除錯,能很快定位到問題出現在哪個方法中。
前端請求攔截,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 }
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>
過濾靜態資源,一些靜態資源不需要攔截,在這裡配置黑名單不讓它進入攔截器。
下面是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 }
上面使用到了方法引數獲取的工具類,程式碼如下:
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 }
最後需要新增配置,攔截哪些介面或是實現類,具體看個人業務
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>