springMVC学习笔记之源码分析

1.2核心类及源码分析

现在我们以上一节的入门案例为代表,分析其配置以及核心类的作用。

1.2.1 DispatcherServlet启动与初始化

了解springMVC的运行机制,我们需要先从DispatcherServlet入手,下面我们首先来回顾我们配置的DispatcherServlet,看一下我们配置的项其具体解释是什么?

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  
  <!-- 通过contextConfigLocation参数指定业务层
       Spring容器的配置文件(多个文件用','号分开);
  	    如果没有配置contextConfigLocation属性,
         默认自动加载spring-servlet.xml文件即:
       <servlet-Name>-servlet.xml -->
  <servlet>
  	<servlet-name>spring</servlet-name>
  	<servlet-class>
  		org.springframework.web.servlet.DispatcherServlet
  	</servlet-class>
  	<init-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>/WEB-INF/applicationContext.xml</param-value>  
    </init-param>  
  </servlet>
  <!-- servlet-Mapping指定的DispatcherServlet处理所有URL未.html为后缀的HTTP请求 -->
  <!-- 注意:一个web.xml可以配置多个DispatcherServlet 通过其<servlet-mapping>
  		的配置,让每一个DispatcherServlet处理不同的请求-->
  <servlet-mapping>
  	<servlet-name>spring</servlet-name>
  	<url-pattern>*.html</url-pattern>
  </servlet-mapping>
</web-app>
1.2.1.1上下文对象初始化

首先,我们要分析的是dispatcherIOC容器的初始化,即webapplication对象的初始化。这里我们需要从DispatcherServlet开始入手,打开DispatcherServlet的源代码,通过观察,我们发现了DispatcherServlet这个类依次的继承于:FrameworkServlet ,HttpServletBean,HttpServlet,既然其是继承的HttpServlet,那从本质上将,这个DispatcherServlet是一个HttpServlet,那自然它就拥有一套生命周期,调用该类时,会首先调用init方法,通过查看源代码,我们发现 DispatcherServlet并没有重写init方法,而是在HttpServletBean中重写了init方法,具体代码如下:

init方法中,初始化了一些类,从这些代码的字面意思中我们可以了解到其主要作用是加载一些属性值,这些属性值其实就是我们在xml配置的诸如init-param此类的信息。接下来调用了initServletBean方法,通过注释我们可以知道这个方法是调用的子类的,因为是Let subclasses do,而subclass的意思就是子类,英文不好的可以百度:

然后我们去其子类FrameworkServlet 中去观察这个方法,看看能得出什么信息,源代码如下:


在代码中,我们抓取核心的两句话:

在讲解initWebApplicationContext方法之前,我们需要先知道webApplicationContext是什么意思,通过观察我们发现这个WebApplicationContext是在类中定义的全局属性:


也就是说每一个dispatcherServlet都有一个WebApplicationContext(上下文)对象来存储自身需要用到的Bean对象而观察这个类,我们发现其继承了ApplicationContext




先看到这,我们知道了webApplicatioContextApplicationContext的子类,且存在于dispatcherServlet中,知道了这层关系后我们去关注下刚才我们提到的核心方法:initWebApplicationContext()方法,该方法的目的是初始化dispatcherServletwebApplicationContext对象,并设置一些参数,核心代码如下:


在方法中,首先去去find这个对象,如果该对象已经生成了自然会存放起来,目前我们的applicationContext还未生成,所以它会执行下面的代码,下面的代码中首先是获得了这个对象的父类对象parent而这个parent其实是已经生成了,如果在web.xml中配置了contextListener的话,这个parent会在监听器中创建并放在在servletContext中(如果没有配置会使用默认的XmlApplicationContext),所以这里只是去取一下,然后把这个parent传到下一个方法中,在createWebApplicationContext方法中才是真正的区创建这个对象的时候,核心代码如下:


