目录
1.2 load-on-startup(Servlet加载配置)
2.1 简单的URL处理器映射器:SimpleUrlHandlerMapping
2.2 组件名URL处理器映射器:BeanNameUrlHandlerMapping
2.3 请求映射器处理器映射:RequestMappingHandlerMapping
3.1 简单的控制器处理器适配器:SimpleControllerHandlerAdapter
3.2 HTTP请求处理器适配器:HttpRequestHandlerAdapter
3.3 Servlet处理器适配器:SimpleServletHandlerAdapter
3.4 请求映射处理器适配器:RequestMappingHandlerAdapter
一、HTML与HTTP
HTML(超文本标记语言)是一种标记语言,使用<html>、<head>、<body>等标签开发网页。该类型文档使用.html为后缀名,使用浏览器打开后可以直接查看页面效果。基于B/S模式的开发和访问中浏览器和服务器的通信需要遵循一定的规则,这个规则就是HTTP协议。HTTP协议是位于应用层的协议,其底层是基于TCP通信。在此协议下,浏览器端发出一个遵循协议的请求,服务器返回浏览器可以识别的响应。在浏览器端,通过URL(统一资源标识符)来建立连接和获取数据。浏览器发出的请求不仅是一个地址,还有请求头和请求体等部分。
HTTP请求具体如下:
- 请求行:包括请求类型(POST和GET等)、请求的资源地址和使用的HTTP协议版本等信息。
- 请求头:包括客户端主机IP和端口(HOST)、浏览器信息(User-Agent)、客户端能接收的数据类型(Accept)、客户端字符编码集(Accept-charset)和Cookie等内容。这些信息有的由浏览器来定义,并且在每个请求中自动发送,类似于HOST和User-Agent等;有的可以通过代码进行处理,类似于Accept和Cookie等。
- 请求体:一般是请求的参数。
HTTP响应消息格式也包含如下部分:
- 状态行:主要包括响应成功或失败的状态。
- 响应头:返回响应消息报头,包括服务器响应的内容类型和字符类型(Content-Type)、响应时间(date)、服务器类型(server)和响应内容的长度(Content-Length)等。
- 响应正文:响应给客户端的文本信息。
MIME类型
MIME类型的定义由两部分组成,使用"/"分隔,前面是大类,后面是具体的类型。MIME主要的大类有文本(Text)、程序(Application)、图片类(Image)、声音(Audio)、视频(Viedo)等。在不同的大类下再细分为不同的具体类型,常见类型如下:
- text/html:HTML文档格式;
- text/plain:纯文本格式;
- image/jpeg:JPEG格式的图片;
- image/gif:GIF格式的图片;
- application/xml:XML数据格式;
- application/json:JSON数据格式;
- application/msword:Word文档格式;
- application/octet-stream:二进制流数据(如常见的文件下载);
- application/x-w-w-w-form-urlencoded:Form表单数据,被编码为key/value格式发送到服务器上(表单默认的提交数据格式)
- multipart/form-data:文件上传的媒体格式类型,数据被编码为一条消息;
在HTTP协议中,请求头的Content-Type属性除了指定MIME类型外,还可以指定字符编码,类似于text/html;charset=utf-8。在页面的Form表单中,使用enctype属性指定MIME类型,默认值是application/x-w-w-w-form-urlencoded,如果需要发送大量的二进制数据,可以使用multipart/form-data,也就是文件上传的方式。
二、MVC模式
MVC是一种框架模式,MVC分别对应Model、View、Controller的首字母,是将数据模型、视图展现和业务逻辑进行分离的方式来组织代码,以此来提高代码的结构性、可重用性、可读性和可维护性。
- Model:模型,要展示的数据,包括数据及其行为。
- View:视图,复杂模型的展示,也就是页面。
- Controller:控制器,负责应用流程控制,用于接收用户请求,然后委托模型进行处理,再将处理结果交给视图展示。
常规的Java Web开发模式,就是使用JSP+Servlet+JavaBean的技术来实现MVC架构。
- JavaBean作为模型分为两种,一种是数据模型封装业务数据,另一种是业务逻辑模型处理业务操作。
- JSP作为视图层,提供页面展示数据。
- Servlet作为控制器,接收用户请求,实现转换业务模型及调用业务模型的相关方法,执行完成后将执行结果返回给视图。
三、Spring MVC框架处理流程
Spring使用Servlet技术提供了一个中央控制器DispatcherServlet,用于统一接收客户端请求,DispatcherServlet根据配置的映射规则将请求分派到不同业务处理的控制器中,各业务控制器调用相应的业务逻辑模型处理后返回模型数据,中央控制器将模型数据交由视图解析器获得最终的视图进行请求的响应。标准的请求和处理流程如下图:
四、Spring Web快速Demo
创建Maven Web工程,引入springmvc依赖。创建Spring配置文件,这里起名叫springmvc.xml。
Model层定义一个User类,如下:
public class User {
private int id;
private String name;
//……省略getter和setter
}
Controller层自定义后端控制器,如下:
public class MvcHelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 初始化模型视图对象
ModelAndView modelAndView = new ModelAndView();
// 设置视图名(这个名称对应要展示的页面:hello.jsp)
modelAndView.setViewName("hello");
User user = new User(1, "User 1");
// 这个"user"对应到hello.jsp中的user
modelAndView.addObject("myUser", user);
return modelAndView;
}
}
如上handleRequest()方法有两个参数HttpServletRequest和HttpServletResponse,可以用于接收请求、参数和处理返回,该方法返回一个ModelAndView类型的对象。ModelAndView可以设置页面和添加返回的模型对象,在MedolAndView中添加的模型对象可以在相应的vie中获取。
Spring配置文件中配置控制器Bean、请求处理映射和视图处理映射
<!-- 控制器Bean -->
<bean id="mvcHelloController" class="com.mec.springmvc.controller.MvcHelloController"></bean>
<!-- 处理器映射器 -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<!-- 路径映射 -->
<property name="mappings">
<props>
<prop key="/mvcHello">mvcHelloController</prop>
</props>
</property>
</bean>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/"></property>
<!-- 视图后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
上面处理器映射器的配置,使用mappings指定路径映射,当访问http://localhost:9090/SpringMVC/mvcHello这个url那么就会映射到mvcHelloController这个控制器;视图解析器的配置,prefix和suffix一起用于匹配视图,定位到WEB-INF/hello.jsp
视图页面WEB-INF/hello.jsp如下
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>User View</title>
</head>
<body>
Hello: <br />
用户ID:${myUser.id} <br />
用户名:${myUser.name} <br />
</body>
</html>
web.xml中配置如下:
<!-- 配置中央控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet最好是在容器启动时一并启动,也就是Servlet的<load-on-startup>值设置为1,这样Spring配置的Bean可以在容器启动时一并实例化。contextConfigLocation的参数用于指定Spring的配置文件,参数值是SpringMVC的配置XML文件路径。
五、SpringMVC核心组件
1.DispatcherServlet(中央控制器)
DispatcherServlet负责请求的分发,所以常被称为请求分发器,它的本质是一个Servlet,间接从HttpServlet继承,和一般Servlet一样,需要配置在web.xml中,用于拦截匹配的路径请求,并将拦截的请求按照规则分发到不同的业务控制器中进行处理。中央控制器可以配置多个,但大部分应用配置一个就够了。
1.1 Spring配置文件读取
默认情况下,DispatcherServlet会根据配置的servlet-name到项目的WEB-INF目录下查找名为[servlet-name]-servlet.xml的Spring配置文件。也可以通过configLoaction参数指定Spring配置文件的路径和文件名。如果只指定文件名,则默认从WEB-INF路径查找配置文件。配置文件如果放置在类文件路径下,可以使用classpath:的方式,如classpath:springmvc.xml,包之间使用斜线“/”进行分隔。多个配置文件之间使用逗号“,”分隔。(classpath:等同于/WEB-INF/classes)
1.2 load-on-startup(Servlet加载配置)
load-on-startup用于标记是否在容器启动时就实例化和加载这个Servlet。其配置的值是一个整数。
- 当其值<0或没有配置时,表示该Servlet被请求时容器才会加载它。
- 当其值=0或>0时,表示容器启动时就会加载。值的大小表示Servlet被载入的顺序,值越小,优先级越高,越容易被先加载。
DispatcherServlet在容器启动时加载,可以避免第一个请求需要较长时间来响应的问题,这也是推荐的配置。
1.3 servlet-mapping(路径拦截匹配)
Servlet容器在接收到浏览器发起的一个URL请求后,会把应用上下文去除后,以剩余的字符串来映射相应的Servlet。例如,URL请求地址是http://localhost:9090/SpringMVC/index.html,其应用上下文是SpringMVC(也是项目名),容器会将http://localhost:9090/SpringMVC去掉,用/index.html部分来做Servlet的映射匹配。
当一个URL与多个Servlet的匹配规则可以匹配时,按照“精确路径---最长路径---扩展名”的优先级匹配Servlet。
- 例1:ServletA的url-pattern为/test,ServletB的url-pattern为/*,如果访问的URL是http:localhost/test,这个时候就会先进行精确匹配,/test被ServletA精确匹配。
- 例2:ServletA的url-pattern为/test/*,而ServletB的url-pattern为/test/a/*,此时访问http:localhost/test/a时,容器会选择路径最长的Servlet来匹配,也就是ServletB。
- 例3:ServletA是扩展名匹配*.do,ServletB配置的是路径匹配/*,此时访问的URL如果是http://localhost/test.do,容器会优先进行路径匹配,调用ServletB。
每个<url-pattern>元素代表一个匹配规则,从Servlet2.5开始,同一个Servlet可以使用多个url-pattern规则,需要注意的是路径匹配和扩展名匹配不能同时设置。
因为DispatcherServlet负责所有请求的分发,一般是配置一个<url-pattern>用于拦截所有的请求进行转发,常见的配置方式如下:
- 传统方式:*.do或者*.action。使用一个特别的扩展名映射需要转发或者处理的请求,对于其他请求则不处理。好处是不会导致静态文件(js、css、png等)被拦截。
- 流行方式:/(REST风格)。将url-pattern设置为/,只要是在web.xml文件中找不到匹配的URL,他们的访问请求都将交给DispatcherServlet处理。这种配置会覆盖Tomcat默认的拦截请求,也就是也会拦截*.js、*.png等静态文件,不过可以通过web.xml或Spring的配置放行这些资源。
- 错误方式:/* 。“/*”会匹配所有请求,会覆盖所有的扩展名匹配,会对所有请求都进行拦截,包括jsp、png、css等。在DispatcherServlet中应避免使用这种方式。
2.HandlerMapping(处理器映射器)
2.1 简单的URL处理器映射器:SimpleUrlHandlerMapping
简单的URL处理器映射器配置URL和Controller处理器的对应,通过配置SimpleUrlHandlerMapping类的<bean>来实现,使用属性mappings进行请求的URL路径和处理器的显式映射,配置示例如上。
2.2 组件名URL处理器映射器:BeanNameUrlHandlerMapping
组件名URL处理器映射器使用Bean的name属性来配置URL映射的地址(/开头),Spring使用BeanNameUrlHandlerMapping处理这种类型的映射,Bean配置示例如下:
<bean id="mvcHelloController" name="/mvcHello" class="com.mec.springmvc.controller.MvcHelloController"></bean>
BeanNameUrlHandlerMapping是默认的映射器处理器,Spring会默认创建一个BeanNameUrlHandlerMapping的实例,所以不需要显式配置它的Bean,不过如果配置了也不会出错。
2.3 请求映射器处理器映射:RequestMappingHandlerMapping
前面两种都是类层级的映射,实际开发中更常使用的是方法层级的注解映射。也就是在@Controller注解类的方法上使用@RequestMapping、@GetMapping和@PostMapping等注解进行请求映射。
Spring3.1之后使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter进行注解类型的处理器映射和注解方法的处理器适配。这两者在使用<mvc:annotation-driven />配置元素后会自动初始化和注册;当然如果不用这种方式还可以配置他们的Bean达到同样的效果,如下:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
3.HandlerAdapter (处理器适配器)
在Spring MVC中,一个HTTP请求会由映射到某个类中的方法来处理,不直接调用处理器而通过HandlerAdapter进行调用,可以为处理器统一提供参数解析、返回值处理等适配工作。从请求被接收到HandlerAdapter进行处理器调用的具体细节如下:
- DispatcherServlet,接收到请求调用doDispatch()方法进行处理。
- doDispatch()方法中,通过HandlerMapping得到HandlerExecutionChain(HandlerExecutionChain包含处理请求的处理器(或处理器链),处理器可以是一个方法或一个Controller的对象)
- 根据不同的Handle类型得到不同的HandlerAdapter。
- HandlerAdapter的handle()方法使用反射机制调用handler对象,除此之外HandlerAdapter还负责一些类型转换工作。
对应不同类型的处理器,Spring内置了对应的处理器适配器,主要包括4类:
- 继承Controller接口的处理器适配器SimpleControllerHandlerAdapter
- 继承HttpRequestHandler接口的处理器适配器HttpRequestHandlerAdapter
- 继承Servlett接口的处理器适配器SimpleServletHandlerAdapter
- 在@Controller注解类中注解方法处理器适配器RequestMappingHandlerAdapter
3.1 简单的控制器处理器适配器:SimpleControllerHandlerAdapter
该处理器适配器是默认的。用于适配实现了Controller接口的处理器,最终调用处理器类的handlerRequest方法。该适配器不会处理请求参数的转换。配置方式如下:
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
3.2 HTTP请求处理器适配器:HttpRequestHandlerAdapter
主要处理普通的HTTP请求的处理器适配器。适配的处理器的类实现了HttpRequestHandler接口。最然HttpRequestHandler和Controller接口都有handlerRequest()处理方法,但Controller接口中的方法的返回值是一个ModelAndView对象,而HttpRequestHandler中的方法无返回值。不过Controller接口的handlerRequest()方法有标注@Nullable注解,也就是可以返回空值,故可以使用Controller接口方式取代实现HttpRequestHandler接口的方式。
3.3 Servlet处理器适配器:SimpleServletHandlerAdapter
该处理器适配器支持的是继承自Servlet接口的类,也就是适配Servlet,Servlet使用service(request, response)进行请求的处理。
3.4 请求映射处理器适配器:RequestMappingHandlerAdapter
最常用的处理器适配器,支持的是HandlerMethod类型的处理器,也就是适配@RequestMapping等请求映射注解的方法。该适配器的作用如下:
- 获取当前Spring容器的Bean类中标注了@ModelAttribute注解但没标注@RequestMapping注解的方法,在真正调用具体的处理器方法之前会将这些方法依次调用。
- 获取当前Spring容器的Bean类中标注了@InitBinder注解的方法,调用这些方法对一些用户自定义的参数进行转换并且绑定。
- 根据当前Handler的方法参数标注的注解类型(如@RequestParam,@ModelAttribute等),获取其对应的参数处理器(ArgumentResolve),并将请求对象中的参数转换为当前方法中对应注解的类型。
- 通过反射调用具体的处理器方法。
- 通过ReturnVallueHandler对返回值进行适配,比如,ModelAndView类型的返回值就由ModelAndViewMethodRetuenValueHandler处理,最终将所有的处理结果统一封装为ModelAndView类型的对象返回。
不同类型地处理器适配器对应相应地映射器,其关系如下:
处理器映射器 | 处理器适配器 | 处理器 |
BeanNameUrlHandlerMapping SimpleUrlHandlerMapping | SimpleControllerHandlerAdapter
| 实现Controller的接口类 |
BeanNameUrlHandlerMapping SimpleUrlHandlerMapping | HttpRequestHandlerAdapter | 实现HttpRequestHandler的接口类 |
BeanNameUrlHandlerMapping SimpleUrlHandlerMapping | SimpleServletHandlerAdapter | Servlet |
RequestMappingHandlerMapping | RequestMappingHandlerAdapter | HandlerMethod使用@Controller注解类中,@RequestMapping注解的方法 |
以上几种处理器可以并存使用,但基于注解的开发中,一般使用RequestMappingHandlerAdapter就可以了。(如果在配置文件中配置了BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping的Bean,那么就不需要显式地配置对应地适配器,因为容器会自动注册。但如果在配置文件中配置了RequestMappingHandlerMapping和RequestMappingHandlerAdapter地情况下,如果还需要其他类型地适配器,则需要显式地配置)。
4.视图与视图解析器
Controller中的请求处理方法执行完成会返回一个ModelAndView类型的对象(这个对象包含一个逻辑视图名和模型数据),接着执行DispatcherServlet的processDispatcherResult方法,结合视图和模型进行请求的响应,具体步骤包括:
- 使用容器中注册的视图解析器,根据视图的逻辑名称,实例化一个View子类对象并返回给DispatcherServlet。
- DispatcherServlet调用视图的render()方法,结合模型数据对视图进行渲染后,把视图的内容通过响应流响应到客户端。
对于返回String、View或ModelMap等类型的映射方法,SpringMVC也会在内部将他们装配成一个ModelAndView对象。
4.1 视图
视图(View)的作用是渲染模型数据,将模型数据里的数据以某种形式呈现给客户端,视图的类型可以是JSP,也可以是JSON或Excel等其他类型。以JSP视图来说,可以理解为将JSP文件中的${}占位符替换成模型里的实际值,通过HttpServletResponse输出流的方式响应到客户端。Spring提供了统一的View接口,该接口定义了如下两个方法:
public interface View {
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
@Nullable
default String getContentType() {
return null;
}
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)throws Exception;
}
- getContentType():得到内容类型,也就是响应到客户端的类型,也如HTML的页面类型text/html、JSON格式的数据类型application/json、PDF格式的文件类型application/pdf或其他的MIME类型。
- render():结合模型数据渲染视图。这个方法会实际调用HttpServletResponse的数据流输出方法out.write(),或者使用RequestDispatcher进行请求的派发。
Spring内置了继承View接口的不同类型视图,包括:
- InternalResourceView:对应JSP文件。
- JstlView:继承自InternalResourceView,是包含JSP标准标签的JSP页面。
- FreeMarkerView:FreeMarker模板引擎视图。
- VelocityView:Velocity模板引擎视图。
- MappingJackson2JsonView:JSON数据格式的视图。
- MarshallingView:XML内容类型的视图。
- RedirectView:重定向视图。
除以上视图外,SpringMVC还提供了PDF、Excel等文档类型的视图。一般的应用开发主要使用两种:JSP相关的视图和JSON数据格式的视图。JSP页面使用InternalResourceView视图,对于前后端分离的架构,前端使用Ajax方式获取数据,Spring后端则更多使用JSON相关的视图MappingJackson2JsonView。视图对象由ViewResolver(视图解析器)根据视图的逻辑名称处理后返回。
4.2 视图解析器
视图解析器是把一个逻辑上的视图名称解析为一个真正的视图,Spring提供了统一视图解析器的统一接口ViewResolver,该接口只有如下一个方法:
public interface ViewResolver {
@Nullable //根据视图名称找到具体的视图实现类
View resolveViewName(String viewName, Locale locale) throws Exception;
}
对应不同类型的视图,Spring也实现了不同的视图处理器,常见的视图处理器如下:
- AbstractCachingViewResolver:带缓存功能的ViewResolver接口的基础实现抽象类将解析过的视图进行缓存,下次再次解析时就会在缓存中直接寻找该视图;
- InternalResourceViewResolver:根据视图名字返回InternalResourceView;
- FreeMarkerViewResolver:返回FreeMarkerView;
- BeanNameViewResolver:根据视图名字去匹配定义好的视图Bean对象。他不会进行视图缓存。
在一个SpringMVC项目中可以同时使用多个视图解析器组成一个视图解析器链。在同等优先级的情况下,遍历的顺序是由视图解析器在SpringMVC配置文件中配置的顺序决定,谁在前谁先遍历。ViewResolver实现了Ordered接口,可以进行排序。当Controller处理器方法返回一个逻辑视图名称后,视图解析器链就会根据优先级进行处理。当一个视图解析器在视图解析后返回一个非空的View对象,视图解析完成,后续的ViewResolver将不会再来解析该视图。当一个ViewResolver在进行视图解析后返回的View对象是null,此时如果还存在其他的order值比它大,那么ViewResolver就会调用剩余的order值中最小的那个来解析该视图,以此类推,当所有ViewResolver都不能解析该视图的时候,Sping就会抛出一个异常。(因为InternalResourceViewResolver的解析器总是返回一个非空的View对象,所以一定要放在ViewResolver链的最后面,也就是order值要设置的足够大)