Spring MVC 解读——@RequestMapping
来源:互联网 发布:中国电视台 知乎 编辑:程序博客网 时间:2024/05/23 16:11
Spring MVC 解读——@RequestMapping
为了降低文章篇幅,使得文章更目标化,简洁化,我们就不例举各种@RequestMapping的用法等内容了.
文章主要说明以下问题:
Spring怎样处理@RequestMapping(怎样将请求路径映射到控制器类或方法)
Spring怎样将请求分派给正确的控制器类或方法
Spring如何实现灵活的控制器方法的
在Spring MVC 3.1 之前的版本中,Spring默认使用 DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter来处理 @RequestMapping注解和请求方法调用,而从3.1开始提供了一组新的API完成这些工作。相比之下,新的API更加的合理完善,开放,易拓 展,面向对象。这篇文章便是基于3.1的新API进行剖析的。
一、概念解析
在开始之前我们先了解下新的API中引入的新接口或者类,这会有助于后面的处理过程的理解。不得不说新的API提供了更多漂亮的抽象,你能感受到面向对象的魅力。
RequestMappingInfo 这个类是对请求映射的一个抽象,它包含了请求路径,请求方法,请求头等信息。其实可以看做是@RequestMapping的一个对应类。
HandlerMethod 这个类封装了处理器实例(Controller Bean)和 处理方法实例(Method)以及方法参数数组(MethodParameter[])
MethodParameter 这个类从2.0就有了,它封装了方法某个参数的相关信息及行为,如该参数的索引,该参数所属方法实例或构造器实例,该参数的类型等。
HandlerMapping 该接口的实现类用来定义请求和处理器之前的映射关系,其中只定义了一个方法getHandler。
AbstractHandlerMethodMapping 这是HandlerMapping的一个基本实现类,该类定义了请求与HandlerMethod实例的映射关系。
RequestMappingInfoHandlerMapping 这个是AbstractHandlerMethodMapping的实现类,他维护了一个RequestMappingInfo和HandlerMethod的Map属性。
RequestMappingHandlerMapping 这个是RequestMappingInfoHandlerMapping的子类,它将@RequestMapping注解转化为RequestMappingInfo实例,并为父类使用。也就是我们处理@RequestMapping的终点。
InitializingBean 这个接口定义了其实现Bean在容器完成属性设置后可以执行自定义初始化操作,我们的AbstractHandlerMethodMapping便实现了这个接口,并且定义了一组自定义操作,就是用来检测处理我们的@RequestMapping注解。
概念讲的太多总不是什么好事。但明白了上述概念基本上就成功一半了,其中的实现相对@Autowired那篇简单多了。
二、InitialiZingBean.afterPropertySet()
我们从头开始,看看到底Spring是怎样检测并处理我们@RequestMapping注解的。不知大家还记不记的这段代码:
Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { exposedObject = initializeBean(beanName, exposedObject, mbd); } }
这是BeanFactory创建Bean过程中需要执行的一段代码,其中populateBean方法便是@Autowired注解的处理过程,执行的属性的自动注入等操作。因为initializeBean方法当时与主题无关没有讲,不过这时它便是我们关注的焦点了。(上一篇@Autowired 详解)
上面概念中我们讲到InitiaizingBean接口,它的实现Bean会在容器完成属性注入后执行一个自定义操作,这不就满足initializeBean方法的执行唤醒嘛,我们来看它的实现:
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { invokeAwareMethods(beanName, bean); return null; } }, getAccessControlContext()); } else {//这里检测当前Bean是否实现一些列Aware接口,并调用相关方法,我们不关心。 invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd);//这是我们需要关心的,下面看下它的实现 } if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
我们接着来看下invokeInitMethods方法的实现:
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable { //是否是InitializingBean的实例 boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { if (System.getSecurityManager() != null) { try { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { public Object run() throws Exception {//利用系统安全管理器调用 ((InitializingBean) bean).afterPropertiesSet(); return null; } }, getAccessControlContext()); } } else {//调用InitializingBean的afterPropertiesSet方法。 ((InitializingBean) bean).afterPropertiesSet(); } } //调用自定义初始化方法。。。省略,不关心 }
上一篇关于<mvc:annotation-driven/>的文章,我们说过了,当在配置文件中加上该标记后,Spring(3.1后)会默认为我们注册RequestMappingHandlerMapping
等Bean定义。而RequestMappingHandlerMapping
实现了InitializingBean接口,因此,在初始化并装配该Bean实例时,执行到上述代码是,便会执行他的afterPropertySet方法。我们接下来看看他的afterPropertySet方法:
public void afterPropertiesSet() { initHandlerMethods(); } //Scan beans in the ApplicationContext, detect and register handler methods. protected void initHandlerMethods() { //扫描所有注册的Bean String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); //遍历这些Bean,依次判断是否是处理器,并检测其HandlerMethod for (String beanName : beanNames) { if (isHandler(getApplicationContext().getType(beanName))){ detectHandlerMethods(beanName); } } //这个方法是个空实现,不管他 handlerMethodsInitialized(getHandlerMethods()); }
它直接调用了initHandlerMethods()方法,并且该方法被描述为:扫描ApplicationContext中的beans,检测并注册处理器方法。we are close。
三、检测@RequestMapping
我们再看它是怎样判断是否是处理器的,以及怎么detect Handler Methods 的:
@Override protected boolean isHandler(Class<?> beanType) { return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) || (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null)); }
啊哈,很简单,就是看看有没有被@Controller或者@RequestMapping注解标记
protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String) ? getApplicationContext().getType((String) handler) : handler.getClass(); final Class<?> userType = ClassUtils.getUserClass(handlerType); Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter(){ public boolean matches(Method method) {//只选择被@RequestMapping标记的方法 return getMappingForMethod(method, userType) != null; } }); for (Method method : methods) { //根据方法上的@RequestMapping来创建RequestMappingInfo实例。 T mapping = getMappingForMethod(method, userType); //注册请求映射 registerHandlerMethod(handler, method, mapping); } }
整个的检测过程大致清楚了:1)遍历Handler中的所有方法,找出其中被@RequestMapping注解标记的方法。2)然后遍历这些方法,生成RequestMappingInfo实例。3)将RequestMappingInfo实例以及处理器方法注册到缓存中。
下面我们看看细节:
@Override protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = null; //获取方法method上的@RequestMapping实例。 RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (methodAnnotation != null) {//方法被注解了 RequestCondition<?> methodCondition = getCustomMethodCondition(method);//始终返回null info = createRequestMappingInfo(methodAnnotation, methodCondition);//创建MappingInfo //检查方法所属的类有没有@RequestMapping注解 RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); if (typeAnnotation != null) {//有类层次的@RequestMapping注解 RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//null //将类层次的RequestMapping和方法级别的RequestMapping结合 info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); } } return info; }
很清晰吧,先获取方法上的@RequestMapping信息,然后获取类级别上的@RequestMapping 信息,然后将两者结合,这里我们有必要再了解下怎样创建RequestMappingInfo对象的(包括他的内部结构),以及怎样将类级别的request mapping信息和方法级别的进行结合的?
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) { return new RequestMappingInfo( new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions), new RequestMethodsRequestCondition(annotation.method()), new ParamsRequestCondition(annotation.params()), new HeadersRequestCondition(annotation.headers()), new ConsumesRequestCondition(annotation.consumes(), annotation.headers()), new ProducesRequestCondition(annotation.produces(), annotation.headers(), getContentNegotiationManager()), customCondition );}
其中涉及到了几个类,我们大致了解下含义:
PatternRequestCondition 它其实就是URL模式的封装,它包含了一个URL模式的Set集合。其实就是@RequestMapping注解中的value值得封装。
RequestMethodRequestCondition 它是@RequestMapping 注解中method属性的封装
ParamsRequestCondition 它是@RequestMapping注解中params属性的封装
等等,依次类推。因此RequestMappingInfo其实就是对@RquestMapping 的封装。
下面我们再看看怎样进行Combine的:
public RequestMappingInfo combine(RequestMappingInfo other) { PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());}
很清晰,对每一个元素都进行combine操作,我们这里只看PatternRequestCondition是怎么结合的,就是看看怎样合并url的。其他没太大必要。
public PatternsRequestCondition combine(PatternsRequestCondition other) { Set<String> result = new LinkedHashSet<String>(); if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) { for (String pattern1 : this.patterns) { for (String pattern2 : other.patterns) { result.add(this.pathMatcher.combine(pattern1, pattern2)); } } } else if (!this.patterns.isEmpty()) { result.addAll(this.patterns); } else if (!other.patterns.isEmpty()) { result.addAll(other.patterns); } else { result.add(""); } return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,this.useTrailingSlashMatch, this.fileExtensions); }
1)两个pattern都存在是,调用PathMatcher的combine方法合并两个pattern。
2)只有一个有时,使用这个。
3)两个都没有时,为空“”。
现在真正的url拼接是由PathMatcher来完成的了。我们就不看他的代码了就是一串if else的组合,重点是考虑进各种情况,我们来看下方法的注释吧:
清晰,全面吧,有兴趣的可以看一下代码,这里不讲了。
四、注册请求映射
上面我们已经讲了@RequestMapping的检测和处理,并且根据@RequestMapping生成了RequestMappingInfo实例,那Spring必定需要将这些信息保存起来,以处理我们的请求。
第三节中我们提到一个方法还没有分析,就是registerHandlerMethod 方法:
protected void registerHandlerMethod(Object handler, Method method, T mapping) { HandlerMethod handlerMethod; if (handler instanceof String) { String beanName = (String) handler; handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); } else { handlerMethod = new HandlerMethod(handler, method); } //上面几行是根据新的处理器实例,方法实例,RequestMappingInfo来生成新的HandlerMethod实例 //下面是从缓存中查看是否有存在的HandlerMethod实例,如果有并且不相等则抛出异常 HandlerMethod oldHandlerMethod = handlerMethods.get(mapping); if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) { throw new IllegalStateException(); } //handlerMethods 是一个Map键是RequestMappingInfo对象,值是HandlerMethod实例 //因此一个HandlerMethod实例可能处理多个mapping,而一个mapping实例只能由一个method处理 this.handlerMethods.put(mapping, handlerMethod); //这里获取mapping实例中的所有url。 Set<String> patterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { //urlMap也是Map,键是url 模式,值是RequestMappingInfo实例 //因此一个mapping实例可能对应多个pattern,但是一个pattern只能对应一个mapping实例 this.urlMap.add(pattern, mapping); } } }
这里可能稍微有点绕,其实道理很简单,当请求到达时,去urlMap中需找匹配的url,以及获取对应mapping实例,然后去handlerMethods中获取匹配HandlerMethod实例。
上一篇文章中我们了解了Spring如何处理@RequestMapping注解,并将请求映射信息保存到系统中以处理客户端发送来的请求,但是Spring是怎样接受请求,并根据请求URL来匹配正确的处理器方法呢,更重要的是Spring允许我们定义签名灵活的处理器方法,也就是说你的参数类型,顺序,返回类型都可以自定义,只要你方便就好。真的很棒,但是他怎样做到的呢? 这篇文章我们就研究这个问题。
一、重要的类和接口
HandlerMethodArgumentResolver 一个策略接口,根据请求解析处理方法的参数值(这是重点),我们就是通过实现它来自定义我们的参数类型的。Spring默认提供了十多个类型的解析器来处理常见的方法参数,如@PathVariabe,@RequestParam, Model,@ModelAttribute等等。
HandlerMethodReturnValueHandler 一个策略接口,用来处理处理器方法的返回值(重点啊), 我们通过实现它来自定义我们的返回类型
RequestMappingHandlerAdapter 一个HandlerAdapter的实现类,用来处理HandlerMethod,它包含了上面两个接口一系列实现类的列表,用于处理不同的参数和返回类型
HandlerMethodArgumentResolverComposite 这个类 维护了一个MethodParameter 与 HandlerMethodArgumentResolver的映射表,可以快速检索某一MethodParameter对应的Resolver,并调用Resolver进行参数解析。
HandlerMethodReturnValueHandlerComposite 这个类只维护了一个所有HandlerMethodReturnValueHandler的列表,每次遍历检索支持某返回类型的处理器。
ModelAndViewContainer 这个类记录了Model和View的对应关系,可快速检索某个视图对应的Model。
说明:当我们阅读@RequestMapping注解的说明时会了解到,Spring的处理器方法默认支持诸多参数类型和返回值类型,并提供了每一个参数类型,返回值类型的解析器和处理器,上述1,2两个接口的实现类,我们大致看一下他们的类层次结构:
以上是方法参数的可能类型以及他们的解析器,Spring默认支持还是挺棒的,可以应付绝大多数需求了。
以上是可能的返回值类型和他们的处理器,同样Spring的默认支持很强大,我们都可以实现自己的参数解析器和返回值处理器。
我们可以看到支持的类型非常多,由于篇幅限制,我们只讲解最常用的一个或几个,如@PathVariable,@RequestParam,Model等,如果有兴趣大家可以自行研究,思路都一样。
二、找到请求对应的处理方法
我们知道Spring会通过DispatcherServlet来处理所有的请求,那么我们就看他是怎么处理的
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); // 确定当前请求的处理器(HandlerExecutionChain 包含Handler和Interceptor列表) mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response);//404异常 return; } // 确定当前请求的处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //......省略诸多代码...... // 调用处理处理器方法,返回ModelAndView对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); }//处理返回结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } }
相信这个方法大家都很熟了,我删除了一些与当前主体无关的代码,下面我们看getHandler是怎么做的:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //这里有一个handlerMappings实例变量,如果你看过<mvc:annotation-driven/>那篇文章,相信你就知道这 //个handlerMappings都包括哪些HandlerMapping实例了? for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
<mvc:annotation-driven/>这篇博客 中说明了在启用该标签时Spring会默认注册RequestMappingHandlerMapping实例在处理@RequestMapping注解,而@RequestMapping (1) 这篇博客说明了该HandlerMapping是如何处理@RequestMapping注解,以及怎么保存请求映射关系的。下面我们就看RequestMappingHandlerMapping的getHandler方法:
//.....//AbstractHandlerMapping//.....public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request);//调用下面的方法 if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); }//返回该请求对应的HandlerExecutionChain(包括处理器方法和拦截器) return getHandlerExecutionChain(handler, request); }//.....//AbstractHandlerMethodMapping(RequestMappingHandlerMapping 的祖先类)//返回值是HandlerMethod//.....@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); ///查找请求路径对应的HandlerMethod实例 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //确保HandlerMethod中的handler是处理器实例而不是处理器名字 return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null; }
以上两个方法都是RequestMappingHandlerMapping的祖先类,逻辑很简单,先获取当前请求的路径,然后查找该路径对应的HandlerMethod实例。@RequestMapping (1)
这篇博客最后讲到了,RequestMappingHandlerMapping中的两个映射表实例,urlMap和handlerMethods,第一个是路径与RequestMappingInfo的映射,第二个是RequestMappingInfo和HandlerMethod的映射,不用说,lookupHandlerMethod方法肯定是检索这两个变量了:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request){ List<Match> matches = new ArrayList<Match>(); //查找urlMap,获取直接匹配的RequestMappingInfo列表。如 //URL 是/work/produce/2, @RequestMapping("/work/produce/2")直接匹配 List<T> directPathMatches = this.urlMap.get(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) {//如果没有找到直接匹配项,遍历所有的注册的RequestMappingInfo来查找 //遍历所有可能的RequestMappingInfo,找到完全匹配的RequestMappingInfo实例,并生成Match对象 //添加到Match列表中,Match是RequestMappingInfo和HandlerMethod的临时映射表。 //举个例子:请求URL可能是GET:/work/produce/2, //而@RequestMapping("/work/produce/{no}" "GET")此时需要匹配是否是GET请求,以及模式是否匹配 addMatchingMappings(this.handlerMethods.keySet(), matches, request); } if (!matches.isEmpty()) {//排序,找出最佳匹配 Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); Match bestMatch = matches.get(0); if (matches.size() > 1) {//如果可能的方法多余一个 Match secondBestMatch = matches.get(1);//并且两个方法的@RequestMapping内容相同 if (comparator.compare(bestMatch, secondBestMatch) == 0) {//抛出异常 Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path"); } } //这里是处理请求路径中的变量,如果/work/produce/{no}匹配的/work/produce/2中将no=2 //添加到Request的属性表中,以便后面@PathVarible参数的处理 handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } }
在进行URL匹配中,Spring会先查找是否存在直接匹配的RequestMappingInfo实例,即@RequestMapping中的value,method属性完全匹配请求的,如果没有找到通常是存在PathVariable的,如果上面讲的/{no}和/222的情况等也是匹配的, 找到匹配项后,需要找出最优解,然后将路径中的变量存入Request的变量表中,我们分别详细的了解下:
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { for (T mapping : mappings) {//遍历所有的RequestMappingInfo列表 T match = getMatchingMapping(mapping, request);//获取匹配的RequestMappingInfo实例 if (match != null) {//并生成匹配的RequestMappingInfo实例和对应HandlerMethod的Match实例 matches.add(new Match(match, handlerMethods.get(mapping))); } } }
继续看getMatchingMapping的实现:
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { //查看RequestMappingInfo的所有属性是否匹配 RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request); ParamsRequestCondition params = paramsCondition.getMatchingCondition(request); HeadersRequestCondition headers = headersCondition.getMatchingCondition(request); ConsumesRequestCondition consumes = consumesCondition.getMatchingCondition(request); ProducesRequestCondition produces = producesCondition.getMatchingCondition(request); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; }//我们重点看这个,路径是否匹配 PatternsRequestCondition patterns = patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } RequestConditionHolder custom = customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } return new RequestMappingInfo(patterns, methods, params, headers, produces, custom.getCondition()); }
我们知道RequestMappingInfo就是@RequestMapping注解的抽象,它包含@RequestMapping中的所有属性,因此在查找匹配项时,需要查看所有这些属性是否与请求匹配。我们这里只看路径模式是否匹配,其他属性自行研究,都很简单:
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) { if (this.patterns.isEmpty()) { return this; }//获取请求路径如/work/produce/2 String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); List<String> matches = new ArrayList<String>(); //遍历@RequestMapping中的所有模式 for (String pattern : patterns) { //找出与请求路径匹配的模式,如/work/produce/{no} String match = getMatchingPattern(pattern, lookupPath); if (match != null) { matches.add(match); } }//排序 Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath)); //返回匹配的请求模式实例 return matches.isEmpty() ? null : new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch); }private String getMatchingPattern(String pattern, String lookupPath) { if (pattern.equals(lookupPath)) { return pattern;//直接匹配 } if (this.useSuffixPatternMatch) {//是否使用后缀模式,/abc/de匹配/abc boolean hasSuffix = pattern.indexOf('.') != -1; if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) { return pattern + ".*"; } }//匹配/work/produce/{no} 和/work/produce/2 if (this.pathMatcher.match(pattern, lookupPath)) { return pattern; }//是否使用结尾的斜线匹配 boolean endsWithSlash = pattern.endsWith("/"); if (this.useTrailingSlashMatch) { if (!endsWithSlash && this.pathMatcher.match(pattern + "/", lookupPath)) { return pattern +"/"; } } return null; }
至于上面的pathMatcher.match方法这里就不分析了,可以自己看看,匹配算法还是比较复杂的,主要是尽可能的全面,除了进行匹配外,还会将路径中的变量保存起来以便@PathVariable参数使用。
以上便是整个的匹配过好麻烦,或许你会说这会不会降低Spring的性能?实话说,在处理首次请求时,效率是很差,但是Spring使用了各种缓存策略,一旦程序进入正轨,效率就非常高了。
三、处理器方法的调用
现在我们已经查找到了对应请求的处理器方法,下面我们就看Spring是如何在运行时动态地调用处理器方法的,并传递正确的参数。在doDispatch方法中,我们看到,确定了处理器(方法)后,Spring接着获取了该处理器方法的适配器(HandlerAdapter概念讲解中说到过,用来调用处理器方法的)
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { //这个handlerAdapters跟handlerMappings一样,启用<mvc:annoation-driven/>后默认注册 //RequestMappingHandlerAdapter(since3.1) for (HandlerAdapter ha : this.handlerAdapters) { if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: Does your handler implement a supported interface like Controller?"); }//是否支持当前处理器,其实就是看看处理器是不是HandlerMethod实例public final boolean supports(Object handler) { return handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler); }
获取了HandlerAdapter后,Spring就会调用handlerAdapter实例的handle方法,并返回ModelAndView实例:
@Override protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //如果HandlerMethod所属的处理器被@SessionAttribute注解标记了 if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { //设置响应头信息,防止缓存以便Session属性的管理 checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } else { //设置响应头,缓存默认时间 checkAndPrepare(request, response, true); } //要求在Session级别上进行同步,即同一个客户端的多个请求需要阻塞调用该处理器方法 if (this.synchronizeOnSession) { 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); }
我们接着看invokeHandlerMethod方法:
private ModelAndView invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //包装请求和响应对象 ServletWebRequest webRequest = new ServletWebRequest(request, response); //获取与HandlerMethod对应的DataBinderFactory。 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); //获取处理器方法所属处理器中被@ModelAttribute标记,但是没有被@RequestMapping标记的方法 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); //创建请求映射方法,并将HandlerAdapter中的参数解析器列表和返回值处理器列表传递给它。 ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); //创建ModelAndViewContainer ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); //获取并设置当前请求的异步调用链实例 AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request); chain.addDelegatingCallable(getAsyncCallable(mavContainer, modelFactory, webRequest)); chain.setAsyncWebRequest(createAsyncWebRequest(request, response)); chain.setTaskExecutor(this.taskExecutor); //调用该处理器方法。 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); if (chain.isAsyncStarted()) { return null; } //获取并返回ModelAndView return getModelAndView(mavContainer, modelFactory, webRequest); }
关于异步调用链那块我们暂不关心,后续文章会专题讨论。从上面代码可以看到,在调用方法前,分别检查了处理器中存在的@InitBinder注解的方法和@ModelAttribute注解的方法,InitBinder方法用于类型转化,如将String转化为Date类型等,可以通过@InitBinder方法实现,感兴趣可以自己看看,不在详细分析。至于@ModelAttribute注解的方法,其返回值会被放入Model对象中供视图使用。下面我们看invokeAndHandle方法:
public final void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //....省略异步调用方法,暂不考虑 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); //....省略几行代码,暂不考虑 try {//处理返回结果 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } }
接着我们看下invokeForRequest方法:----we are so close.
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取方法参数值。 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); //传递参数值,调用,返回返回值 Object returnValue = invoke(args); return returnValue; }
啊哈上面的代码貌似很简单,实则不是,重点就在getMethodArgumentValues方法,这才是我们这片文章的真正主题呢。打起精神来了:
private Object[] getMethodArgumentValues( NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取处理器方法的MethodParameter数组,就是方法的“参数定义”列表。 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; //遍历所有方法参数 for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(parameterNameDiscoverer); //确定泛型参数的类型 GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); //根据提供的参数值,解析当前参数的值 args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } //根据内置的参数解析器(上面的图片中列出了),来解析当前的参数值 if (argumentResolvers.supportsParameter(parameter)) { try { args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); continue; } } //如果参数值依旧为空,抛出异常。 if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; }
该方法是HandlerMethod中的方法,因此可以调用getMethodParameters()方法获取参数列表,然后遍历这些参数,分别用参数解析器来解析当前参数值,其中,argumentResolvers是HandlerMethodArgumentResolverComposite,概念讲解中已经阐述,它包含了所有的参数解析器的列表,以及参数类型和解析器的映射表,我们不妨看看到底什么怎么回事:
//是的,这个方法是HandlerAdapter中的方法,上一篇文章我们介绍了这是InitializingBean接口中的方法,//会被自动调用public void afterPropertiesSet() { if (this.argumentResolvers == null) { //调用下面的方法,获取所有某人参数解析器。 List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); //这个是HandlerAdapterComposite实例。 this.argumentResolvers = new HandlerMethodArgumentResolverComposite() .addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite() .addResolvers(resolvers); }//注册默认返回值处理器 if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite() .addHandlers(handlers); } } /** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ 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 ServletModelAttributeMethodProcessor(false)); 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()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); 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; }
这下清晰了吧,RequestMappingHandlerAdapter实现了InitializingBean接口,因此Spring启动是会调用它的afterPropertySet方法,进行上述参数解析器的注册。然后在处理器方法调用过程中会遍历这些解析器找到支持当前参数的解析器并解析参数。Perfect。我们再回到之前的resolveArgument方法:
public Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //调用下面的方法回去支持当前参数类型的解析器。 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); //调用该解析器的解析方法进行解析。 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } //前面说过了,HandlerAdapterComposite会维护一个MethodParameter到解析器的映射关系。没错吧 private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : argumentResolvers) { //判断解析器是否支持当前参数类型 if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
再往下就到某个解析器怎样解析具体参数了,我们知道就像上面图片中描述的,有大约一二十个解析器,我们不可能全部分析,这里我们只分析其中常见的一个:@PathVariable注解的解析器。
public boolean supportsParameter(MethodParameter parameter) { //如果该参数没有被@PathVariable注解标记,则返回false,不支持 if (!parameter.hasParameterAnnotation(PathVariable.class)) { return false; } //如果该参数是Map类型的。则判断@PathVariable是否设置了value属性 if (Map.class.isAssignableFrom(parameter.getParameterType())) { String paramName = parameter.getParameterAnnotation(PathVariable.class).value(); return StringUtils.hasText(paramName); } return true; }
从上面代码我们知道PathVariableMethodArgumentResolver支持被@PathVariable注解的参数。下面我们看它怎样解析参数值得:
public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //获取参数类型 Class<?> paramType = parameter.getParameterType(); //获取参数的名-值信息,如@PathVariable("NO") 则NO为名称,值为请求路径中对应 //@RequestMapping("/work/produce/NO")中NO的值。 NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); //解析请求路径中对应名称的值。上例中NO对应的值。 Object arg = resolveName(namedValueInfo.name, parameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required) { handleMissingValue(namedValueInfo.name, parameter); } arg = handleNullValue(namedValueInfo.name, arg, paramType); }//DataBinder,后续讲解。 if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); } //将上面解析到的名称和值放到Request的属性表中。 handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
上面代码首先获取参数的@PathVariable的value属性值,如果value是空,则将参数的名称作为NameValueInfo的name值,然后用这个name值匹配请求路径中的变量值,作为NameValueInfo的value值。
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){ Map<String, String> uriTemplateVars =//从请求的属性表中获取值 (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (uriTemplateVars != null) ? uriTemplateVars.get(name) : null; }
上面再lookupHandlerMethod方法中,调用了handleMatch方法,我们说了它会将解析到的路径变量放到request的变量表中,这不,这里就用到了,这样我们就获取到了@PathVariable对应参数的值了。到此为止,@PathVariable参数的解析就算完成了,其他类型的参数解析思路一样不同的就是resolveArgument方法中的逻辑了,大家可以自行了解。
注:这里Spring用到了多种设计模式,包括组合模式,策略模式,适配器模式等。其实这些实现都是3.1v的,之前的版本,参数解析这块相当乱,几乎完全在一个方法内实现的,拓展性,维护性相当差,3.1后我们可以很轻松的实现自己的参数解析器等,真的很棒。
真的很累了。。。。。。
四、返回值的处理
上一节我们分析了参数的解析,及方法的调用,下面我们再来看返回值的处理,回到HandlerMethod的invokeAndHandle方法来,该方法的最后调用了returnValueHandlers.handleReturenValue方法,其中returnValueHandlers是HandlerMethodReturnValueHandlerComposite实例,就像HandlermethodArgumentResolverComposite一样,它包含了所有HandlerMethodReturnValueHandler的列表,并在Spring启动时完成注册。
public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) { for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) { if (returnValueHandler.supportsReturnType(returnType)) { return returnValueHandler; } } return null;}//这个是处理String类型的返回值,即将返回值解析为视图名------ViewNameMethodReturnValueHandlerpublic void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof String) { String viewName = (String) returnValue; //将返回值存储为视图名 mavContainer.setViewName(viewName); if (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true); } } else { throw new UnsupportedOperationException("Unexpected return type: "); } }
返回值的处理思路与参数的处理几乎一样了,根据不同的返回值类型,查找匹配的处理器,然后进行处理(主要就是设置Model和View了,如上面的代码将返回值解析为视图名),这里就不多说了。返回值处理完了,剩下的就是将返回值响应给客户端了,再往下就是视图的解析了,也就是我们下篇文章的主题了。
- Spring MVC 解读——@RequestMapping
- Spring MVC 解读——@RequestMapping
- Spring MVC 解读——@RequestMapping
- Spring MVC —— @RequestMapping
- Spring MVC — @RequestMapping原理讲解-1
- Spring MVC — @RequestMapping原理讲解-2
- Spring 的MVC @RequestMapping
- Spring 的MVC @RequestMapping
- Spring Mvc @RequestMapping 详解
- Spring MVC @RequestMapping
- Spring MVC @RequestMapping使用方法
- Spring MVC RequestMapping
- Spring MVC 中的RequestMapping
- Spring MVC @RequestMapping
- spring-mvc @RequestMapping
- Spring MVC--4.@RequestMapping
- Spring MVC @RequestMapping注解
- Spring MVC 解读——@Autowired
- easyui中treegrid的数据格式
- hive实例-乘用车辆和商用车辆销售数据分析
- springboot实现热部署/自动编译
- 推出vim编辑器的命令
- 解决thunar 无权限挂载本地磁盘
- Spring MVC 解读——@RequestMapping
- iOS日常学习
- [九大内置对象]session、application、pageContext
- C++拷贝构造函数详解
- Guava学习笔记:Preconditions优雅的检验参数
- 一个表单小应用
- 入库数据中文乱码问题
- 【JavaSE笔记】Java常用类及其方法(六)_BigDecimal
- visual studio 无法打开WinSock2.h报错