基于Spring mvc官方文档
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
Spring mvc-简介 + DispatcherServlet
从学习Spring mvc开始, 应用前面的RPC框架, 来开发一个web网站.
GitHub>>
1.简介
Spring web MVC 是一个基于Servlet API构建的新颖的web框架, 并且在最开始就被包含在了Spring Framework项目内. 相较于正式名称 “Spring Web MVC“, “Spring MVC“这个名字更为人们所熟知.
2.DispatcherServlet
就像其他的web框架一样, Spring MVC也是围绕着一个核心的Servlet
(DispatcherServlet
)而设计的. DispatcherServlet
为那些可配置的组件在处理实际工作时提供了一系列的共享的算法. 这种模型非常灵活, 支持各种各样的工作方式.
和所有的servlet
一样, DispatcherServlet
需要通过Java代码或者web.xml
来配置. 随后DispatcherServlet
将使用Spring来扫描发现组件, 以便使用这些组件来处理实际的事务逻辑, 比如请求映射, 视图, 异常的处理等等.
下面是使用java代码来注册及初始化DispatcherServlet
的例子:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
接下来是通过web.xml配置的例子(推荐):
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
记录下我遇到的一个问题:
上述web.xml文件中,<servlet-mapping>
下的<url-pattern>
选项/, /*的区别:
/*会将controller返回的结果再传递给DispatcherServlet
, 如果找不到对应的controller就会产生404的错误
/会将controller返回的结果直接当做视图来处理
2.1 多层 context
DispatcherServlet
需要一个由 ApplicationContext
拓展而来的WebApplicationContext
来配置自己. WebApplicationContext
有指向和自身相关联的ServletContext
和 Servlet
的链接, 同时也绑定到了ServletContext
, 以便应用程序能够使用RequestContextUtils
中的静态方法来获取WebApplicationContext
.
对很多应用程序来说, 简单地拥有一个单独的WebApplicationContext
已经足够了, 但是构造一个多层次的context
也是可能的. 在这种情况下, root WebApplicationContext
被多个DispatcherServlet
(或者其他的Servlet
) 实例所共享, 而每个实例拥有自己的child WebApplicationContext
配置.
典型的root WebApplicationContext
包含着像数据仓库和各种服务这些需要在各个Servlet
实例之间共享的Java bean. 而典型的child WebApplicationContext
只包含其Servlet
本地的beans.
下面是一个多层 context
Java 代码配置的例子:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
web.xml配置方法
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
2.2 特殊的bean类型
DispatcherServlet
委托给一些特殊的beans来完成请求的处理以及生成相应的响应. 特殊的beans指的是那些由Spring 管理的, 实现了WebFlux框架协议的Object实例. 这些beans通常来自于协议内部定义, 但是你可以自定义它们的属性, 继承甚至是替换掉它们.
一下是这些特殊beans的列表:
Bean Type | 说明 |
---|---|
HandlerMapping | 通过一系列的预处理或后处理拦截器来讲请求映射给处理程序. 这种映射取决于HandlerMapping 实现的具体细节, 不同的实现可能有不同的结果. 两个主要的 HandlerMapping 的实现之一是RequestMappingHandlerMapping , 该类支持@RequestMapping 注解标注的方法. 另一个是SimpleURLHandlerMapping , 主要负责将明确注册了的URI路径请求传递给处理程序 |
HandlerAdapter | 帮助DispatcherServlet 来调用对应请求的处理程序, 而无需在意处理程序的具体实现细节. 例如, 调用一个由注解标注的controller 需要处理注解. HandlerAdapter 主要就是负责向DispatcherServlet 封装掉这样细节. |
HandlerExceptionResolver | 按一定的策略将异常传递给处理程序, 或者返回一个出错提示的HTML页面 |
ViewResolver | 将由处理程序返回的基于字符串的视图名称映射到实际的视图, 以便生成响应 |
LocaleResolver, LocaleContextResolver |
处理客户端正在使用的地区信息以及时区等, 以便能够提供国际化了的视图 |
ThemeResolver | 处理web应用能够使用的主题, 例如提供个性化的布局. |
MultipartResolver | 在某些多段解析类库的帮助下来解析多段类型请求(比如浏览器的文件上传) |
FlashMapManager | 存取”input”和”output”的FlashMap , FlashMap 能够在个请求之间传递属性值 |
2.3 Web MVC 配置
应用程序可以根据需求来声明上表中的特殊bean, 以便实现请求的处理. DispatcherServlet
将在 WebApplicationContext
中查找这些特殊的beans. 如果没有找到匹配的bean类型, 那么DispatcherServlet
将会使用在DispatcherServlet.properties
中定义的默认类型.
在很多情况下, MVC 配置是很好的起点. 它既可以在java代码中配置, 也可以在xml配置文件中声明, 同时还提供了一些用于自定义的高级回调函数API.
2.4 Servlet 配置
在Servlet 3.0
以上的环境中, 既可以用代码也可以在web.xml中来配置Servlet
选项. 以下是一个注册DispatcherServlet
的例子:
import org.springframework.web.WebApplicationInitializer;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
WebApplicationInitializer
是一个由Spring MVC提供的接口, 以便保证你的实现能够被发现并且自动的用于初始化任何Servlet 3
容器.
一个实现了WebApplicationInitializer
抽象基类是AbstractDispatcherServletInitializer
, 使得配置更加简单. 我们只需要简单地重写那些指定servlet映射和DispatcherServlet
配置位置的方法, 就可以实现DispatcherServlet
的注册.
这种方法推荐那些基于Java代码配置的Spring应用:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
如果是基于XMl配置文件的应用, 那么就应该直接继承AbstractDispatcherServletInitializer
:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
// AbstractDispatcherServletInitializer也提供了一个方便的方法来添加过滤器
Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
每个过滤器凭以它的实际类型为基础的名字来添加并自动映射到DispatcherServlet
.
AbstractDispatcherServletInitializer
的 isAsyncSupported
的protected
方法提供了一个用来确定DispatcherServlet
以及他的所有过滤器的对异步的支持的方法. 默认情况下, 这个标志位被设为true
.
最后, 如果想更加深入的自定义DispatcherServlet
, 你可以重写createDispatcherServlet
方法.
2.5 处理(Processing)
DispatcherServlet
按如下的方式来处理请求:
WebApplicationContext
会被搜索并被添加到请求中, 以便controller
和其他的组件能够使用它. 默认被存储的key
为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
- Locale Resolver也被添加到请求中, 以便处理组件能够使用这些地区信息来处理请求(例如, 渲染视图, 准备数据等等). 如果你不需要国际化, 那么就可能不需要它.
- Theme resolver被添加到请求中, 以便视图能够决定使用哪个主题. 如果不使用主题, 就可以忽略它.
- 如果制定了一个多段文件resolver, 辉县检查请求是否分为了多段. 如果请求分段, 那么它将被包装为
MultipartHttpServletRequest
, 以方便未来对请求的处理. - 搜索一个适当的请求处理器, 如果找到了合适的处理器, 那么与该处理器相关联的执行链(预处理, 后处理, controller)将得到按序执行, 以准备或者渲染Model. 或者对于基于注解的controller来说, 可能会直接生成一个响应, 和不是返回一个view.
- 如果反返回了一个model, 视图将得到渲染. 如果没有返回model(可能由于预处理器或者后处理器因为安全原因拦截了这个请求), 就不会渲染view, 因为此时请求已经得到满足了.
在WebApplicationContext
中声名的HandlerExceptionResolver
会在请求处理过程中处理抛出的异常.
Spring的DispatcherServlet
也支持返回有Servlet API
指定的last-modification-date
. 决定特定请求的last-modification-date
的过程是很明确的: DispatcherServlet
搜索一个合适的handler, 然后判断这个handler是否实现了LastModified
接口. 如果是, 则通过LastModified
接口中的long getLastModified(request)
方法得到的值将被返回给客户端.
可以向web.xml中的Servlet
的初始化参数中添加参数来自定义DispatcherServlet
. 如下是可用的参数列表:
参数 | 说明 |
---|---|
contextClass | 指定一个实现了WebApplicationContext 的类, 该类将被Servlet 用作context. 默认情况下, 该类为XMLWebApplicationContext . |
contextConfigLocation | 传递给上述contextClass实例的字符串, 用来指示哪里可以找到contexts. 这个选项可以由以逗号分隔的多个字符串构成, 来支持多个context. 为了防止某些context 位置被多次定义, 后定义的位置优先 |
namespace | WebApplicationContext 的命名空间, 默认为[servlet-name]-servlet |
throwExceptionIfNoHandlerFound | 指示是否在某个请求找不到适当的handler时抛出NoHandlerFoundException , 该异常可以被HandlerExceptionResolver 捕获. 默认情况下, 该选项被设置为 false , 在这种情况下, DispatcherServlet 将把响应的状态码设置为404(NOT_FOUND) 而不抛出异常. 值得注意的是, 如果设置了默认的handler, 那么请求将被传递给默认的哈handler而不会发出404请求 |
2.6 拦截(Interception)
所有的HandlerMapping
都支持handler
拦截器, 这种特性对于为某些请求执行特殊的处理功能的时候有很大的帮助. 拦截器必须继承org.springframework.web.servlet
包的HandlerInterceptor
接口. 该接口定义了三个方法:
preHandle(...)
- 在handler
执行之前调用postHandle(...)
- 在handler
执行之后调用afterCompletion(...)
- 在请求执行完成之后调用
preHandle(...)
返回一个布尔值. 可以通过这个返回值来控制执行链是否继续. 如果该方法返回true
, handler
执行链将继续执行. 否则, DispatcherServlet
将会认为该拦截器已经完成了对请求的妥善处理, 执行链后序的拦截器以及handler
都不会得到执行.
需要注意的是postHandle(...)
对于@ResponseBody
和 @ResponseEntity
注解标注的方法来说用处不大. 因为这种情况下, 在postHandle(...)
调用之前, HandlerAdapter
已经完成了对response的处理. 换句话说就是在postHandle(...)
被调用的时候想再修改response已经太晚了.
2.7 异常(Exceptions)
如果在请求映射时或者@Controller
这样的handler
处理过程中抛出了异常, DispatcherServlet
将会把这个异常传递给一个由HandlerExceptionResolver
beans组成的处理链来处理这个异常, 并提供一个处理结果, 典型的就是一个异常响应.
下表是可用的HandlerExceptionResolver
:
HandlerExceptionResolver | 说明 |
---|---|
SimpleMappingExceptionResolver | 异常类名和异常视图之间的映射. 对于浏览器程序渲染出错页面是很有用的 |
DefaultHandlerExceptionResolver | 处理由Spring MVC抛出的异常, 并将它们映射到HTTP状态码. |
ResponseStatusExceptionResolver | 处理由@ResponseStatus 注解标注的异常, 并把他们映射到HTTP状态码. |
ExceptionHandlerExceptionResolver | 通过调用@Controller 或者@ControllerAdvice 中标注的@ExceptionHandler 方法来处理异常. |
*处理链 (Chain of Resolvers)
在配置中声明多个HandlerExceptionResolver
beans即可组成一个异常处理链. 可以通过order
属性来决定的它们的顺序, order
值越大, 位置就越靠后.
HandlerExceptionResolver
能够返回:
ModelAndView
- 指向出错页面- 空的
ModelAndView
- 如果Resolver
妥善处理了这个异常 null
- 如果异常未能得到处理, 交给接下来的Resolver
来尝试. 如果直到链的最后异常仍未被处理, 他将被传递给Servlet
容器.
MVC Config
会自动地声明一些内建的resolvers
, 这些resolvers
能够处理默认的Spring MVC异常, @ResponseStatus
标出的异常, 以及@ExceptionHandler
能够处理的异常. 这些resolvers
可以被自定义或者直接替换掉.
*容器错误页面 (Container error page)
如果没有HandlerExceptionResolver
能够处理异常而冒泡给了Servlet
容器, 或者响应的状态码被设为出错状态(例如, 4xx, 5xx), Servlet
容器可能会渲染一个默认的出错页面. 可以在web.xml
中声明出错页面的映射来自定义这个页面.
<error-page>
<location>/error</location>
</error-page>
在上面这种情况下, 当一个异常冒泡到Servlet
容器, 或者响应的状态码为出错状态, Servlet
容器将会调度到上述配置的URL. 随后交给DispatcherServlet
来处理. DispatcherServlet
可能会将其交给一个@Controller
来处理, 然后@Controller
来决定是渲染一个出错页面还是返回一个JSON信息. 下面是一个@Controller
的例子:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
2.8 视图(View Resolution)
Spring MVC定义了ViewResolver
和 View
接口来使你在不必掌握具体的view技术的情况下渲染浏览器中显示的model. ViewResolver
提供了在实际view和view名称之间的映射. 而View
则负责在向具体的view技术传递数据之前的准备工作.
下表是一些可用的ViewResolver
:
ViewResolver | 说明 |
---|---|
AbstractCachingViewResolver | 该抽象类的子类将会对他们处理的视图实例进行缓存. 缓存会提升某些view技术的表现. 也可以关闭缓存功能, 只需要把cache 属性设为false 即可. 除此之外, 在如果必须要刷新某个view, 可以使用removeFromCache(String viewName, Locale loc) 方法. |
XMLViewResolver | ViewResolver 的实现类接受一个XML的配置文件, 该配置文件与Spring的XML文件有相同的文档类型定义(DTD). 默认的配置文件是/WEB-INF/views.xml . |
ResourceBundleViewResolver | 实现了ViewResolver 接口, ResourceBundleViewResolver 使用定义在ResourceBundle 中定义bean的方式. 当要处理view的时候, 使用[viewname].(class) 属性值作为view类, [viewname].url 属性值作为view的url. |
UrlBasedViewResolver | ViewResolver 的一种简单实现, 将逻辑view名直接映射为URL. 如果你的逻辑名称直接匹配view资源的时候非常有用. |
InternalResourceViewResolver | UrlBasedViewResolver 的子类, 支持InternalResourceView , 以及子类JstView 和TitlesView . 可以使用setViewClass(...) 来指定view类. |
FreeMarkerViewResolver | UrlBasedViewResolver 的子类, 支持FreeMarkerView 以及它的所有子类. |
ContentNegotiatingViewResolver | 实现了ViewResolver 接口. 负责处理基于请求文件名或者Accept 头的view. |
*处理 (handling)
与异常Resolvers类似, 也可以通过声明多个Resolver
来构造一个处理链, 同时需要指定order
属性. order
值越大, 该resolver
的位置就越靠后.
ViewResolver
可以返回null
来表明无法找到该view. 但是对于JSP和InternalResourceViewResolver
来说, 唯一可以得知JSP是否存在的方式就是通过RequestDispatcher
来执行一次调度(dispatch). 因此, InternalResourceViewResolver
必须声明在处理链的最后.
*重定向 (redirecting)
在view名称前加上特殊的redirect:
前缀即可执行重定向. UrlBasedViewResolver
(以及它的子类)会把这个前缀看做是一条重定向指令, 余下的view名称就是重定向的URL.
结果就像controller返回了一个RedirectView
一样. 像redirect:/myapp/some/resource
这样的view名称将被看作是相对于当前Servlet
context的相对地址, 而redirect:http://myhost.com/some/arbitrary/path
这样的地址将作为绝对URL.
需要注意的是, 如果一个controller方法是被@ResponseStatus
标注的, 注解的值将优先于RedirectView
的值.
*转发 (fowarding)
特殊的前缀forward:
将被UrlBasedViewResolver
和它的子类识别, 然后创建一个InternalResourceView
然后执行RequestDispatcher.forward()
来进行转发. 也正是因此, forward:
前缀对于InternalResourceViewResolver
和InternalResourceView
是没用的.
2.9 地区(Locale)