SpringMvc分析-3

SpringMvc分析-3

本篇文章想要说说SpringMvc的一些常见的类和接口,以及他们的作用. 建议对照流程图来看
springMvc分析-1(总体概述)
SpringMvc分析-2(细节)

开头

Mvc是model,view,Controller的缩写。请求由Controller处理,数据变为Model,将Model用于View的渲染。在之前的Servlet的写法中,也有类似的写法,可以只写一个HttpServlet的实现类,在这个实现类里面做请求的分发,也可以配置很多的Servlet,在Web.xml中配置他能处理的请求路径。

利用一个中心的控制器(Controller)来处理请求的时候,通过请求的路径,找到对应的处理类来处理请求。本质就是这个样子。但是直接在方法里面写Html不太合适,也不好看。这才有了JSP。MVC的这种思想就出来了。

调用目标方法

SpringMvc中,DispatchServlet就是中央控制器,实现实现HttpServlet接口就好,但是现在有个问题,怎么找到处理该请求的方法呢?总的思路是作为配置,要不就是直接一个配置文件,写的简单一点的话,key直接就是请求的url,value是bean的名字。这里用的是注解。这里就需要一个类来总找处理类的事情,作为框架来说,得考虑到很多的情况,大多数的是按照请求路径来做匹配的,保不齐就有可能按照别的来做来做匹配。就得需要一个类来做总的处理(HandlerMapping),把它们分开。


要是按照请求路径做匹配的话,那得把注解的信息扫描出来,并且放到一个地方,好方便之后查找,在什么时候通过什么样的方法来查找呢?

