一、概述
在SpringMVC中,除了Filter和Interceptor拦截器外,还有对请求Controller的处理,即对请求和响应内容的处理和对请求参数的处理。
二、ControllerAdvice
@ControllerAdvice本质上同Component一样,因此也会被当成组件扫描。
其中@ExceptionHandler常用到。即抛出的异常会被统一拦截处理。在项目中对MethodArgumentNotValidException异常拦截处理- @ControllerAdvice
- public class GlobalHandler {
- @ExceptionHandler(MethodArgumentNotValidException.class)
- public Result exceptionHandler(MethodArgumentNotValidException e) {
- Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(),
- BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg());
- logger.error("req params error", e);
- return result;
- }
- }
- // 上述对MethodArgumentNotValidException异常统一拦截后并统一返回异常
复制代码 实现原理:- public class DispatcherServlet extends FrameworkServlet {
- // ......
- protected void initStrategies(ApplicationContext context) {
- initMultipartResolver(context);
- initLocaleResolver(context);
- initThemeResolver(context);
- initHandlerMappings(context);
- initHandlerAdapters(context);
- // 处理所有异常
- initHandlerExceptionResolvers(context);
- initRequestToViewNameTranslator(context);
- initViewResolvers(context);
- initFlashMapManager(context);
- }
- // ......
- }
复制代码 DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里- public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
- implements ApplicationContextAware, InitializingBean {
- // ......
- private void initExceptionHandlerAdviceCache() {
- // ......
- List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
- AnnotationAwareOrderComparator.sort(adviceBeans);
- for (ControllerAdviceBean adviceBean : adviceBeans) {
- ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
- if (resolver.hasExceptionMappings()) {
- // 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来
- this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
- if (logger.isInfoEnabled()) {
- logger.info("Detected @ExceptionHandler methods in " + adviceBean);
- }
- }
- // ......
- }
- }
- }
- public ExceptionHandlerMethodResolver(Class<?> handlerType) {
- // 查询当前类中@ExceptionHandler的方法
- for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
- // 获取方法的异常类型
- for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
- // 添加到缓存中
- addExceptionMapping(exceptionType, method);
- }
- }
- }
复制代码 此构造函数在new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)时候调用,传进来的requestResponseBodyAdvice就刚好是在初始化RequestMappingHandlerAdapter的时候全局扫描进来的所有的增强器们
3.2 如何使用
请求日志的打印,用于POST请求的,这里实现了RequestBodyAdvice用来打印请求参数,也使用了ResponseBodyAdvice打印返回的信息- // 处理返回结果时,如果异常不为空,则进行异常处理
- private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
- @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
- @Nullable Exception exception) throws Exception {
- boolean errorView = false;
- if (exception != null) {
- if (exception instanceof ModelAndViewDefiningException) {
- // ...
- }
- else {
- Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
- // 调用异常Handler处理
- mv = processHandlerException(request, response, handler, exception);
- errorView = (mv != null);
- }
- }
- // ...
- }
复制代码- class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
- //它持有所有的,记住是所有的advice们
- private final List<Object> requestBodyAdvice = new ArrayList<>(4);
- private final List<Object> responseBodyAdvice = new ArrayList<>(4);
- // 可以看到这是个通用的方法。内来进行区分存储的 getAdviceByType这个区分方法可以看一下
- // 兼容到了ControllerAdviceBean以及beanType本身
- public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
- this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
- this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
- }
- @Override
- public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
- throw new UnsupportedOperationException("Not implemented");
- }
- @Override
- public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
- throw new UnsupportedOperationException("Not implemented");
- }
- // 可以看到最终都是委托给具体的Advice去执行的(supports方法)
- // 特点:符合条件的所有的`Advice`都会顺序的、依次的执行
- @Override
- public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
- for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
- if (advice.supports(parameter, targetType, converterType)) {
- request = advice.beforeBodyRead(request, parameter, targetType, converterType);
- }
- }
- return request;
- }
- ... // 其余方法略。处理逻辑同上顺序执行。
- // 最重要的是如下这个getMatchingAdvice()匹配方法
- private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
- // 简单的说你想要的是Request的还是Response的List呢?
- List<Object> availableAdvice = getAdvice(adviceType);
- if (CollectionUtils.isEmpty(availableAdvice)) {
- return Collections.emptyList();
- }
- List<A> result = new ArrayList<>(availableAdvice.size());
- for (Object advice : availableAdvice) {
- if (advice instanceof ControllerAdviceBean) {
- ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
- // 这里面会调用beanTypePredicate.test(beanType)方法
- // 也就是根据basePackages等等判断此advice是否是否要作用在本类上
- if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
- continue;
- }
- advice = adviceBean.resolveBean();
- }
- // 当前的advice若是满足类型要求的,那就添加进去 最终执行切面操作
- if (adviceType.isAssignableFrom(advice.getClass())) {
- result.add((A) advice);
- }
- }
- return result;
- }
- }
复制代码 最后打印的日志信息
四、HandlerMethodReturnValueHandler
4.1 HandlerMethodReturnValueHandler
对返回信息做特殊处理,且只会被调用一次,谨慎处理
默认情况下,spring会调用RequestResponseBodyMethodProcessor来处理返回执行。它实现了HandlerMethodReturnValueHandler的handleReturnValue的方法,
如何选择returnHandler是在HandlerMethodReturnValueHandlerComposite类做了选择- AbstractMessageConverterMethodArgumentResolver(一般实际为RequestResponseBodyMethodProcessor):
- // 唯一构造函数,指定所有的advices
- public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
- Assert.notEmpty(converters, "'messageConverters' must not be empty");
- this.messageConverters = converters;
- this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
- this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
- }
复制代码- // 生成日志信息,并放到request中
- @ControllerAdvice
- public class RequestBodyAdviceHandler implements RequestBodyAdvice {
- public RequestBodyAdviceHandler() {
- }
- public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
- Method method = methodParameter.getMethod();
- Class<?> declaringClass = method.getDeclaringClass();
- RestController RestController = (RestController)declaringClass.getAnnotation(RestController.class);
- return RestController != null;
- }
- public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
- return inputMessage;
- }
- public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
- this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
- return body;
- }
- public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
- this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
- return body;
- }
- private String toJSONString(Object body, MethodParameter parameter) {
- IgnoreLogBody ignore = (IgnoreLogBody)parameter.getMethodAnnotation(IgnoreLogBody.class);
- if (ignore == null) {
- return JSON.toJSONString(body);
- } else {
- String[] ignoreKey = ignore.ignoreKey();
- return ignoreKey != null && ignoreKey.length != 0 ? JSON.toJSONString(body, new IgnoreLogPropertyFilter(ignore.ignoreKey(), ignore.key()), new SerializerFeature[0]) : JSON.toJSONString(body);
- }
- }
- private void writeRequestLog(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
- HttpServletRequest request = RequestHelper.getRequest();
- request.setAttribute("_REQUEST_STARTTIME_", System.currentTimeMillis());
- String requestId = request.getHeader("_REQUEST_ID_");
- if (StringUtils.isEmptyStr(requestId)) {
- requestId = TraceContext.traceId();
- }
- if (StringUtils.isEmptyStr(requestId) || "Ignored_Trace".equals(requestId)) {
- requestId = UUID.randomUUID().toString().replaceAll("-", "");
- }
- request.setAttribute("_REQUEST_ID_", requestId);
- StringBuilder info = new StringBuilder("==>\n");
- info.append("[*=请求requestID=]>: ").append(requestId).append("\n");
- info.append("[==请求地址=======]>: ").append(request.getRequestURL().toString()).append("\n");
- info.append("[==请求方法=======]>: ").append(request.getMethod()).append("\n");
- info.append("[==操作用户=======]>: ").append(UserInfoContext.getCurrentUserCode()).append("\n");
- info.append("[==客户IP========]>: ").append(RequestHelper.getClientIP()).append("\n");
- info.append("[==映射方法=======]>: ").append(parameter.getMethod()).append(".").append("\n");
- if (body == null) {
- info.append("[==请求参数=======]>: ").append("该接口未定义参数或参数为空");
- } else if (converterType == FastJsonHttpMessageConverter.class) {
- info.append("[==请求参数=======]>: ").append(this.toJSONString(body, parameter));
- } else {
- info.append("[==请求参数=======]>: ").append(converterType);
- }
- info.append("\n");
- request.setAttribute("_REQUEST_LOG_INFO_", info);
- }
- }
复制代码 知道了HandlerMethodReturnValueHandler是用于返回数据的handler,那么自己实现这个类用户封装自己返回的方法。
4.2 如何使用
一般请求下,返回的数据如下:- @ControllerAdvice
- public class ResponseBodyAdviceHandler implements ResponseBodyAdvice<Object> {
- private static final IEventLogger logger = Logtube.getLogger(ResponseBodyAdviceHandler.class.getName());
- public ResponseBodyAdviceHandler() {
- }
- public boolean supports(MethodParameter methodParamter, Class<? extends HttpMessageConverter<?>> converterType) {
- return this.isRestController(methodParamter);
- }
- public Object beforeBodyWrite(Object body, MethodParameter methodParamter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest req, ServerHttpResponse response) {
- HttpServletRequest request = RequestHelper.getRequest();
- if (request == null) {
- return body;
- } else {
- StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_");
- if (info == null) {
- info = new StringBuilder();
- }
- String requestBodyData = null;
- if (body == null) {
- requestBodyData = null;
- } else if (selectedContentType.includes(MediaType.APPLICATION_JSON)) {
- requestBodyData = JSON.toJSONString(body);
- } else {
- requestBodyData = body.toString();
- }
- Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_");
- info.append("[==响应结果=======]>: ").append(requestBodyData == null ? "null" : requestBodyData);
- info.append("\n");
- if (startTime != null) {
- info.append("[==执行耗时=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
- }
- String requestId = (String)request.getAttribute("_REQUEST_ID_");
- logger.info(info.toString());
- return body;
- }
- }
- private boolean isRestController(MethodParameter methodParamter) {
- RestController annotation = (RestController)methodParamter.getDeclaringClass().getAnnotation(RestController.class);
- return annotation != null;
- }
- }
复制代码 data是其真实数据。在Controller层,需要调用如下- @Override
- public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
- ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
- // 选择返回的handler处理
- HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
- if (handler == null) {
- throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
- }
- // 执行handleReturnValue将数据写入到reponse流中
- handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
- }
复制代码 那么通过注解@AutoResult可以将ResponseResultUtil替换调用,就是在处理HandlerMethodReturnValueHandler时,处理自己handleReturnValue返回数据。但是这里有一个点,就是实现了自己的类,那么自己实现的ResponseBodyAdvice,将不会被调用,因为AutoResultReturnValueHandler拦截的请求,会直接返回,不会再调用后续的handler方法。- // 被@ResponseBody注解的方法,会被执行该类
- @Override
- public boolean supportsReturnType(MethodParameter returnType) {
- return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
- returnType.hasMethodAnnotation(ResponseBody.class));
- }
- //将returnValue写入到流中
- @Override
- public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
- ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
- throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
- mavContainer.setRequestHandled(true);
- ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
- ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
- // Try even with null return value. ResponseBodyAdvice could get involved.
- writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |