文章目录
大名鼎鼎的Spring MVC。
Spring MVC是基于原生的
Servlet API
和Spring。
从Spring-5.0开始并行化一个新的Web框架spring-webflux。都会在后面介绍。
1.1 DispatcherServlet
分发器。像大多数web框架一样,围绕着前置controller模式,指一个中心的 Servlet(DispatcherServlet),提供对请求处理的分享算法,真实的工作交给配置的组件。这种模式是灵活和支持分流。
核心的DispatcherServlet
需要使用java配置或者web.xml
来配置。反过来DispatcherServlet
使用Spring的配置来发现他委托的组件:mapping,view resolution,exception handling处理等。
使用java配置注册和初始化DispatcherServlet
。
分发器
1.1.1 Context的结构层次
DispatchServlet希望WebApplicationContext(从Application扩展)
有自己的配置。WebApplicationContext
有对ServletContext
和Servlet
的链接。同时也会被绑定到ServletContext
,以便于RequestContextUtils
静态方法反向找到WebApplicationContext。
对于很多应用来说一个独立的WebApplicationContext
就够了。但是也有可能有一个层次化的context结构,一个root WebApplicationContext被多个DispatcherServlet(或者其他Servlet)
共享,每一个都拥有子WebApplicationContext
配置。
root WebApplicationContext
典型的包含基础设施bean,例如
data repositories 和 business services,需要被多个servlet共享。这些bean可以被有效的复用和重用,典型的每个Servlet拥有本地版的bean。
层次图
等效的xml配置
1.1.2 Special Bean Types 特殊的Bean类型。
DispatcherServlet
委托给特殊的bean去处理request和提供合适的response。这里的特殊bean指的是被Spring-managed管理的的实例,也实现Spring框架,这些类通常是自带的,但是你也可以去自定义。
bean type | 解释 |
---|---|
HandlerMapping | 映射一个request到一个handler,且拥有一堆链式的拦截器pre-和post-。映射是基于一些准则,但是各自的实现有很大差异。两个主要的HandlerMapping实现是RequestMappingHandlerMapping (支持@requestMapping)和SimpleUrlHandlerMapping (对URI path有明确的要求) |
HandlerAdapter | 帮助DispatchServlet去调用Handler(当请求来的时候),无视真实的handler是如何调用的。这个类的主要目的是帮助DispatcherServlet无需关注调用handler的实现细节 |
HandlerExceptionResolver | 解决异常的策略,有可能将异常路由到handler,到HTML的错误*(如404,500页面) |
ViewResolver | 解决将String-based viewname对应到真实的view页面作为响应 |
LocaleEsolver,localContextResolver | 解决本地化的client端提供一个时间域,然后提供国家化的视图 |
ThemeResolver | 解决你web应用的主题 |
MultipartResolver | 解析分片请求multi-part request(比如brower 格式化 文件上传) |
FlashMapManager | 存储和解析"input"和"output",flashMap可以将一个请求的属性传递给另外一个 |
1.1.3 Web MVC Config
应用可以声明基础设施bean如上面1.1.2所列的来处理请求。DispatcherServlet会为每个special bean来检查WebApplicationContext。如果没有匹配的bean类型,他会使用DispatcherServlet.properties配置默认标识的类型。
大多数场景,mvc config是最佳的开始。他用java或者xml提供一个high-level的配置。
1.1.4 Servlet Config
Servlet 3.0+,你可以用是用代码或者web.xml来配置Servlet 容器。下列示例注册一个DispatcherServlet
。
WebApplicationInitializer
是Spring MVC提供的一个接口,确保你的实现能被检测到,并自动应用到Servlet 3容器。一个WebApplicationInitializer
的抽象实现是AbstractDispatcherServletInitializer
让你更容易的注册DispatcherServlet
通过覆盖指定的方法。
use Java-based的配置推荐如下模板
如果你是用XML-based的Spring 配置,你应该是用AbstractDispatcherServletInitializer
,如下
AbstractDispatcherServletInitializer
也提供添加Filter实例,自动会添加到DispatcherServlet
。
1.1.5 Processing 处理逻辑
DispatchServlet处理请求的流程如下
WebApplicationContext
被搜索并作为一个属性绑定到request中去,会被controller和其他process中元素使用到。默认会被绑定到DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
key下- 本地的
Resolver
被绑定到request上,去解决本地处理请求需要使用的资源(渲染 view,准备数据等等)。如果不需要本地Resolver
,那么你就不需要这一层,。 - theme resolver ,主题resolver会被绑定到request上去让成员,比如views去侦测到具体哪个主体可以被使用,如果你不需要这个主题层,那么你可以忽视他。
- 如果你具体指定了一个multipart file resolver,request会被multiparts侦测到。如果multiparts被发现了,request会被包裹在
MultipartHttpServletRequest
中,为proccess的其他元素提供进一步的处理。 - 寻找合适的handler。如果hanler被找到了,关联在这个hanlder上的(preprocessors, postprocessors, and controllers) 执行链,被按顺序执行,目的去准备一个model和渲染。换句话说,有注解的controller,他的response会被(在HandlerAdapter)中的渲染,而不是返回一个view。
- 如果一个model被返回了,这个view也被渲染了。如果没有mode被返回(maybe due to a preprocessor or postprocessor intercepting the request, perhaps for security reasons),没有view被提供,那就是因为request早已经被实现了。
被声明在WebApplicationContext
中的HandlerExceptionResolver
bean,被用来解决处理request proceesing的异常。这个resolver可以被自定义
Spring的DispatcherServlet支持返回last-modification-date
(最后修改日期),由于Sevelet 的具体API。对于一个具体 的request,process支持侦测最后一次的修改日期:这DispatcherServlet寻找合适嗯handler(实现LastModified)。如果找到了LastModified
中getLastModified(request)
方法被返回给client。
你可以自定义分离的DispatcherServlet
实例,通过添加到Servelt初始化参数(init-param elements)到web.xml中去。下列的表列举了被支持的参数
1.1.6. Interception 介入
HandlerMapping
的实现支持handler interceptors是十分有用的,当你想为request应用一些特殊的规则-例如检查。Interception必须实现org.springframework.web.servlet
中的HandlerInterceptor
,其包含了三个灵活的方法:
- preHandle(…): Before the actual handler is executed。return boolean
- postHandle(…): After the handler is executed
- afterCompletion(…): After the complete request has finished
preHandle(…)返回一个boolean用来确定,是否继续执行下来的调用链。true执行,false中断。
postHandle(…)很少被用到。是用@ResponseBody和ResponseEntity方法时,response在HandlerAdapte内已经被提交或者写入,意味着做任何更改太迟了—比如添加extra header。
对于这种情况,你可以实现ResponseBodyAdvice
然后声明一个Controller Advice bean
,直接配置在RequestMappingHandlerAdapter
中
1.1.7 异常
如果在request mapping过程比如 在一个@Controller中发生异常了,DispatcherServlet
会委托链式的HandlerExceptionResolver的bean去处理异常,也提供默认的处理,通常是erorr response.
resolver的实现
Chain of Resolvers resolver链
你可以通过声明多个HandlerExceptionResolver
来构成异常链,在你的spring配置里,并且设置它的order
属性。order的序号越大,resolver就会被放到越后面。
HandlerExceptionResolver具体会返回这些内容
- a ModelAndView that points to an error view.
- An empty ModelAndView if the exception was handled within the resolver
- null if the exception remains unresolved, for subsequent resolvers to try, and, if the exception remains at the end, it is allowed to bubble up to the Servlet container.
Container Error Page
error page的返回
声明的error page mapping
以上的配置会被映射到controler逻辑
1.1.8 view Resolution
Spring MVC定义了ViewResolver和view接口,提供渲染逻辑。ViewResolver提供viewname和真实views映射的能力。view路由到数据的准备到具体的view技术。
ViewResolver的实现
ViewRsolver | Description |
---|---|
AbstractCachingViewResolver | AbstractCachingViewResolver的子类,负责缓存view实例,缓存提升了查询性能。你可以设置属性cache 为false来关闭。如果你在runtime必须刷新一个特定的view,你可以使用removeFromCache(String viewName, Locale loc) method. |
XmlViewResolver | 实现ViewResolver,来接受xml配置。默认的配置文件位置是/WEB-INF/views.xml |
ResourceBundleViewResolver | 实现ViewResolver,来接受bean的定义在ResourceBundle ,这个bundle是基于name的。对于每个需要支持的view,需要配置[viewname].(clss) 和[viewname].url 。 |
UrlBasedViewResolver | ViewResolver的简单实现,处理view name到urls的映射,无需清晰的定义。这种方式适用于你的viewname,匹配你的view resource,但是不能产生二义性,找到多个匹配 |
InternalResourceViewResolver | UrlBasedViewResolver的便利子类,支持InternalResourceView (Servlets and JSPs)。其子类有JstlView and TilesView。你可以细化这个view类去生成所有views,通过是用setViewClass(..) |
FreeMarkerViewResolver | UrlBasedViewResolver的便捷子类,去支持FreeMarkerView |
ContentNegotiatingViewResolver | 实现ViewResolver接口,基于request的file name或者 Acceprt header属性 |
Handling
你可以链式的声明view resolvers,设置order属性可以排序,越小越执行。
ViewResolver约定,当未找到view可以返回一个null。但是在JSPs和InteralResourceViewResolver,去判定JSP是否存在的唯一方式是通过RequestDispatcher
去做分发。因此你必须在relover列表的最后配置InternalResourceViewResolver
。
配置view解决方案和添加ViewResolver 一样容易,详见config篇。
Redirecting
重定向细节:view name的前缀让你实现重定向。URLBaseViewResolver
用前缀来判定是否需要重定向。其余的的重定向URL。
网络形象是是否返回RedirectView
的原因之一,但是现在可以依赖自己的prefix来判定是否需要去重定向例如(redirect:/myapp/some/resource
)重定向到了当前的servlet的context,当一个名称例如redirect:https://myhost.com/some/arbitrary/path
。
注意如果controller如果带有@ResponseStatus
,注解的值会优先于RedirectView。
Forwarding
转向forward:
,UrlBasedViewResolver
可以解析。这会创建一个InternalResourceView
,其做了一个定向RequestDispatcher.forward()
。因此不用
InternalResourceViewResolver
和InternalResourceView
(for JSPS),但是仍旧有用。
Content Negotiation
ContentNegotiatingViewResolver
不能解决views但是可以委托其他 view resolvers和选择这些view。这个委托可以从Accept
header接受一个查询参数(例如"/path?format=pdf")
这个ContentNegotiatingViewResolver
选择合适的View
去处理request,比如Content-type
字段,ContentNegotiatingViewResolver
支持Content-type
和ViewResolvers
的绑定。处理请求时会命中第一个ViewResolver
,命中不了时,会返回DefaultViews
。随后的操作是SingleTon模式的View
进行渲染。
Accept Header
可以包含模糊匹配
比如text/*
。
1.1.9 Locale
本地化,相对的是国际化。DispatcherServlet让你可以自动解决本地客户端的locale化。这部分是由LocaleResolver
对象来解决的。
当request来时,DispatcherServlet去寻找local resolver,如果发现了就使用。通过使用RequestContext.getLocale()方法,你总是可以获取到本地resolver。
额外的本地化方案,你可以向handler mapping 添加额外的intercepeter去处理本地化需求。
本地化的解决包是在org.springframework.web.servlet.i18n
而且配置你的应用容器用一种普通的方式。下列是Spring包含的几种resolver。
Time Zone
获取client的本地化,指导它的time zone十分有用。LocaleContextResolver接口提供一个对LocaleResolver的扩展–让resolver提供一个丰富的LocaleContext
。
TimeZone
可以通过使用RequestContext.getTimeZone()
方法获取。Time zone的信息会自动被 Date/Time的 Cpnverter和Formatter的对象所注册到。
Header Resolver
local resolver 侦测到在request中accept-language
的header。通常,header 的field会包含client端本地的os系统。注意resolver不能支持time zone信息。
Cookie Resolver
本地的resolver侦测到Cookie去看Local和TimeZone是否被细化。他使用具体的细节。通过使用本地resolver的属性,你可以细化这个cookie得名称和最大时长,下列是一个例子。
Session Resolver
Locale Interceptor
Session Resolver
Session Resolver让你可以从session中获取Locale
和TimeZone
,这些可以被包含在用的请求里。和CookieLocaleResolver的差异是,这个策略本地化的选择本地setting在 Servlet container的HttpSession容器里。结果是设置只会应用到每一个session中,因此,当session结束时就会丢失。
注意无需直接和额外的session管理组件。SessionLocalResolver是和HttpSession属性一同关联在HttpServletRequest。
Locale Interceptor
你可以通过添加LocaleChangeInterceptor
到一个HandlerMapping
定义中去开启本地化的更改。开启后会侦测在request中的参数来直接更改本地化,即会调用在LocaleResolver
中的setLoacle
方法。
示例中展示调用*.view
的资源,其包含了参数siteLanguage
。所以示例中
1.1.10 Themes
主题的设置。theme是一系列静态资源的集合,典型的是style sheets和images,这些会影响应用的风格。
Defining a theme
去给你的Web应用使用主体,你必须去实现org.springframework.ui.context.ThemeSource
接口。WebApplicationContext
接口扩展了ThemeSource,但是委托给一个具体的实现。默认是org.springframework.ui.context.support.ResourceBundleThemeSource
实现去加载properties文件(从classpath的root)。去使用自定义的ThemeSource
或者去配置一个基于ResourceBundleThemeSource
,你可以是用名称注册到Context中,Web application context会自动容器里并去使用。
当你使用ResourceBundleThemeSource
的时候,主题会定义在一个简单的properties文件。
例如
properties的key-values,会关联到view code中去。
默认ResourceBundleThemeSource
会是用空的basename前缀。结果是properties文件会从classpath的root的去加载。因此,cool.properties主体文件放在root下就可以,例如/WEB-INF/classes
。ResourceBundleThemeSource
会使用标准的java源文件去加载组件,允许全国际化的主题。
例如/WEB-INF/classes/cool_nl.properties
会被引用到一个特殊的本经图片。
Resolving Themes
如果你如前面定义了这些theme。DispatcherServlet会寻找bean名为themeResolver
的bean,然后找出是用哪一个ThemeResolver
。A theme resolver和LocaleResolver
工作方式一样。从request中去侦测主题的选定
1.1.11. Multipart Resolver
分片 resolver。
MultipartResolver
在org.springframework.web.multipart
package中,在上传文件的时候去解析多片的请求。有 Commons FileUpload
和基于Servlet 3.0
的请求。
去开启多分片传输,你需要在DispatcherServlet中声明
MultipartResolver
bean,名称为multipartResolver
。DispatcherServlet会侦测并把它应用到即将到来的request。
当POST并使用multipart/form-data
,当前的resover 会解析并包裹HttpServletRequest
为MultipartHttpServletRequest
去解决分片。
Apache Commons FileUpload
使用Commons FileUpload,你可以配置CommonsMultipartResolver
名为multipartResolver
的bean。你也需要去添加这个依赖。
Servlet 3.0
Servlet 3.0的分片穿出需要在Servlet container的配置中打开。
- 在java code中,在Servlet注册中设置
MultipartConfigElement
- 在web.xml中添加
<multipart-config>
标签
下列是如何在code中设置。
1.1.12. Logging
DEBUG级别的日志在Spring MVC被设置成友好的方式,聚焦于有价值的信息。
TRACE级别的设置原则和DEBUG级别基本一样。但是被用在打印任何输出。
敏感数据
DEBUG 和 TRACE 也许会打印敏感数据。这个为什么header和参数会被默认掩盖,但是你也可以开启他,通过在DispatcherServlet设置enableLoggingRequestDetails