Spring MVC 自定义接口参数解析器
在开发中,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
类,二者都实现了HandlerAdapter
的handle
方法。
// 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;
}
以上就是参数解析的大概过程,如下图所示:
下面我们来看看如何定义自己的参数解析器,当请求为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;
}
}