Fork me on GitHub
19 December 2017

在开发中,MVC提供的接口参数解析类型满足不了需求,这时就需要去自定义自己的参数解析器,无论是XML,JSON等数据类型。其实Spring MVC已经提供了方法来解决该问题。可使用mvc:argument-resolvers来注入自定义的参数解析器。

<mvc:argument-resolvers>
    <bean />
</mvc:argument-resolvers>

Configures HandlerMethodArgumentResolver types to support custom controller method argument types. Using this option does not override the built-in support for resolving handler method arguments. To customize the built-in support for argument resolution configure RequestMappingHandlerAdapter directly.

通过以上描述,我们知道可通过该配置添加Controller的参数解析器。

Spring MVC 3.2以前通过AnnotationMethodHandlerAdapter类来实现,3.2以后被弃用,改为RequestMappingHandlerAdapter类,二者都实现了HandlerAdapterhandle方法。

// AnnotationMethodHandlerAdapter
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	Class<?> clazz = ClassUtils.getUserClass(handler);
	Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
	.....
	// 调用invokeHandlerMethod
	if (this.synchronizeOnSession) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
				return invokeHandlerMethod(request, response, handler);
			}
		}
	}

	return invokeHandlerMethod(request, response, handler);
}

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {

	ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
  	// 调用ServletHandlerMethodResolver的resolveHandlerMethod方法,该方法负责解析参数
	Method handlerMethod = methodResolver.resolveHandlerMethod(request);
	......
	return mav;
}



// RequestMappingHandlerAdapter
// RequestMappingHandlerAdapter继承于AbstractHandlerMethodAdapter,AbstractHandlerMethodAdapter是个抽象类,
// 该类实现了HandlerAdapter的handle方法,该方法调用handleInternal抽象方法,并在handleInternal中进行参数解析设置
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	......

    // 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);
    }

    ......

    return mav;
}

// 由该方法进行参数解析器配置
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		// 把this.argumentResolvers注入到ServletInvocableHandlerMethod中
		invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

		......

        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);
	}
	finally {
		webRequest.requestCompleted();
	}
}

/**
 * Return the list of argument resolvers to use including built-in resolvers
 * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
 */
 // 该方法会在afterPropertiesSet中调用,也就是说在属性设置完成后会创建参数解析器
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
	List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

	// Annotation-based argument resolution
	resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
	resolvers.add(new RequestParamMapMethodArgumentResolver());
	resolvers.add(new PathVariableMethodArgumentResolver());
	resolvers.add(new PathVariableMapMethodArgumentResolver());
	resolvers.add(new MatrixVariableMethodArgumentResolver());
	resolvers.add(new MatrixVariableMapMethodArgumentResolver());
 	// GET请求会使用ServletModelAttributeMethodProcessor进行参数解析
	resolvers.add(new ServletModelAttributeMethodProcessor(false));
	// 带有RequestBody注解的请求由RequestResponseBodyMethodProcessor处理
	resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
	resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
	resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new RequestHeaderMapMethodArgumentResolver());
	resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new SessionAttributeMethodArgumentResolver());
	resolvers.add(new RequestAttributeMethodArgumentResolver());

	// Type-based argument resolution
	resolvers.add(new ServletRequestMethodArgumentResolver());
	resolvers.add(new ServletResponseMethodArgumentResolver());
	resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
	resolvers.add(new RedirectAttributesMethodArgumentResolver());
	resolvers.add(new ModelMethodProcessor());
	resolvers.add(new MapMethodProcessor());
	resolvers.add(new ErrorsMethodArgumentResolver());
	resolvers.add(new SessionStatusMethodArgumentResolver());
	resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

	// Custom arguments
	// 自定义的解析器会在此处被添加
	if (getCustomArgumentResolvers() != null) {
		resolvers.addAll(getCustomArgumentResolvers());
	}

	// Catch-all
	resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
	resolvers.add(new ServletModelAttributeMethodProcessor(true));

	return resolvers;
}



// ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 处理请求参数, 调用父类InvocableHandlerMethod的invokeForRequest方法
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    ......
}

// InvocableHandlerMethod
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 进行参数解析处理
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

  	......

    return returnValue;
}

/**
 * Get the method argument values for the current request.
 */
// 真正的参数解析在此处完成
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
            	// 参数解析
              	// 调用HandlerMethodArgumentResolver的实现类来进行处理
                args[i] = this.argumentResolvers.resolveArgument(
                        parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            }
            catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                }
                throw ex;
            }
        }
        if (args[i] == null) {
            throw new IllegalStateException("Could not resolve method parameter at index " +
                    parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
                    ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
        }
    }
    return args;
}

以上就是参数解析的大概过程,如下图所示:

DispatcherServletDispatcherServletHandlerAdapterHandlerAdapterAbstractHandlerMethodAdapterAbstractHandlerMethodAdapterRequestMappingHandlerAdapterRequestMappingHandlerAdapterInvocableHandlerMethodInvocableHandlerMethodHandlerMethodArgumentResolverCompositeHandlerMethodArgumentResolverCompositeHandlerMethodArgumentResolverHandlerMethodArgumentResolverdoDispatchhandlehandleInternalinvokeHandlerMethodinvokeAndHandle, invokeForRequest, getMethodArgumentValuessupportsParameter, resolveArgument

下面我们来看看如何定义自己的参数解析器,当请求为GET时,使用Spring默认的GET请求的参数解析器,请求为POST时,使用messageConvert解析器,与使用@RequestBody效果一致, 本例使用Jackson作为JSON解析器。

spring-mvc.xml

<mvc:annotation-driven>
	<mvc:argument-resolvers>
		<bean id="multiArgumentResolvers" class="net.cofcool.annotation.MultiArgumentResolvers" />
	</mvc:argument-resolvers>
</mvc:annotation-driven>

MultiRequestTypes

package net.cofcool.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiRequestTypes {

}

MultiArgumentResolvers

package net.cofcool.annotation;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;


@Slf4j
public class MultiArgumentResolvers implements HandlerMethodArgumentResolver {

    // POST请求,Content-Type为JSON
    private HandlerMethodArgumentResolver jsonResolver;

    // get请求
    private HandlerMethodArgumentResolver requestParamResolver = new ServletModelAttributeMethodProcessor(false);


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return hasMultiRequestTypesAnnotation(parameter);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

        if (checkGetRequest(parameter, request)) {
            return requestParamResolver
                .resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }

        try {
            return getJsonResolver().resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        } catch (NullPointerException e) {
            log.info("resolve json error, try parse by get processor: " , e);
            return requestParamResolver
                .resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
    }

    private boolean checkGetRequest(MethodParameter parameter, HttpServletRequest request) {
        return
            HttpMethod.GET.matches(request.getMethod()) && hasMultiRequestTypesAnnotation(parameter);
    }

    private boolean hasMultiRequestTypesAnnotation(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(MultiRequestTypes.class);
    }

    @SuppressWarnings("unchecked")
    private HandlerMethodArgumentResolver getJsonResolver() {
        if (this.jsonResolver == null) {
            List resolvers = new ArrayList<>();
            resolvers.add(getJackson2HttpMessageConverter());

            jsonResolver = new RequestResponseBodyMethodProcessor(resolvers);
        }

        return jsonResolver;
    }

    AbstractJackson2HttpMessageConverter getJackson2HttpMessageConverter() {
        AbstractJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        converter.setSupportedMediaTypes(mediaTypes);
        converter.setPrettyPrint(true);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(Include.NON_NULL);

        return converter;
    }
}