本篇章为SpringMVC的详细介绍,还有一些之前学习时整理的笔记的汇总
大部分Java应用都是Web应用,展现层是Web应用不可忽略的重要环节。Spring为展现层提供了一个优秀的Web框架SpringMVC。和众多其他的Web框架一样,它基于MVC的设计理念。此外,它采用了松散耦合、可插拔的组件结构,比其他的MVC框架更具扩展性和灵活性。SpringMVC通过一套MVC注解,让POJO成为处理请求的控制器,无需实现任何借口。同时,SpringMVC还支持REST风格的URL请求:注解驱动及REST风格的SpringMVC是Spring的出色功能之一。此外SpringMVC在数据绑定、视图解析、本地化处理及静态资源处理上都有许多不俗的表现。它在框架设计、扩展性、灵活性等方面超越了Struts、WebWork等MVC框架。
SpringMVC体系概述
SpringMVC框架围绕DispatcherServlet这个核心展开,它负责接货请求并将其分派给响应的处理器处理。SpringMVC框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理及表单标签绑定等内容。
1 体系结构
SpringMVC是基于Model 2实现的技术框架,Model 2是经典的MVC模型在Web应用中的变体,这个改变主要源于HTTP协议的无状态性。Model 2的目的和MVC一样,也是利用处理器分离模型、视图和控制,达到不同技术层级间松散层耦合的效果,提高系统灵活性、复用性和可维护性、在大多数情况下,可以将Model 2与MVC等同起来。
在利用Model 2之前,把所有的展现逻辑和业务逻辑集中在一起,有时也称这种应用模式为Model 1.Model 1的主要缺点就是紧耦合,复用性差,维护成本高。
Spring框架模型:
从接收请求到返回响应,SpringMVC框架的众多组件同理配合、各司其职的完成分内工作。在整个框架中,SispatcherServlet处于核心位置,它负责鞋套和组织不同组件以完成请求处理并返回响应的工作。和大多数WebMVC框架一样,SpringMVCC通过一个前端Servlet接收所有的请求,并将具体工作委托给其他组件进行处理,DispatcherServlet就是SpringMVC的前端Servlet。
SpringMVC处理整体流程:
① 客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet。
② DispatcherServlet接受到这个请求之后将根据请求的信息(URL、http方法、报文、参数cookie等)以及HandlerMapping的配置找到处理请求的处理器(Handlerr)。
③ - ④ DispatcherServlet根据HandlerrMapping找到对应的Handler,将处理全交给Handler(Handler将具体的处 理进行封装),在由具体的HandlerAdapter对Handler进行具体的调用。
⑤ Handler对数据处理完成以后将返回一个ModelAndView对象给DispatcherServlet。
⑥ 由于Handler返回的ModelAndView只是一个逻辑视图并不是一个正式的视图,DispatcherServlet通过ViewResolver将逻辑视图转化为真正的视图View
⑦ Dispatcher通过model解析出ModelAndView中的参数进行解析最终展现出完整的view并返回给客户端。
2 配置DispatcherServlet
DispatcherServlet是SpringMVC的核心,它负责接收HTTP请求并协调SpringMVC的各个组件完成请求的处理工作。和Servlet一样,用于必须在web.xml中配置好DispatcherServlet。
要了解SpringMVC框架的工作机制,必须要弄清楚三个问题:
[1] DispatcherServlet框架如何截获特定的HTTP请求并交给SpringMVC框架处理。
[2] 位于Web层的Spring容器WebApplicationContext如何与位于业务层的Spring容器ApplicationContext简历关联,以使Web层的Bean可以调用业务层的Bean。
[3] 如何初始化SpringMVC的各个组件,并将它们装配到DispatcherServlet中。
① 配置DispatcherServlet,截获特定的URL请求
大家知道,我们可以在web.xml中配置一个Servlet,并通过<servlet-mapping>指定其处理的URL。这是传统的DispatcherServlet配置方式。而Spring4.0 已全面支持Servlet3.0 ,因此也可以采用变成式的配置方式。这里先采用传统的web.xml的方式进行讲解,然后介绍基于Servlet3.0的新方式。假设我们希望SpringMVC的DispatcherServlet能截获并处理所有以.html结束的URL请求,那么可以在web.xml中按如下方式进行配置
<!-- 业务层和持久层的Spring配置文件,这些配置文件被父Spring容器所使用 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 声明DispatcherServlet -->
<servlet>
<servlet-name>smart</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 名为DispatcherServlet匹配的URL模式 -->
<servlet-mapping>
<servlet-name>smart</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
在listener标签内通过contextConfigLocation参数制定业务层Spring容器的配置文件,多个配置文件可以使用逗号分隔。ContextLoaderListener是一个ServletContextListener,它通过contextConfigLocation参数所制定的Spring配置文件启动业务层的Spring容器。
在servlet标签内配置了名为smart的DispatcherServlet,它默认自动加载/WEB-INF/smart-servlet.xml的Spring配置文件(<servlet-name>-servlet.xml),启动web层的Spring容器。
在servlet-mapping标签内通过制定DispatcherServlet处理所有以.html为后缀的HTTP请求,即所有带.html后缀的HTTP请求都会被DispatcherServlet截获并处理。
多个Spring容器之间可设置为父子级的关系,以实现良好的解耦。在这里,Web层Spring容器将作为业务层Spring容器的子容器,即web层容器可以引用业务层容器的Bean,而业务层容器却访问不到web层容器的Bean。
一个web.xml中可以配置多个DispatcherServlet,通过其<servlet-mapping>配置,让每个DispatcherServlet处理不同的请求。
DispatcherServlet遵循契约优于配置的原则,在大多数情况下,用户无需进行额外的配置,只需遵守契约要求即可。
如果确实要对DispatcherServlet的默认规则进行调整,则可以通过servlet标签的<init-param>指定
[1] namespace:
DispatcherServlet对应的命名空间,默认为<servlet-name>-servlet,用于构造Spring配置文件的路径。在显示指定该属性后,配置文件对应的路径为WEB-INF/<namespace>.xml,而非WEB-INF/<servlet-name>-servlet.xml。如果将namespace设置为sample,则对应的Spring配置文件为WEB-INF/sample.xml。
[2] contextConfigLoaction:
如果DispatcherServlet上下文对应的Spring配置文件有多个,则可以使用该属性按照Spring资源路径的方式指定。如classpath:sample1.xml,classpath:sample2.xml,DispatcherServlet将使用类路径下的这两个配置文件初始化WeApplicationContext。
[3] publishContext:
布尔类型的属性,默认值为true。DispatcherServlet根据该属性决定是否将WebApplicationContext发布到ServletContext的属性列表中,以便调用者可借由ServletContext找到WebApplicationContext实例,对应的属性名为DispatcherServlet#getServletContextAttributeName()方法的返回值。
[4] publishEvents:
布尔类型的属性。当DispatcherServlet处理完一个请求后,是否需要像容器发布一个ServletRequestHandledEvent事件,默认值为true。如果容器中没有任何事件监听器,则可以将该属性设置为false,以便提高运行性能。
<!-- 显式指定web层的Spring配置文件 -->
<servlet>
<servlet-name>smart</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/webApplicationContext.xml</param-value>
</init-param>
</servlet>
之前提到过Spring4.0 已全面支持Servlet3.0 ,因此在Servlet3.0 环境中,也可以使用编程的方式来配置Servlet容器。
下面的代码可以达到在web.xml中配置DispatcherServlet一样的效果:
public class SmartApplicationInitializer implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext container){
ServletRegistration.Dynamic registration = container.addServlet("dispatcher",new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("*.html*");
}
}
接下来看看Servlet3.0 的实现原理。在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer的类,如果发现已有实现类,就会调用它来配置Servlet容器。在Spring中,org.springframework.web.SpringServletContainerInitializer类实现了该接口,同时这个类优惠查找实现org.springframework.web.WebApplicationIntitalizer接口的类,并将配置任务交给这些实现类去完成。另外 ,Spring提供了一个遍历的抽象类AbstractAnnotationConfigDispatcherServletInitializer来实现这个接口,使得它在注册DispatcherServlet时只需简单的指定它的Servlet映射即可。在上述示例中,当应用部署到Servlet3.0 容器中时,容器启动时会自动发现它,并使用它来配置Servlet上下文。
② 探究DispatcherServlet的内部逻辑
现在剩下的最后一个问题是:Spring如何将上下文中的SpringMVC组件装配到DispatcherServlet中,让我们一起查看DispatcherServlet的initStategies()方法的代码:
/**
* 初始化此servlet使用的策略对象.
* 可以在子类中重写,以便初始化进一步的策略对象.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);//初始化上传文件解析器-多部分请求解析器
initLocaleResolver(context);//初始化本地化解析器
initThemeResolver(context);//初始化主题解析器
initHandlerMappings(context);//初始化处理器映射器
initHandlerAdapters(context);//初始化处理器适配器
initHandlerExceptionResolvers(context);//初始化处理器异常解析器
initRequestToViewNameTranslator(context);//初始化请求到视图名翻译器
initViewResolvers(context);//初始化视图解析器
initFlashMapManager(context);//初始化闪存映射管理器
}
initStrategies方法将在WebApplicationContext初始化后自动执行,此时Spring上下文中的Bean已经初始化完毕。该方法的工作原理是:通过反射机制朝赵并装配Spring容器中用户显示自定义的组件Bean,如果找不到,则装配默认的组件实例。
SpringMVC定义了一套默认的组件实现类,也就是说,即使在Spring容器中没有显示定义组件Bean,DispatcherServlet也会装配好一套可用的默认组件。在spring-webmvc的jar包下org/springframework/web/servlet类路径下拥有一个DispatcherServlet.properties配置文件,该文件指定了DispatcherServlet所使用的默认组件。
如果用户希望采用非默认类型的组件,则只需在Spring配置文件中配置自定义的组件Bean即可。SpringMVC一旦发现上下文中有用户自定义的组件,就不会使用默认的组件,下图为DispatcherServlet装配每种组件的过程:
有些组件最多允许存在一个实例,如MultipartResolver、LocaleResolver等,图中使用中间为空的星星标识,而另一些组件允许存在多个实例图中使用黑色的星星标识。同一类型的组件如果存在多个,那么他们之间的优先级顺序如何确定呢?这些组件都实现了org.springframework.core.Ordered接口,可以通过order属性确定优先级顺序,值越小优先级越高。
当DispatcherServlet初始化后,就会自动扫描上下文的Bean,根据名称或类型匹配的机制查找自定义的组件,找不到的时候就使用DispatcherServlet.properties定义的默认组件。