假装看源码之springmvc (一) 如何进行请求参数的解析

     首先,我们先回忆下日常开发中的骚操作,比如请求一个url /product?id=xxx&name=yyy, 再controol中的对应映射方法,只要填一个id的参数,就能得到xxx的值,又比如我想得到request的值,直接填个request,它就给我了,仿佛springmvc要啥就有啥,非常的神奇,一直想弄明白这是怎么个原理,碍于水平菜,心态又浮躁,一直没能一探究竟,下了一本springmvc的源码分析书,前面看的还好,一深入就被带入繁枝细节不能自拔,今天天气晴朗,心情愉悦,突然又想起这个事情,于是打算假装的分析下这个过程到底是怎么回事。

    在知道springmvc外围方法运行原理(即service方法及调用链三层以内...)的基础上,一个请求是由handlerMapping找到对应的handler及intercepts交给despatcherServlet去叫handlerAdpter执行,handler的执行的过程是在handlerAdpter中。这个参数解析的过程我可以明确的判断是在,handlerAdpter去执行具体的handler之间发生的

    那么是怎么实现的呢?假如要我做一个这个功能我觉得很简单,在从handlerMapping中找到的handler,它里面肯定封装了对应处理器类的Class,方法的method,那么我只要拿到method根据它的paramter的类型,去找我目前能不能提供这个参数。找到了把请求param的String类型进行转换。赋值给方法的参数即可。但是springmvc中可以获得那么多类型,而且每种类型的解析方式都是不同的(比如request是直接提供的,String name是从request的参数中取的,Model好像是new出来的 等等),假如能够做好这个功能,大量的逻辑判断写到一起,扩展性也极差。

    下面我们来看下springmvc是怎么做的。

    一、首先既然是handlerAdpter干活的,我们就来看下一般我们用的RequestMappingHandlerAdapter的类长什么样

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {   //实现了initializingBean,初始化会调afterPropertiesSet
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;  //看名字叫自定义参数解析
	private HandlerMethodArgumentResolverComposite argumentResolvers; //看名字叫参数解析的组合
	private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers; //initBinder的参数解析(暂不分析全局的影响)
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; //看名字叫自定返回值的处理
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers; //返回值处理的组合
	private List<ModelAndViewResolver> modelAndViewResolvers; 

      1.1、从上图我们知道此adater它首先可以拿到BeanFactory,然后会调用一个初始化方法,它的成员变量包括一些参数解析和返回值解析的数组,以及一个叫做xx组合的。下面就看下初始化的方法(构造函数主要构建了messageConverters(@XXBody注解用的),本次不研究这个)。

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {   //实现了initializingBean,初始化会调afterPropertiesSet
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;  //看名字叫自定义参数解析
	private HandlerMethodArgumentResolverComposite argumentResolvers; //看名字叫参数解析的组合
	private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers; //initBinder的参数解析(暂不分析全局的影响)
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; //看名字叫自定返回值的处理
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers; //返回值处理的组合
	private List<ModelAndViewResolver> modelAndViewResolvers; 

   1. 2、初始化就是初始这2个组合器,这个组合器内部(略)很简单就是一个List类型用来装,一个map类型用来缓存已经找到解析器的参数,再加上对list内的resovers进行操作。我们先看下getDefaultArgumentResolvers()方法。

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
		// Annotation-based argument resolution  //基于注解的解析器
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));//@ReuqestPraram用的
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());//@PathVariable用的
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false)); //如果是false还可以解析无注解但非简单类型的PO对象!
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		// Type-based argument resolution //基于参数的解析器
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());

 1.3、上面我们可以看到默认加了很多参数解析器,既然有默认肯定还可以加自定义的,这是后话。大部分情况默认的就可以满足我们的需求,而上面有些是Resolver结尾,有些是Processor结尾,后者就是同时实现了参数解析和返回值解析的。下面我们就只看一个RequestParamMethodArgumentResolver(factoy,false)的实现.

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
	public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
		this.useDefaultResolution = useDefaultResolution;
	}
	@Override  //参数解析的接口方法实现----判断这个解析器支持的参数类型
	public boolean supportsParameter(MethodParameter parameter) { //传入参数的包装类
		Class<?> paramType = parameter.getParameterType();
		if (parameter.hasParameterAnnotation(RequestParam.class)) { //有@RequestParam注解的参数
			if (Map.class.isAssignableFrom(paramType)) {//等同于instansof操作的反向,并且都是class来比较
				String paramName = parameter.getParameterAnnotation(RequestParam.class).value();
				return StringUtils.hasText(paramName); //如果是个map类型必须注解带value值
			}else {return true; //有这个注解基本就判断支持}
		}
		else {
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			} //如果没有这个注解,但是是MultipartFile类型,也支持。
			else if (MultipartFile.class.equals(paramType) || "javax.servlet.http.Part".equals(paramType.getName())) {
				return true;
			} //构造传入true,并且是简单对象,很好理解,一个po对象,你给个名字给它,但是请求参数根据这个名字赋值没意义!
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(paramType);
			}
			else {return false;}
		}
	}
	@Override //找到了解析器,就执行参数解析的过程
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
		//.......
	}

