SpringMVC是近年来出现的一个非常优秀的web框架,它是基于MVC思想设计的,采用松散耦合可插播组件结构,比其他的MVC框架更加灵活,更具可扩展性,此外SpringMVC在视图解析、数据绑定等方面等有着非常不错的表现,而已成为当今最受欢迎的MVC框架。
SpringMVC的核心是DispatcherServlet,它主要用于接收请求后,协调各个组件完成对请求的响应。和其他J2EE的MVC框架一样,SpringMVC也是利用前端Servlet接收请求,处理请求,最后返回相应结果的。而DispatcherServlet即是SpringMVC对应的前端Servlet。
图1.1 SpringMVC体系结构
SpringMVC在接收到请求后,主要是按照以下的流程处理并响应请求的。
(1)客户端发起Http请求,web应用服务器(如Tomcat)接收到请求后,如果请求格式匹配web.xml中配置的DispatcherServlet拦截请求格式(如*.html),那么web服务器将会把该请求转交给DispatcherServlet进行处理。
(2)DispatcherServlet接收到请求后,会根据请求的信息(如URL,请求方法等)从HandlerMapping中找到处理对应请求的处理器(Handler)。
(3)DispatcherServlet会将HandlerMapping返回的Handler交给HandlerAdapter进行包装。再统一由HandlerAdapter的适配器接口实现类通过接口实现调用Handler(具体HandlerAdapter如何实现利用适配器接口调用Handler后续会进行分析)。
(4)Handler处理完请求对应的业务逻辑后会返回ModleAndView给DispatcherServlet。其中ModleAndView包含Handler处理后返回的逻辑视图名以及数据对象。
(5)DispatcherServlet利用ModleAndView中的逻辑视图名通过ViewResolver解析到真正的视图对象View。
(6)DispatcherServlet获取到视图对象View后,会整合ModleAndView中的数据对象Modle进行视图渲染。
(7)渲染完成后返回客户端响应信息。
图1.2
既然DispatcherServlet归根到底是SpringMVC用于接收请求的Servlet,那就必须讲一下DispatcherServlet的类结构(如图1.2所示)。DispatcherServlet类结构主要是包含用于接收请求的Servlet部分以及用于监听容器事件的Listener部分。DispatcherServlet主要是通过继承FrameworkServlet实现对用户请求的处理以及为每一个请求的Servlet管理WebApplicationContext实例。
类结构的Servlet部分中,Servlet接口是Servlet类型的顶级接口,定义了所有子类都必须实现的方法,如Servlet生命周期中的核心方法init(ServletConfig config),service(ServletRequestreq, ServletResponse res)以及destroy()等,如图1.3所示。
图1.3
GenericServlet实现Servlet以及ServletConfig接口,定义了通用的,与协议无关的Servlet。GenericServlet方法定义如图1.4所示。
图1.4
GenericServlet 使编写Servlet 变得更容易,它提供生命周期方法init() 和destroy()的简单版本,以及ServletConfig()接口中的方法的简单版本。如果需要需要编写基于特定协议的Servlet,只需要拓展GenericServlet即可。从类结构中可以看到,HttpServlet是基于Http协议扩展了GenericServlet的抽象类。
ServletConfig接口主要是用于定义Servlet容器通过Servlet配置对象在Servlet实例初始化的时候传递配置信息。ServletConfig方法定义如图1.5所示:
图1.5
HttpServlet是基于Http协议的Servlet扩展,继承了GenericServlet。HttpServlet定义多个用于处理不同类型Http请求的方法。Http请求目前支持7中方法,分别为 GET 、 POST 、 PUT 、 DELETE 和以及TARCE、HEAD 、 OPTIONS 。其中前四个方法为请求数据相关的操作,后三个方法主要用于查询、测试服务器或者请求相关的数据,具体如图1.6所示:
图1.6
(1)GET---请求获取服务器由Request-URI所标识的资源;
(2)POST---在Request-URI所标识的资源后附加新的数据,可以理解为向服务器提交数据;
(3)PUT---请求服务器存储一个资源,并用Request-URI标识资源,可以理解为向服务器的某个目录放置资源;
(4)DELETE---删除由Request-URI所标识的资源;
(5)TARCE---请求服务器回送收到的请求信息,主要用语测试或诊断;
(6)HEAD---请求获取由Request-URI所标识的资源的响应消息报头;
(7)OPTIONS---请求查询服务器的性能,或查询与资源相关的选项和需求;
HttpServlet的核心方法service(HttpServletRequestreq, HttpServletResponse resp)用于处理请求,子类完全可以不从写该方法,因为该方法主要用于获取到请求方法类型后,调用对应类型的处理方法处理请求即可,具体如图1.7所示:
图1.7
HttpServletBean是对HttpServlet的简单扩展,主要是将web.xml中
图1.8
其中的PropertyValues主要是将web.xml中配置的Servlet中的初始化参数转换为对象,其格式实际上也是key-value的形式,具体转换方式如图1.9所示:
图1.9
其中的requiredProperties是HttpServletBean的属性,Set中存储的属性名称是当前Servlet实例化必需的。通过ServletConfig对象获取当前Servlet配置的初始化参数名称以及值。通过遍历所有的初始化属性将放置到PropertyValues接口的实现类实例的propertyValueList属性中,如图1.10所示:
图1.10
每次添加一个PropertyValue实例进入到propertyValueList之前,首先遍历propertyValueList,如果该属性在propertyValueList中已经存在,则合并属性,将合并后的PropertyValue实例放置到已经存在的PropertyValue实例的位置,并且返回。反之则直接添加到propertyValueList中。
将PropertyValue添加propertyValueList后,将该PropertyValue名称从requiredProperties移除,用于标识该属性有初始化值。遍历执行完成后,如果requiredProperties仍然不为空,则表示有初始化必须的属性没有进行配置,则抛出异常,Servlet初始化失败。
图1.8中PropertyValues实例化后包含注入的所有必需的属性,然后通过BeanWrapper实例封装了一个当前Servlet实例的行为,设置以及获取当前Servlet实例的属性值。其中registerCustomEditor()方法主要是注册用户自定义的属性编辑器,可以根据具体需要对Servlet的属性进行设置。子类可以通过从写initBeanWrapper()方法对BeanWrapper实例进行特定的实例化。
这样做的主要目的是将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;
另外initServletBean()方法提供给子类初始化的扩展,该方法FrameworkServlet覆盖。
FrameworkServlet是Spring框架中的web层Servlet的基类,提供集成的Spring容器ApplicationContext上下文。FrameworkServlet主要做两件事情,如图1.11所示:
(1)初始化WebApplicationcontext,为每一个请求管理对应的上下文实例;
(2)提供initFrameworkServlet()方法提供子类进行初始化扩展;
图1.11
其中initWebApplicationContext()用于初始化当前Servlet实例的WebApplicationContext实例。首先通过WebApplicationContextUtils到ServletContext中去获取根上线文,如图1.1.2所示:
图1.1.2
其中根上下文的在ServletContext中的属性名称是WebApplicationContext.class.getName()+ ".ROOT",那么根上下文具体是什么?又是在什么时候被设置到ServletContext中呢?还记得web.xml中的Spring容器初始化的配置吗?如图1.13所示:
图1.13
该段配置以后,Tomcat容器启动后会加载当前的配置,生成Spring的上下文,这就是根上下文,主要包含通用的Spring容器中的bean(如Dao,Service等),但是不包含web层的相关bean(如controller等),为什么要这样做呢?个人感觉是通过这种方式实例化容器中的bean后可以轻松的与其他的框架进行集成,比如Spring MVC,Struts2等,因为这些web框架在实例化各自的web层控制器时有各自的机制和方式,但是下层的bean的实例化方式却是一致的。
根上下文创建好后就会被放置到ServletContext中,即在ContextLoader中的initWebApplicationContext()方法中进行设置,如图1.14所示:
图1.14
那么为什么要把根上下文放置到ServletContext中呢?其实这是和ServletContext的定义以及SpringMVC框架中核心控制器DispatcherServlet处理请求的机制有关。
web容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。 ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写Servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。而在web.xml中配置的DispatcherServlet的配置内容如图1.15所示:
图1.15
DispatcherServlet初始化的上下文加载的Bean是只对Spring MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件。
但是Spring MVCweb等的相关组件bean在实例化的时候需要访问底层的其他层的bean实例(如service层的业务逻辑bean),这时候怎么办呢?这时候就需要DispatcherServlet中实例化的上下文继承根上下文,这样便可以直接访问到其他层次的bean,同时也实现了底层bean与web层框架的解耦。
DispatcherServlet继承FrameworkServlet,实现了onRefresh()方法,同时完成web层前端控制器相关资源的初始化工作。如图1.16所示:
图1.16
从initStrategies()方法我们可以看出DispatcherServlet实例化时会初始化web层相关的bean,如HandlerMapping,HandlerAdapter等,并且如果我们没有进行配置,DispatcherServlet会提供默认的配置。
从以上的Servlet的体系结构以及DispatcherServlet的实例化过程我们可以看出主要完成以下几个事情:
(1)通过配置Servlet实现SpringMVC核心控制器DispatcherServlet的初始化;
(2)通过ServletContext共享Spring根上下文,使得每一个Servlet实例获取根上下文中的bean,用于实例化SpringMVC web层的相关bean。
(3)初始化DispatcherServlet作为核心控制器,接收处理请求需要的相关资源,如HandlerMapping,HandlerAdapter等。
(4)通过Servlet体系结构中的继承关系以及抽象方法,可以根据具体的需求对各个层级的Servlet抽象方法进行重写以满足不同的功能需要,父类中只定义流程和方法引用,具体实现由子Servlet完成,实现定义与实现的分离,便于扩展。
下篇将介绍DispatcherServlet如何作为核心控制器实现请求的接收,处理请求。