你的位置:首页 > Java教程

[Java教程]Spring框架下的 “接口调用、MVC请求” 调用参数、返回值、耗时信息输出


主要拦截前端或后天的请求,打印请求方法参数、返回值、耗时、异常的日志。方便开发调试,能很快定位到问题出现在哪个方法中。

 

 

前端请求拦截,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.

 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 Method103    * @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 <??> 2 <beans ="http://www.springframework.org/schema/beans" 3   ="http://www.w3.org/2001/ 4   ="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