Spring MVC是目前主流的WEB MVC框架,许多公司都在使用Spring MVC或者间接的使用Spring MVC。之前一直都是哟Spring MVC但没有具体了解过其实现原理和它的源码实现。
在开始之前,我们先回顾下SpringMVC处理请求的流程:
DispatcherServlet
DispatcherServlet称之为整个SpringMVC最核心的类也不为过,它负责初始化SpringMVC的组件和进行请求的转发。一般在使用Spring MVC时,我们都需要在web.xml配置DispatcherServlet配置方式如下:
1 | <servlet> |
从上述代码中我们可以知道DispatcherServlet的类路径,我们使用Idea的类图查看工具,查看下DispatcherServlet都实现和继承了哪些接口和类?
从上图我们可以看到DispatcherServlet继承了HttpServlet类,HttpServlet中有三个可重写的方法(init()、doService()和destory()),而HttpServletBean重写了init()方法,并最终会调用到DispatchServlet的initStrategies(ApplicationContext context),而这个方法就是Spring MVC初始化组件的地方。代码如下:
1 | protected void initStrategies(ApplicationContext context) { |
HandleMapping:
HandleMapping是Spring MVC 非常主要的一个模块,Spring MVC通过处理器映射器找到其相关的处理器,并将其封装为处理器适配器,处理器适配器然后执行其handle方法。那么处理器映射器是怎样将request请求和处理方法对应起来,又是怎么找到的。接下来,我们主要看下initHandlerMappings(context)方法,该方法用来初始化处理器映射器。
1 | private void initHandlerMappings(ApplicationContext context) { |
上述方法就是HandleMapping的初始化,首先会判断DispatcherServlet类变量中的handleMappings是否为空,如果为空的话,则从容器中获取,如果容器不存在,则采用默认的处理器射器赋值。默认的处理器器映射器在DispatcherServlet.properties中配置,具体的配置如下:
1 | org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver |
知道了如何初始话处理器映射器,我们接着分析如何通过处理器映射器去寻找到处理器。分析到这里,我们继续回顾下之前说的DispatcherServlet方法是继承了HttpServlet方法,而HttpServlet方法存在三个方法,我们刚才只分析了init()方法来初始化各组件,我们并没有分析doService()方法,在doService方法中调用了一个doDispatch(request, response)方法,该方法就是主要处理请求到处理器以及封装为适配器处理业务逻辑的地方,源码如下:
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
我们通过看代码发现,mappedHandler = getHandler(processedRequest);是获取处理器的主要实现,那么getHandler是怎么实现的呢?
1 | @Nullable |
首先判断HandlerMappings是否为空,然后遍历HandlerMapping的实现类,通过实现类的getHandler(request)获取到处理器。我们在这里主要分析两个HandMapping的实现类,BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,这两个是默认的处理器映射器。
BeanNameUrlHandlerMapping
我们可以从上图看到BeanNameUrlHandlerMapping实现了ApplicationContextAwre接口,对于单例模式首次调用Bean时,ApplicationContext会查看是否实现了ApplicationContextAwre接口,若实现了则调用setApplicationContext()方法,在ApplicationObjectSupport中的setApplicationContext()方法调用了initApplicationContext(),在类AbstractDetectingUrlHandlerMapping重写了这个方法,具体的代码如下:
1 | /** |
在上述代码块中最重要的便是获取容器中的所有bean名称,然后通过遍历容器中的bean名称解析URL,然后调用registerHandler注册URL和beanName对应的Handler。我们继续分析registHandler方法
1 | protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { |
截止这一步,我们分析了Spring MVC的BeanNameUrlHandlerMapping的初始化逻辑,是如何将URL和Handler实例进行管理起来,我们做个小总结在这里:
首先会判断DispatcherServlet父类中HttpServlet中的init()方法,init()调用其在FrameWorkServlet的initServletBean方法用于初始化WebApplicationContext对象
初始化完WebApplicationContext对象之后,会调用实现类DispatcherServlet中的onRefresh()方法, onRefresh()方法用于初始化Spring MVC中的组件(文件上传解析组件、本地解析器组件、主题解析器组件、HandlerMapping的实现、HandlerAdapter的实现、异常处理解析组件、请求转换视图解析组件、视图解析器组件以及flash管理组件)。
其余组件组件就不说了,这里主要说initHandlerMaping方法用于初始化HandlerMapping实现类,如果在环境中未配置,则默认采用BeanNameUrlHandlerMapping和RequestMappingHandlerMapping实现类。
这一步应该是在在初始化WebApplicationContext实例时,BeanNameUrlHandlerMapping实现类ApplicationContextAwre方法,会在初始化Bean之后调用setApplicationContext方法,setApplicationContext方法调用类子类的detchHanlers方法,用于将配置文件中的URL和Handler关系注册到handlerMap对象中,供之后的getHandler调用。
这就是BeanNameUrlHandlerMapping的初始化的整个过程,之后我们会介绍获取Handler实例的源码。
RequestMappingHandlerMapping
在分析RequestMappingHandlerMapping之前,我们依照惯例,先看下它的实现图:
我们从上图可以看到RequestMappingHandlerMapping也继承了AbstractHandlerMapping的方法,并且AbstractHandlerMethodMapping类实现了InitializingBean接口,通过实现init()方法来注册HandlerMapping的相关信息,接下来我们就分析init()方法中调用到的initHandlerMethods()方法。
1 | protected void initHandlerMethods() { |
上述方法就是对RequestMappringHandlerMappting的初始化,其主要的操作包括获取容器中的Bean名称,遍历bean名称,然后获取Bean类型,通过isHandler方法判断是否在类上边含有@Controller或@RequestMapping注解,如果有则返回true,然后调用detectHandlerMethods方法获取其中方法,并做处理与注册。我们继续看detectHandlerMethods方法:
1 | protected void detectHandlerMethods(final Object handler) { |
上述方法看起来比较复杂,实际上做的处理是获取Bean对应的Class对象,然后获取用户目标类,然后在获取目标类中符合的方法吗,然后将其封装为一个Map<Method, T>的对象,T在这里实际是一个RequestMapptingInfo对象,将RequestMapping转换为一个Java使用的对象数据结构。最后再遍历结果集,调用registerHandlerMethod方法。我们继续看registerHandlerMethod方法:
1 | protected void registerHandlerMethod(Object handler, Method method, T mapping) { |
上述方法没有做过多的处理,而是调用了MappingRegistry的regist方法。
1 | public void register(T mapping, Object handler, Method method) { |
上述方法我们一眼看去,好像注册了许多东西到各自的map对象中,我们来一一讲解他们的作用,首先我们看到会将hanlder对象和method方法创建一个HanlerMethod对象,这个Handlermethod对象在之后一直发挥作用,将mapping信息作为key,HandlerMthod对象作为值来保存到map对象中。其二是解析mapping中的Url信息,将其作为urllookup的键,mapping作为主要信息保存在urlLookup的变量中。其三是保存name和handlerMethod的键值。其四跟踪代码发现corsConfig返回的是个null。最后是将name、mapping、url、hanlerMethod对象包装起来保存。通过上边我们已经知道RequestMappingHandlerMapping是怎样使用到的信息报错起来的。那保存的信息到底如何使用,我们继续回到DispatchServlet对象,去看doDispatch的getHandler方法,getHandler方法调用了getHandlerInternal,getHandlerInternal是一个抽象方法,查找实现方法,发现AbstractHandlerMethodMapping实现了getHandlerInternal方法,具体的代码如下:
1 | @Override |
lookupHandlerMethod方法:
1 | protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { |
上述代码是获取HandlerMethod对象的主要方法,首先是根据request请求的URL获取到相应的RequestMappingIOnfo对象,然后根据RequestMappingInfo对象获取到相应的HandlerMethod对象,这两个都是之前在将初始化RequestMappingHandlerMapping时放置到MappingRegistry对象中;之后再通过RequestMappingInfo中的比较条件获取到最适合的RequestMappingInfo和HandlerMethod的集合,然后返回其中的HandlerMethod对象。如果没有获取到相应的Match,则返回null。
RequestMappingHandlerMapping小结:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping很相似,都继承了ApplicationContextAwre类,但是其实现方式却不是在setApplicationContext方法中,而是在AbstractHandlerMethodMapping类实现了InitializingBean接口,其中调用了detachHandlerMathods方法,这个方法则是将类中拥有RequestMapping注解的或者Controller注解的方法封装成RequestMappingInfo对象,并获取该注解所在的方法,将其注入到MappingRegistry相应的变量中,供后续使用。而RequestMappingHandlerMapping的getHandler方法也一样通过调用getHandlerInternal方法获取,而具体的获取方法通过AbstractHandlerMethodMapping的lookupHandlerMethod方法实现, 主要是获取根据URI获取MappingRegistry中保存的RequestMappingInfo对象,然后根据RequestMapping对象获取HandlerMethod对象,将其封装为Match,然后通过RequestMappingInfo的比较条件获取到合适的HandlerMethod对象进行返回。
上述就是RequestMappingHandlerMapping的初始化和获取方法,当然写的比较简单,没有对许多具体的方法做相应的解释,但是大家通过看源码就能知道意思,所以也没有在这里做分析。
总结
HandlerMapping(处理器映射器)是了解DisptcherServlet的第一个模块,也是非常重要的模块,通过HandlerMapping我们可以获取到相应的处理器,然后交由HandlerAdapter封装为一个处理器适配器,然后处理器适配器调用handler方法进行业务端的处理返回ModelView对象,然后视图解析器对其做处理返回到前台页面,这次的源码分析就到此……。之后我会继续分析DispatcherServlet的下一个模块-HandlerAdapter,让我们期待……