通过观察我们发现,这个webApplicationContext对象其实是通过BeanUtils类的instantiateClass方法获取的,至于怎么获取的咱们暂时不讨论,如果没有特殊实现类,会默认采用XMLwebApplicationContext作为实现类。获取后,给这个对象设置parent属性,设置servletContext,设置config,设置namespace等,接下来是设置configLication,这里要注意,这里的configLocation就和我们之前在web.xml中配置的contextConfigLocation联系起来了。最后执行的方法是refresh方法,这个方法内容比较多,我们通过注释来看一下其执行的方法都做了什么:


我们看到referesh做了很多的工作,我们先不去讨论其细节,先缕一缕刚才分析过的所有方法逻辑,通过下面这个图可以帮助我们分析:


针对refresh中的这么多方法,我们今天不对每一个进行研究,我们只对这个方法进行研究


obtainFreshBeanFactory方法中,返回了一个beanFactory 这个bean工厂在接下来的参数中依次传递,可以说非常重要,那我们就来研究下这个方法主要做了什么,通过这个图我们能知道这个方法主要的调用逻辑:



进入obtainFreshBeanFactory方法,然后执行refreshBeanFactory方法,再去loadBeanDefinitions方法,loadBeanDefinitions方法中,其实就是去加载了配置文件的实体Bean等信息。讲到着,其实这块内容就讲完了,因为当配置文件加载完成后,我们的DispatcherServletIOC容器(webapplicationcontext)就初始化完成了。




1.2.1.2 MVC框架初始化

 

当上下文对象即DispatcherservletIOC容器初始化完成后,接下来就会初始化springMVC所依赖的一些解析器,适配器或者映射器,举例来说就如何视图解析器,主题解析器,国际化解析器等。而这个过程是在onRefresh方法中完成。



图中的onRefresh方法又做了什么?



initStrategies方法将在webApplicationContext初始化后自动执行,此时spring的上下文中的bean已经初始化完毕,该方法的工作就是通过反射机制查找并装配spring容器中用户显示自定义的组件Bean,如果找不到再装配默认的组件实例。

 

SpringMVC定义了一套默认的组件实现类,当spring容器没有显示的定义组件Bean时,dispatcherServlet也会装配一套可用的默认组件。可以在org/springframework/web/servlet包下查找到该文件:



这些解析器的作用在接下来的,对http做请求响应处理时会大有用户,例如其中的initHandlerMappings(context)方法,是对请求做处理的映射器的是初始化,在请求响应中还会来使用。

 

可能有的人已经看的不耐烦了,但尽管如此,我还是要提醒一下,以上代码我们只是分析了servletinit方法,即初始化方法,了解servlet生命周期的都知道当init方法执行完成后会调用service方法。

 

 

前两节,我们主要讲解的是dispatcherservlet作为一个servlet 我们在调用它时会执行其init方法,而在init初始化方法中,我们主要是初始化了dispatcherservletwebapplicationcontext,这个webapplicationcontext的父类是applicationcontextspring容器的上下文对象;接下来是对MVC框架的初始化,主要是对一些默认的例如视图解析器,主体解析器的初始化。现在假设上下文对象和MVC框架都初始化完成后,接下来我们就要对HTTP请求做接受和响应处理了。

1.2.2 MVC处理HTTP请求响应
1.2.2.1 HandlerMapping对请求的映射处理

前面我们已经分析了在DispatcherServlet初始化的第二步中-MVC框架初始化中已经初始化完成了所有的HandlerMapping,这些Mapping都存在在一个List中并排序,存储着HTTP请求对应的映射数据。这个List中的每一个元素都对应着一个具体的handMappng的配置,每一个handlermapping可以持有一系列才能从URL请求到controller的映射,而Spring mvc提供了一系列的HandlerMapping实现:


这么多的实现,我们不可能一一讲解完,这里我们先拿最后一个SimpleUrlHandlerMapping这个类作为案例来分析,之后我们还会做一个例子,会用到注解,那个时候我们再讲解DefaultAnnotationHandlerMapping.当然我们这一节主要是讲解原理性东西。

打开SimpleUrlHandlerMapping这个类的源代码:


我们发现在这个类中,定义了一个Map,从字面意思上我们理解认为它就是一个URLController的映射Map。在父接口HandlerMapping中,我们可以找到一个方法:



