java怎么拦截某个对象
262
2022-09-20
Spring MVC 解读——@RequestMapping (2)
Spring MVC 解读——@RequestMapping
上一篇文章中我们了解了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实例变量,如果你看过
//.....//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
在进行URL匹配中,Spring会先查找是否存在直接匹配的RequestMappingInfo实例,即@RequestMapping中的value,method属性完全匹配请求的,如果没有找到通常是存在PathVariable的,如果上面讲的/{no}和/222的情况等也是匹配的, 找到匹配项后,需要找出最优解,然后将路径中的变量存入Request的变量表中,我们分别详细的了解下:
private void addMatchingMappings(Collection
继续看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
至于上面的pathMatcher.match方法这里就不分析了,可以自己看看,匹配算法还是比较复杂的,主要是尽可能的全面,除了进行匹配外,还会将路径中的变量保存起来以便@PathVariable参数使用。
以上便是整个的匹配过好麻烦,或许你会说这会不会降低Spring的性能?实话说,在处理首次请求时,效率是很差,但是Spring使用了各种缓存策略,一旦程序进入正轨,效率就非常高了。
三、处理器方法的调用
现在我们已经查找到了对应请求的处理器方法,下面我们就看Spring是如何在运行时动态地调用处理器方法的,并传递正确的参数。在doDispatch方法中,我们看到,确定了处理器(方法)后,Spring接着获取了该处理器方法的适配器(HandlerAdapter概念讲解中说到过,用来调用处理器方法的)
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { //这个handlerAdapters跟handlerMappings一样,启用
获取了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
这下清晰了吧,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
上面再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了,如上面的代码将返回值解析为视图名),这里就不多说了。返回值处理完了,剩下的就是将返回值响应给客户端了,再往下就是视图的解析了,也就是我们下篇文章的主题了。
欢迎大家评论,交流。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~