zoukankan      html  css  js  c++  java
  • spring mvc从@ResponseBody取到json发现中文乱码

      问题背景:如题。

      问题定位:代码跟踪,从源头入手,一步一步跟进,直到设置中文编码的地方。

      问题代码:

    /**
         * 获取单个测试桩接口内容
         *
         * @author wulinfeng
         * @param method
         * @return
         */
        @RequestMapping(value = "/getMethod/{method}", method = RequestMethod.GET)
        public @ResponseBody String getMethodContent(@PathVariable("method") String method)
        {
            return testPillingService.getMethodContent(method);
        }

      怎么切入?是个问题。既然是从源头跟起,那么还是拿DispatcherServlet来开刀吧。所有Controller请求都不可避免的要到这个请求集散地来,而这里最主要的就是doDispatch方法了:

    /**
         * Process the actual dispatching to the handler.
         * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
         * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
         * to find the first that supports the handler class.
         * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
         * themselves to decide which methods are acceptable.
         * @param request current HTTP request
         * @param response current HTTP response
         * @throws Exception in case of any kind of processing failure
         */
        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
    
                try {
                    processedRequest = checkMultipart(request);
                    multipartRequestParsed = (processedRequest != request);
    
                    // Determine handler for the current request.
                    mappedHandler = getHandler(processedRequest);
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }
    
                    // Determine handler adapter for the current request.
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                    // Process last-modified header, if supported by the handler.
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (logger.isDebugEnabled()) {
                            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
    
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
    
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    // Instead of postHandle and afterCompletion
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                }
                else {
                    // Clean up any resources used by a multipart request.
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }

      看961行,得到了请求参数、请求url映射的处理器、处理的适配器后,进入真正的请求处理流程。先到AbstractHandlerMethodAdapter类,再到子类RequestMappingHandlerAdapter里  

    protected ModelAndView handleInternal(HttpServletRequest request,
                HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
            ModelAndView mav;
            checkRequest(request);
    
            // Execute invokeHandlerMethod in synchronized block if required.
            if (this.synchronizeOnSession) {
                HttpSession session = request.getSession(false);
                if (session != null) {
                    Object mutex = WebUtils.getSessionMutex(session);
                    synchronized (mutex) {
                        mav = invokeHandlerMethod(request, response, handlerMethod);
                    }
                }
                else {
                    // No HttpSession available -> no mutex necessary
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No synchronization on session demanded at all...
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
    
            if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
                if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                    applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
                }
                else {
                    prepareResponse(response);
                }
            }
    
            return mav;
        }
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
            ServletWebRequest webRequest = new ServletWebRequest(request, response);
    
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
    
            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
    
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }
    
            return getModelAndView(mavContainer, modelFactory, webRequest);
        }

       进入RequestMappingHandlerAdapter类832行,我们终于到了把处理响应的地方了,接着看ServletInvocableHandlerMethod类107行

    public void invokeAndHandle(ServletWebRequest webRequest,
                ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    
            Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
            setResponseStatus(webRequest);
    
            if (returnValue == null) {
                if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
                    mavContainer.setRequestHandled(true);
                    return;
                }
            }
            else if (StringUtils.hasText(this.responseReason)) {
                mavContainer.setRequestHandled(true);
                return;
            }
    
            mavContainer.setRequestHandled(false);
            try {
                this.returnValueHandlers.handleReturnValue(
                        returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
            }
            catch (Exception ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
                }
                throw ex;
            }
        }

       InvocableHandlerMethod类主要是通过反射来调用Controller方法的

    /**
         * Invoke the method after resolving its argument values in the context of the given request.
         * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s.
         * The {@code providedArgs} parameter however may supply argument values to be used directly,
         * i.e. without argument resolution. Examples of provided argument values include a
         * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
         * Provided argument values are checked before argument resolvers.
         * @param request the current request
         * @param mavContainer the ModelAndViewContainer for this request
         * @param providedArgs "given" arguments matched by type, not resolved
         * @return the raw value returned by the invoked method
         * @exception Exception raised if no suitable argument resolver can be found,
         * or if the method raised an exception
         */
        public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
                Object... providedArgs) throws Exception {
    
            Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
            if (logger.isTraceEnabled()) {
                StringBuilder sb = new StringBuilder("Invoking [");
                sb.append(getBeanType().getSimpleName()).append(".");
                sb.append(getMethod().getName()).append("] method with arguments ");
                sb.append(Arrays.asList(args));
                logger.trace(sb.toString());
            }
            Object returnValue = doInvoke(args);
            if (logger.isTraceEnabled()) {
                logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
            }
            return returnValue;
        }
    /**
         * Invoke the handler method with the given argument values.
         */
        protected Object doInvoke(Object... args) throws Exception {
            ReflectionUtils.makeAccessible(getBridgedMethod());
            try {
                return getBridgedMethod().invoke(getBean(), args);
            }
            catch (IllegalArgumentException ex) {
                assertTargetBean(getBridgedMethod(), getBean(), args);
                String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
                throw new IllegalStateException(getInvocationErrorMessage(message, args), ex);
            }
            catch (InvocationTargetException ex) {
                // Unwrap for HandlerExceptionResolvers ...
                Throwable targetException = ex.getTargetException();
                if (targetException instanceof RuntimeException) {
                    throw (RuntimeException) targetException;
                }
                else if (targetException instanceof Error) {
                    throw (Error) targetException;
                }
                else if (targetException instanceof Exception) {
                    throw (Exception) targetException;
                }
                else {
                    String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
                    throw new IllegalStateException(msg, targetException);
                }
            }
        }

       取到值后,关键是ServletInvocableHandlerMethod的handleReturnValue方法,子类RequestResponseBodyMethodProcessor复写了该方法

    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
            mavContainer.setRequestHandled(true);
    
            // Try even with null return value. ResponseBodyAdvice could get involved.
            writeWithMessageConverters(returnValue, returnType, webRequest);
        }
    /**
         * Writes the given return value to the given web request. Delegates to
         * {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
         */
        protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest)
                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
            ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
            ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
            writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
        }

      引发血案的馒头就在这个方法里了

    /**
         * Writes the given return type to the given output message.
         * @param value the value to write to the output message
         * @param returnType the type of the value
         * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
         * @param outputMessage the output message to write to
         * @throws IOException thrown in case of I/O errors
         * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated by {@code Accept} header on
         * the request cannot be met by the message converters
         */
        @SuppressWarnings("unchecked")
        protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
                ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
            Class<?> valueType = getReturnValueType(value, returnType);
            Type declaredType = getGenericType(returnType);
            HttpServletRequest request = inputMessage.getServletRequest();
            List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
            List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
    
            if (value != null && producibleMediaTypes.isEmpty()) {
                throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
            }
    
            Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
            for (MediaType requestedType : requestedMediaTypes) {
                for (MediaType producibleType : producibleMediaTypes) {
                    if (requestedType.isCompatibleWith(producibleType)) {
                        compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
                    }
                }
            }
            if (compatibleMediaTypes.isEmpty()) {
                if (value != null) {
                    throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
                }
                return;
            }
    
            List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
            MediaType.sortBySpecificityAndQuality(mediaTypes);
    
            MediaType selectedMediaType = null;
            for (MediaType mediaType : mediaTypes) {
                if (mediaType.isConcrete()) {
                    selectedMediaType = mediaType;
                    break;
                }
                else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                    selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                    break;
                }
            }
    
            if (selectedMediaType != null) {
                selectedMediaType = selectedMediaType.removeQualityValue();
                for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                    if (messageConverter instanceof GenericHttpMessageConverter) {
                        if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(
                                declaredType, valueType, selectedMediaType)) {
                            value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
                                    (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                    inputMessage, outputMessage);
                            if (value != null) {
                                addContentDispositionHeader(inputMessage, outputMessage);
                                ((GenericHttpMessageConverter<T>) messageConverter).write(
                                        value, declaredType, selectedMediaType, outputMessage);
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Written [" + value + "] as "" + selectedMediaType +
                                            "" using [" + messageConverter + "]");
                                }
                            }
                            return;
                        }
                    }
                    else if (messageConverter.canWrite(valueType, selectedMediaType)) {
                        value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                inputMessage, outputMessage);
                        if (value != null) {
                            addContentDispositionHeader(inputMessage, outputMessage);
                            ((HttpMessageConverter<T>) messageConverter).write(value, selectedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Written [" + value + "] as "" + selectedMediaType +
                                        "" using [" + messageConverter + "]");
                            }
                        }
                        return;
                    }
                }
            }
    
            if (value != null) {
                throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
            }
        }

       关键变量是selectedMediaType,由它决定了Content-Type究竟是啥,中文会不会有乱码。它又是怎么来的呢,接着看代码。看本文的顶端,我们定义的Controller方法返回的String,所以valueType、declaredType都是String,然后看响应的Content-Type都有哪些

    /**
         * Returns the media types that can be produced:
         * <ul>
         * <li>The producible media types specified in the request mappings, or
         * <li>Media types of configured converters that can write the specific return value, or
         * <li>{@link MediaType#ALL}
         * </ul>
         * @since 4.2
         */
        @SuppressWarnings("unchecked")
        protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
            Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
            if (!CollectionUtils.isEmpty(mediaTypes)) {
                return new ArrayList<MediaType>(mediaTypes);
            }
            else if (!this.allSupportedMediaTypes.isEmpty()) {
                List<MediaType> result = new ArrayList<MediaType>();
                for (HttpMessageConverter<?> converter : this.messageConverters) {
                    if (converter instanceof GenericHttpMessageConverter && declaredType != null) {
                        if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
                            result.addAll(converter.getSupportedMediaTypes());
                        }
                    }
                    else if (converter.canWrite(valueClass, null)) {
                        result.addAll(converter.getSupportedMediaTypes());
                    }
                }
                return result;
            }
            else {
                return Collections.singletonList(MediaType.ALL);
            }
        }

      从canWrite方法过滤出StringHttpMessageConverter是支持的转换类,而它的Content-Type就是text/plain;charset=ISO-8859-1,而且它就放在producibleMediaTypes列表的第一个,后面直接就取ISO-8859-1作为字符编码了,中文乱码就来了。

      定位到这里,要解决中文乱码也就简单了,我们看上面方法开始标红的代码,其实先是从produces注解开始拿字符编码的,取不到才去转换器里一个个取,所以我们只要在Controller上加个注解produces = "application/json;charset=utf-8"就解决了。

      

  • 相关阅读:
    MyEclipse连接Oracle,出现ORA00604和ORA12705异常
    MyEclipse连接Oracle,出现ORA00604和ORA12705异常
    SSH Secure Shell Client 乱码问题
    android开发(20) 使用adb建立pc和android设备之间的连接。usb连接方式。
    android开发(22)使用正则表达式 。从一个字符串中找出数字,多次匹配。
    .net 中,使用c# 语言 ,执行exe程序。
    [转载]大数据存取的选择:行存储还是列存储?
    android开发(21)蜂鸣提示音和震动提示的实现。
    arcgis for android 学习 (8) 空间查询 点击某点,选中该点所在单位区域。
    android开发(24)使用SQLiteOpenHelper的onUpgrade实现数据库版本升级
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/7729922.html
Copyright © 2011-2022 走看看