这个方法返回的是一个请求处理器叫做HandlerExecutionChain,打开这个方法的实现


我们发现这个getHandler的内部 主要是获取一个handler,而这个handler是什么呢,它就是我们访问的URL所映射的Controller。它和HandlerExecutionChain的关系是,它是HandlerExecutionChain的一个属性。从HandlerExecutionChain的源码中我们可以看到:



这个HandlerExecutionChain是比较简单的,主要包含两个重要属性,分别是handler,就是我们刚才说的HTTP请求的Controller,还有一个是interceptorList,拦截器链,拦截器链可以为handler增加一些功能,而这些拦截器链我们可以通过配置文件去增加,这里就不说了。



讲到这,我们再小结一下:

我们以SimpleUrlHandlerMapping为例子,说这个类中含有一个urlMap,它是urlcontroller的映射,当SimpleUrlHandlerMappinghttp做处理的时候,会从这个urlMap中取出对应的handler对象做处理,而这个handler对象就是HandlerExecutionChain对象,但这里有一个盲点就是HandlerExecutionChain何时生成的我们没有讲,现在我们来讲:

 

这个HandlerExecutionChain生成的时机其实是MVC框架初始化时生成的,我们将Spring为我们提供了很多的HandlerMapping实现类,而这些实现类都会在MVC框架初始化时被加载如下图:


图中的initHandlerMappings,如果在加载中加载到了我们现在讲的这个SimpleUrlHandlerMapping,那这个mapping会执行一个注册过程,在注册过程中完成对HandlerExecutionChain对象的构建,具体代码在SimpleUrlHandlerMapping类中的:


方法registerHandlers的代码如下:



一直跟着方法往下走,会跟到其父类AbstractUrlHandlerMapping方法:


此时,所有的准备工作就都完成了,根据handlerMap的配置,springMVC可以方便的由URL请求得到它对应的handler,有了这些准备工作,springMVC就开始静待HTTP请求的到来了。

1.2.2.1 HandlerMapping对请求的分发处理

上一节我们以SimpleUrlHandlerMapping作为研究对象向大家讲解了springMVC是如何加载和保存HTTP请求所指向的controller对象的,以及请求和控制器是通过何种方式进行映射的,都是一些准备性的工作,那现在当我们的请求以及映射控制器都准备好了后,我们就该思考下一个问题:请求是怎么实现分发而到达对应的handler的呢?所以本节我们讲的内容就是关于springMVC是如何对请求进行分发的。

 

我们之前讲过dispatcherservlethttpservlet的子类,所以根据servlet的生命周期,当执行完init方法后,dispatcherservlet会进入doService方法,用户处理get或者post请求。然后根据springMVC的使用,业务逻辑的调用入口是在handlerhandler函数中实现的,而这个doservice就是连接springmvc和应用逻辑实现的地方,让我们看一下这个doService方法的源代码:





在源代码中,我们可以看到springMVC对这个request请求做了一些处理,它获取了request的请求中的参数放置在了一个集合当中,还对request设置了很多参数,最后红色框内,调用了doDispatcher方法是是开始做分发请求的处理。这个方法很长,我们截取其核心的代码来给大家标注一下注释:


至此,我们就讲完了HandlerMapping对请求进行分发处理的过程,当处理完成后,会返回一个modelandview对象,这个对象封装了一些数据,springMVC会将此对象交给视图解析器进行解析,最终显示在页面中,而交给视图解析器的时机依旧是在doDispatcher方法中,只不过上图中没有截取到,依旧是doPatcher,在源代码的902行,我们可以看到如下代码:


关于这个render方法,我们在下一节会讲到。



1.2.3 MVC处理视图呈现

前面我们主要讲解了springMVC中的CController)控制器,还有少许的Mmodel)模型,其中M是指数据的传递者Modelandview对象。下面我们主要是讲解关于View视图的相关知识,上一节中我们讲到当m生成后会调用render方法将m传递给视图解析器,下面我们就来分析下render方法。

1.2.3.1视图呈现的设计模式

具体来讲,在dispatcherservlet中,对视图呈现的处理是在render方法中完成的,源代码如下:


