首先,我们先回忆下日常开发中的骚操作,比如请求一个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点写到现在,还没吃饭,很多细节没有详解,因为太关注细节,很容易陷入源码分支的干扰。