1.4 上面的这个类我们可以看出,参数解析的过程都是先找然后再解析,1.3的过程中我们可以看的出,这些解析器都是按顺序进行加入到Composite组合器中,说明那个解析器先匹配就先执行,而上面展示的类排在第一个,优先级最高,而且我们都知道RequestParam注解是按名称进行的,按照一般的程序设计的先行原则,按名称肯定最优先,而没加这个注解的也有一个按名称进行赋值的,那就是MultipartFile。回忆下我们填写的文件上传参数名称是不是需要根据File的name值进行填写。补充(1.3的最后重复添加了一个这个解析器,this.userDefaultResolution是true,它可以最后默认给简单对象按名称来解析。)

二、对于上面的处理器适配器,初始化过程中是构建了一系列的参数解析器和返回值解析器,而具体参数解析器是怎么运行的,我们需要建立一demo实际跟踪一下。

   
    	/**
	 * 请求链接 /demo2/more?id=1&username=zhang&xx=xxx
	 */
	@RequestMapping("/more")
	public String moreParam(Model model,@RequestParam String xx,User user,HttpServletRequest request){
		System.out.println(user.getId());
		System.out.println(user.getUsername());
		System.out.println(xx);
		model.addAttribute("resut", "成功");
		return "index";
	}

2.1、一个普通的Handler方法,映射的链接是/demo2/more,它有几个参数,model,request,这种我们先不预期是那个解析器处理的,xx我们可以知道根据上面的分析,它是new RequestParamMethodArgumentResolver(getBeanFactory(), true)处理的,而User 根据new ServletModelAttributeMethodProcessor(false)解析处理的。我们先RequestMappingHandlerAdapter实际调用handler处理请求的方法handleInternal()-->调用invokeHandleMethod()看起。

protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) {
	if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
		checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
	}else {checkAndPrepare(request, response, true);} //此方法检查session,get/post是否支持,设置lastModified
	if (this.synchronizeOnSession) { //如果同步的方式操作session,那么就加锁
		HttpSession session = request.getSession(false);
		if (session != null) {
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {return invokeHandleMethod(request, response, handlerMethod);}
		}
	}
	return invokeHandleMethod(request, response, handlerMethod);//真正执行的方法
}

private ModelAndView invokeHandleMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod){
	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); 
	ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); //下面是创建方法的包装类,并加了解析器等等
	ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
       //.................  //还给requestMappingMethod加了视图容器和异步功能的分支,全部省略,节约篇幅。
	requestMappingMethod.invokeAndHandle(webRequest, mavContainer);//真正执行的方法!!
	if (asyncManager.isConcurrentHandlingStarted()) {
		return null;
	}
	return getModelAndView(mavContainer, modelFactory, webRequest);
}

2.2 请求方法最终是构造了一个ServletInvocableHandlerMethod调用invokeAndHandler执行的,创建过程中,对原始的handlerMethode包装成了一个新的子类,并且设置了参数解析器的组合加返回值处理器的组合。然后进行异步判断,不是异步就正常执行此新包装类的InvokeAndHandle()方法。所有的解析过程都发生在这个方法中。

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) {
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);//再点进去,得到方法的参数。
		Object returnValue = doInvoke(args); //最终处理方法执行
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);
			GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
			args[i] = resolveProvidedArgument(parameter, providedArgs); 
			if (args[i] != null) {
				continue;
			}
			if⑴ (this.argumentResolvers.supportsParameter(parameter)) {//如果解析器组合有一个支持参数
			    args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			    continue; //解析器组合会调用实际匹配的(按初始化顺序匹配的)解析器进行解析。
			}
			if (args[i] == null) { //最终没有找到解析器会报错。
				String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
				throw new IllegalStateException(msg);
			}
		}
		return args;
	}

2.3 从第三张图我们可以看到参数解析是遍历每一个参数,从解析器组合中查看有没有,如果有的话,内部会加入缓存map(前面说过),再进行解析。如果最终解析器没找到,就会报错。我们把从(1)号位置进入查看,demo中每个解析器是怎么匹配的。



     .........................第一个参数是Model,我们看下那个解析器支持它的。


    .........................遍历解析器组合,看那个最先匹配这个参数对象。


 ...........................找到第一个Model的,它的判断是根据是否Model类型。isAssignableFrom就是instanceof反向及用Class判断。


............................它的获取就是之前创建的ModelAndViewContainer。

2.4 上面debug了model的匹配解析器->解析器处理的流程。下面看第二个参数。@RequestParam String xx.


---------------->第二个参数,在遍历的第一次就找到了,是RequestParamMethod...可以匹配处理,而它的匹配过程上面已经说了,就是看有没有@RequestParam注解,下面只看它的执行方法.

    

2.5 具体每个解析器的怎么解析参数的,可以根据此流程多debug下查看。后面还有2个参数不一一演示了。

三、结尾

    从早上11点写到现在,还没吃饭,很多细节没有详解,因为太关注细节,很容易陷入源码分支的干扰。


猜你喜欢

转载自blog.csdn.net/shuixiou1/article/details/79676859
今日推荐