在Spring中,主要Bean交给Spring管理,都可以通过ApplicationContext获取到,这个时候就可以通过注解来判断了。(具体的代码在AbstractHandlerMethodMapping#detectHandlerMethods)。

注解里面除了请求的路径之外,还可以支持的多点,比如需要什么参数,可以接受什么样子请求类型。这些都是一个注解的内部属性,注解应该也得封装为对象(RequestMappingInfo)。

拿到注解信息,封装为对象之后,得把这些信息注册到一个地方,方便查找(MappingRegistry),在匹配的时候,得按照@RequestMapping注解里面的哪些属性来做匹配,这里需要一个东西来做匹配(RequestCondition),具体看它的实现类AbstractRequestCondition,@RequestMapping注解里面不同的属性都对应一个RequestCondition。


到这里,已经结果了在Dispatch里面找到对应的处理类的问题了。如果没有找到要怎么办?没有找到就是404。

剩下就是调用目标方法获取model,渲染View就好了。但是别急,这里面也有的事情要做。

按照Spring的这种思想,在请求前和请求后怎么能不做点事情呢?Servlet中都有Filter。这里肯定也有(HandlerInterceptor),不过,这里的更加的详细,并且指定了一些规则更加的详细了,在处理请求前,调用完目标方法后试图渲染之前,视图渲染之后。三个。此外,还可以对HandlerInterceptor做拓展,可以增加对指定路径的拦截器(MappedInterceptor)。 这些操作都是在上面的HandlerMapping中做的。

它规定的这种调用模式就是这样的。拦截器,处理类,就得把这俩封装在一个对象里面(HandlerExecutionChain),不可能在Java里面一个方法返回了两个对象。之后调用的时候就利用HandlerExecutionChain来做了。

这个时候就需要调用了。因为前面是通过HandlerMapping将请求的操作分开了,只是返回了HandlerExecutionChain。不同的HandlerMapping有不同的处理逻辑,这里就需要一个类来适配这种行为(HandlerAdapter),在它里面真正的操作调用目标方法,通过它直接返回一个ModelAndView。之后就通过ModelAndVie来渲染视图了。

此外,还得考虑到异常的情况,如果调用HandlerAdapter的时候发生了异常怎么办?比如调用目标方法的时候,发生了RuntimeException,所以,这里很需要一个对于异常的处理操作.其实就是在调用HandlerAdapter的时候整体用try catch包裹起来,拿到异常对象。

继续说HandlerAdapter的处理逻辑。最简单直接的方式是直接将原始的HttpServletRequest和HttpServletResponse传递过去,从请求中取值交给对应的处理类来做,但是这样也太不友好了。所以这里就有对参数的处理了。还有对返回值的处理,这里肯定要有对返回值的处理的,不能在业务代码里面直接往HttpServletResponse中写数据吧,这里Model和View是分开的,

对参数的处理包括,参数的校验(Validator),参数的绑定,(DataBinder),不同参数的不同处理方式(HandlerMethodArgumentResolver),返回值的处理(HandlerMethodReturnValueHandler)。处理好参数之后方法的调用就可以直接通过反射来调用了,但是理论上来说,从HandlerAdapter角度来说的话,它只是调用一下目标方法,至于这些参数绑定,返回值的处理,它一概不关心,从它的角度来说,调用的操作就是一个整体。所以,这里需要将整个调用的操作封装为一个对象(HandlerMethod,ServletInvocableHandlerMethodInvocableHandlerMethod)。之后的参数校验,绑定,参数的处理,返回值的处理,都是在它里面来做的。DataBinder只是用来绑定数据,校验数据的,它是在HandlerMethodArgumentResolver里面使用的。HandlerMethodArgumentResolver是对不同参数的不同的处理方式,这里还需要一个类专门来做参数名字的提取操作(ParameterNameDiscoverer)。还有一个点,这些参数,方法的返回值,都是各种各样的。为了便于之后的操作,比如获取返回值的类型信息等等,也得把他们封装为一个对象,便于之后的操作(MethodParameter)。

DataBinder和Validator的关系是依赖,DataBinder依赖于Validator。在绑定参数的时候需要校验参数是否合法,那么在创建DataBinder的时候就得需要一个工厂类类做这件事情,不能在别的类里面干这个事情(WebDataBinderFactory),还是Spring的这种思想,这种参数绑定,验证的这活,肯定是有可能需要定制化的,所以,得来一个initializer。在创建DataBinder的时候做自定义的初始化 InitBinderDataBinderFactory,其实就是@Initbinder注解,同样的,在程序启动之后,就需要扫描出来,它没放在一个Registry里面,而是直接放在InitBinderDataBinderFactory#binderMethods属性里面。


到这里目标方法的调用已经ok了,是通过HandlerMethod来做的,在调用目标方法之前还得干点什么事情。比如上面说Model存放在哪里?View存在在哪里?这里就需要一个上下文对象,里面包含了在下面用得到基本的信息。(ModelAndViewContainer)。此外,还有一种情况,就是万一,目标方法是不需要view的呢?这里就得需要一个标志位,表示不需要view,或者当前请求已经处理model部分已经处理完成(ModelAndViewContainer#requestHandled)字段。

在调用目标方法之前,按照Spring的这种思想,不得来一个前置+后置的这种增强操作?。所以就有了(@ModelAttribute)。和上面发现@Controller一样,这里也需要在程序跑起来之后,将所有的符合条件的方法收集起来,但是这里没有像上面那样规范,只是在RequestMappingHandlerAdapter#modelAttributeAdviceCache和modelAttributeCache属性,只是一个大Map而已。在调用目标方法之前需要调用这些方法前一步操作Model,在操作完之后还需要更新Model,或者更新Session(SessionAttributesHandler)。整个操作得放在一个对象里面来做为了方便好看,(ModelFactory)。

到这,HandlerAdapter调用目标方法,返回ModelAndView就搞定了。下一步就是渲染View了。


View渲染

可能有一种情况,方法的返回值是不需要View的,这个时候就不需要渲染了。model返回为null,表示不需要view,view就不处理。

View这是一个抽象的概念,View的实现类有很多种,Http content-type 规定了就很很多种,所以,View在实际的使用中就很有很多的实现类,View是自己渲染的,但是得有一个类来根据ModelAndView来创建一个合适的View吧(ViewResolver)。比如Springboot中的ThymeleafViewResolver就是用来创建ThymeleafView的。创建View的时候就可以把View需要的东西全部设置进去,之后的视图渲染View自己就可以实现。

异常的处理

上面说了异常的处理,在调用方法的时候,用try catch块包裹起来。异常也是有专门的处理类的(HandlerExceptionResolver),通过它返回一个ModelAndView,就能和上面说的View的渲染衔接上了。如果有异常,就走异常的处理,返回一个ModelAndView,没有异常,就没有捕获到,对用的Exception对象为null,直接走正常的逻辑。

这里就有一个问题,如果异常了会怎么处理?肯定是要留给业务代码来处理,不能框架自己内部处理,所以这里也需要一个操作,和之前的@InitBinder,@ModelAttribute,@Controller类似的逻辑,需要发现对应的处理方法,那肯定也是在程序启动之后,扫描全部的Bean,获取对应的处理方法, 放在一个集中的地方,便于之后的操作。注意,这里也没有Registry,也只是一个大map(ExceptionHandlerExceptionResolver#exceptionHandlerCache和exceptionHandlerAdviceCache)。还有一个问题,不能所有的异常都给同一个方法类处理,我的意思是,异常也得分类,比如,指定这类的异常让这个方法处理,所以,这里就得有一个对异常种类的处理操作(其实就是匹配异常了)对应的代码在ExceptionHandlerMethodResolver#resolveMethodByExceptionType,如果有一个异常有多个匹配项目的话,还得处理一个最匹配的问题(ExceptionDepthComparator#compare)。

找到对应的处理方法之后,就可以去调用目标方法了,上面说了调用目标方法需要封装为HandlerMethod,这就又走了一遍上面的逻辑,最后返回了ModelAndView。交给上面的视图渲染。


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

Supongo que te gusta

Origin juejin.im/post/7066647265846394893
Recomendado
Clasificación