自定义参数解析器同一个参数支持多种Content-Type

本文深入探讨SpringMVC的参数解析流程,解决同时支持application/json和application/x-www-form-urlencoded的问题,通过自定义参数解析器实现灵活的参数处理。

一堆废话

事出有因, 原先上线的接口现在被要求用Java重写,按照原暴露出去的文档然后毫无疑问的,按照Java的惯例,
一定是@RequestBody然后去接收application/json;charset=utf-8,然后一通参数接收处理逻辑。

结果测试都通过了,上线的时候,刚把原接口切到新接口上,日志就狂飙application/x-www-form-urlencoded;charset=utf-8 NOT SUPPORT
What?然后就一通问号脸。赶紧把接口切回到老接口,然后跑去问PHP的同事,什么情况,原对接参数不是json吗?
然后才明白,草,原来要同时支持application/json;charset=utf-8application/x-www-form-urlencoded;charset=utf-8

PHP咱是不懂的,但是Java对这个需求的原生支持却不是很好,印象中没有现成的。
因为一般我们定义对象接收参数,如果使用了@RequestBody接收,那么传参一定要使用post+一个对应的参数解析器一个可读的流,按照现在的情况即application/json;charset=utf-8
要么是直接一个对象接收,不要加任何注解,这个时候对应的Content-Typeapplication/x-www-form-urlencoded;charset=utf-8则参数可正常解析。

但是这两种情况是矛盾的,如果一个加了@RequestBody的参数对应的Content-Typeapplication/x-www-form-urlencoded;charset=utf-8, 则最终无法解析。反过来如果一个未加@RequestBody的参数对应的Content-Typeapplication/json;charset=utf-8则也无法解析。

那么现在就只能来看一下如何定义一个自定义的参数解析器来完成这个需求了。但是这个自定义的参数解析器还有点不太一样,因为数据格式本身不是我们自定义的,本身就存在对应标准的解析器。只是SpringMVC在根据参数去找对应解析器的时候没有对应起来。我们现在只要让自己的解析器能够让这个参数转发到对应可以解析的参数解析器上就可以了。

探究Springmvc参数解析器工作流程

现在就要不怕麻烦的还看一下原来的参数解析器是如何工作的,毕竟不知道它怎么写的我也不知道怎么抄。

SpringMVC项目,二话不说,直接找到org.springframework.web.servlet.DispatcherServlet#doDispatch这个方法,看整个处理器的流程,这里直接简化找到最终映射到方法后的执行

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

方法跳转流程如下

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
   
   
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
   
   
		return handleInternal(request, response, (HandlerMethod) handler);
	}
}

我再跳org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
   
   
	/**
	 * 参数解析器列表
	 */
	@Nullable
	private HandlerMethodArgumentResolverComposite argumentResolvers;
	
	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
   
   
		ModelAndView mav;
		checkRequest(request);
		// 这里删除了大量相关判断方法,只关注实际跳转执行方法
		// No synchronization on session demanded at all...
		mav = invokeHandlerMethod(request, response, handlerMethod);	
	}
	
	/**
	 * 上面跳到了这里
	 */
	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
   
   
		// 这里又删除了大量的代码
		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		// 将本类自己的参数解析器列表赋值给ServletInvocableHandlerMethod 
		if (this.argumentResolvers != null) {
   
   
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
   
   
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		invocableMethod.setDataBinderFactory(binderFactory);
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		// 带着使命继续往下执行
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
	}
}

接着跳到了org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
   
   
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
   
   
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		// 再删除无关代码
	}

}

这里总算看到了一些关键信息了org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

public class InvocableHandlerMethod extends HandlerMethod {
   
   
	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
   
   
		// 这里就是解析参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
   
   
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}
	
	/**
	 * 上面方法跳到了这里
	 */
	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
   
   
		// 这里就是获取入参的参数类型,如单个字符串的每个参数,或者是某个对象参数,甚至是HttpServletRequest
		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
   
   
			return EMPTY_ARGS;
		}
		
	
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值