从上述的图中我们可以看到呈现过程是这样的:在modelandview中寻找视图对象的逻辑名,进行解析得到实际需要使用的视图对象,或者直接从modelandview中取出已经存在的视图对象,调用视图对象的render方法来展示数据。

不同的视图烈性,往往对应的是不同视图对象的实现。SpringMVC支持多种视图,比如JPS/JSTL视图,Freemaker视图,velocity视图,excelpdf视图等,对于这些视图,springMVC通过与第三方框架的继承来实现。

对于视图的设计,我们通过其类关系图来了解一下其设计模式:


从类的关系图中,我们可以发现在view接口下,实现了一些列的具体的view对象,而写具体的view对象,又根据不同的特性归类到不同的抽象类当中,这些设计方式是有点类似于设计模式中的策略模式,通过策略模式,可以对不同的视图类实现方法进行归类,便于应用的使用和扩展。


1.2.3.2 常见视图的实现方式

我们讲SpringMVC为我们提供了很多的视图类,帮助我们呈现不同格式的数据。这里我们不侧重于讲解源代码,后面当我们做实例的时候,会结合这几种视图方式分别实现。

 

1、JSP视图

使用JSP的页面作为WEB UI 是使用java设计web应用比较常见的选择之一,如果在jsp中使用jstl来丰富jsp的功能,使用使用jstlView来作为view对象,从而对数据进行视图呈现,其代码实现我们不去看了,但其核心代码是讲所有的值都放置在hashmap中,然后转遍历放在request中,这样在jsp中就可以获取到request的值了。

 

2、ExcelView视图

除了能够使用JSP以后,springMVC还整合了其他常用的数据格式的页面,例如excel数据,对于excel,spring使用的是已存在的解决方案,把生成的excel输出到httpresponse中,在客户端展示,spring3.0提供了poijxl两个方案,他们的实现类分别是abstractExcelViewabstractJExcelView,具体的代码可以参阅一下。除此之外还有pdf的视图呈现,解决方案是iText.


1.2.4 小结

SpringMVC的启动和初始化可以分为两个步骤:

(1)上下文对象初始化,这个上下文对象时指dispatcherservletwebapplicationcontext对象的初始化,这个对象会存储关于这个servlet相关的bean信息,这些bean信息是从其关联的configlocation中获取的,webapplicationcontext的生成依赖于其父类applicationcontext对象,而applicationcontextspring启动时已经被加载生成保存在了servletcontext中,默认实现为xmlApplicationContext

(2)MVC框架初始化,这个过程主要是为MVC框架的执行准备必要的解析器和映射器,例如视图解析器,请求解析器等,这个过程中把必要的数据准备好,待执行doservice中就会拿来使用。这个过程中,我们讲解了handlerMapping对请求的映射处理以及分发处理,主要是通过map存储以url作为key,以controller作为value的方式。

 

 

 

SpringMVC的实现方式大致由以下几个步骤完成:

1、需要建立controller控制器和http请求之间的映射关系,即在mvc实现中是如何根据请求得到对应的controller的?通过分析我们知道了在springmvc中,这个工作是在handlerMapping中封装的handlerExecutionChain对象完成的,而对controller控制器http请求的映射关系配置是bean定义中描述的,并在IOC容器初始化时,通过初始化handlermapping来完成的,这些定义的映射关系会呗载入到一个handlerMap中使用。

2、在初始化过程中,contriller独享和http请求之间的映射关系建立好以后,为springMVC接受HTTP请求并完成相应处理做好了准备,在MVC框架接收到HTTP请求的时候,dispatcherservlet根据具体的url请求信息,在mapping中进行查询得到chain,而这个chain封装了controller,这个controller会完成请求的响应动作,返回modelandview对象,这个如何名字一样可以获得model数据是视图对象。

3、得到ModelAndView视图后,dispatcherservlet吧获得的模型数据交给特定的视图对象,从而完成这些数据的视图呈现工作,这个视图呈现由视图对象的render方法来完成,毫无疑问对于不同的视图对象,render方法会完成不同的视图呈现处理,为用户提供丰富的web UI表现。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值