SpringBoot MVC 和靜態資源
首先,我們一定要搞清楚,mvc 配置和 static 配置的聯系和區別。 mvc 配置其實就是給 spring mvc 框架用的, 具體來說, 比如 @RequestMapping, 它會返回一個ModelAndView。 我們對這個ModelAndView進行渲染的時候, 需要查找 view, 具體怎么查找呢? 通過mvc 的各種配置。 當然,最終是交給了某個 viewResolver 進行解析。靜態資源,其實也是經過了spring mvc,具體來說是ResourceHttpRequestHandler,但ResourceHttpRequestHandler 幾乎不需要配置。spring mvc幾乎沒有提供靜態資源的配置項(除了一個靜態資源的映射),boot倒是提供了 關於位置和后綴的配置, 具體參見我的boot相關博客。
SpringBoot 中的MVC配置
boot 提供了很多 mvc 配置項,
# SPRING MVC (WebMvcProperties) spring.mvc.async.request-timeout= # Amount of time (in milliseconds) before asynchronous request handling times out. spring.mvc.date-format= # Date format to use. For instance `dd/MM/yyyy`. spring.mvc.dispatch-trace-request=false # Dispatch TRACE requests to the FrameworkServlet doService method. spring.mvc.dispatch-options-request=true # Dispatch OPTIONS requests to the FrameworkServlet doService method. spring.mvc.favicon.enabled=true # Enable resolution of favicon.ico. spring.mvc.formcontent.putfilter.enabled=true # Enable Spring's HttpPutFormContentFilter. spring.mvc.ignore-default-model-on-redirect=true # If the content of the "default" model should be ignored during redirect scenarios. spring.mvc.locale= # Locale to use. By default, this locale is overridden by the "Accept-Language" header. spring.mvc.locale-resolver=accept-header # Define how the locale should be resolved. spring.mvc.log-resolved-exception=false # Enable warn logging of exceptions resolved by a "HandlerExceptionResolver". spring.mvc.media-types.*= # Maps file extensions to media types for content negotiation. spring.mvc.message-codes-resolver-format= # Formatting strategy for message codes. For instance `PREFIX_ERROR_CODE`. spring.mvc.servlet.load-on-startup=-1 # Load on startup priority of the Spring Web Services servlet. spring.mvc.static-path-pattern=/** # Path pattern used for static resources. spring.mvc.throw-exception-if-no-handler-found=false # If a "NoHandlerFoundException" should be thrown if no Handler was found to process a request. spring.mvc.view.prefix= # Spring MVC view prefix. spring.mvc.view.suffix= # Spring MVC view suffix.
最常用的大概是:
spring.mvc.view.prefix=/somePath/
spring.mvc.view.suffix=.html
這個兩個都是對 viewResolver 進行配置用的。這兩個值默認都是空, 為空的意思是說,@RequestMapping 返回的是什么, 那么boot 就直接查找那個view, 沒有前綴和后綴 。這會產生一種奇怪的情況, 那就是, 比如映射是@RequestMapping("lk") 返回 "aa" 或者"/aa" ( 結果是一樣的 ), 如果aa 文件不存在於跟目錄,那么就返回 404; 如果存在,不管 aa 文件內容是什么, 由於瀏覽器不 識別器類型, 於是變成了下載。。
boot 在查找mvc view 的時候, 是去靜態資源目錄去查找的, 也就是 spring.resources.static-locations 對應的目錄。
如果我們直接反問靜態資源, 不管是 spring.mvc.view.prefix 還是 spring.mvc.view.suffix , 還是 其他的 spring.mvc.view 開頭的屬性, 怎么配置都是沒影響的。 但是@RequestMapping返回的view, 卻又是在 靜態目錄進行查找的(非thymeleaf 等視圖模板的情況, thymeleaf等是在templates下查找的), 而且 spring.mvc.view.prefix 及 spring.mvc.view.suffix 都是有效的。 這里應該要理清, 不然容易搞混。
再強調一遍, 訪問靜態資源, 一定要路徑完全匹配, 否則就被 boot 認為是訪問 動態資源, 比如 mvc 請求!
SpringBoot MVC 和JSP
如果我們希望使用 jsp 做動態資源的渲染, 那么, 我們應該這么配置:
spring.mvc.view.suffix=.jsp
但是呢, 這樣做之后,還是不夠的。 因為boot 內嵌的 tomcat容器默認不能解析 jsp, 關於 boot 中使用jsp,參見我的另外的博客。 一般來說,最好還是不要再boot 中使用jsp。誰用誰苦自己知道, 我當初就被坑了很久。 官方的說明是這樣的:
If possible, JSPs should be avoided. There are several known limitations when using them with embedded servlet containers. —— 盡量不要使用jsp !!
為什么? 因為各種局限:
27.4.5 JSP Limitations
When running a Spring Boot application that uses an embedded servlet container (and is packaged as an executable archive), there are some limitations in the JSP support.
局限1 With Tomcat, it should work if you use war packaging. That is, an executable war works and is also deployable to a standard container (not limited to, but including Tomcat). An executable jar does not work because of a hard-coded file pattern in Tomcat.
局限2 With Jetty, it should work if you use war packaging. That is, an executable war works, and is also deployable to any standard container.
局限3 Undertow does not support JSPs.
局限4 Creating a custom error.jsp page does not override the default view for error handling. Custom error pages should be used instead.
There is a JSP sample so that you can see how to set things up
這么簡單的英語我就不翻譯了。其實官方也是有提供boot集成jsp示例的,jsp官方的示例是:https://github.com/spring-projects/spring-boot/tree/v2.0.0.M7/spring-boot-samples/spring-boot-sample-web-jsp。 我試過,確實可行(不廢話嗎,官方的能有啥毛病....) 但是盡管如此,還是很容易踩坑,一不小心就。。
SpringBoot MVC 自動配置是如何生效的?
boot 的mvc 功能大致是通過 WebMvcAutoConfiguration,以及DispatcherServletAutoConfiguration ,EmbeddedServletContainerAutoConfiguration 完成的。它做了很多工作。 多到, 很多時候, 出了問題, 我們搞不清, 只能通過源碼來了解。
我們看一下大致有那些 自動配置:
配置了:
DispatcherServlet 這個當然是必須的,在傳統項目中,我們可能通過web.xml 來配置,boot 是使用編程方式配置
MultipartResolver 上傳解析器
ServletRegistrationBean 用來定制傳統的Servlet,一般配合@Bean使用。
OrderedHttpPutFormContentFilter 對method 為put即patch 的form 請求的做一些參數處理
OrderedHiddenHttpMethodFilter 把post請求中_method 參數轉換為 請求的method,
ResourceChainResourceHandlerRegistrationCustomizer 資源鏈,前端資嵌套引用時會用到
WelcomePage 首頁,歡迎頁面
ResourceResolver 靜態資源
RequestMappingHandlerAdapter
RequestMappingHandlerMapping
Validator
ConfigurableWebBindingInitializer
ExceptionHandlerExceptionResolver
ContentNegotiationManager
InternalResourceViewResolver
BeanNameViewResolver
ContentNegotiatingViewResolver
LocaleResolver
Formatter<Date>
FaviconConfiguration
ErrorPagexxx
官方說明
它完成了很多的工作。引用 官方 27.1.1 Spring MVC Auto-configuration(https://docs.spring.io/spring-boot/docs/2.0.0.M7/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration)的描述:
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
Support for serving static resources, including support for WebJars (covered later in this document).
Automatic registration of Converter, GenericConverter, and Formatter beans.
Support for HttpMessageConverters (see below).
Automatic registration of MessageCodesResolver (covered later in this document).
Static index.html support.
Custom Favicon support (covered later in this document).
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
如果你想使用使用spring boot mvc 的特性的同時,想增加一些mvc 的配置,比如攔截器,格式化去,視圖控制器等,你可以寫一個類繼承WebMvcConfigurer,注解 @Configuration,但不要有@EnableWebMvc注解。如果你只是想定制化RequestMappingHandlerMapping, RequestMappingHandlerAdapter, 或者ExceptionHandlerExceptionResolver,那么你只要定義一個WebMvcRegistrationsAdapter 實例就ok了
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
如果你想自己控制整個 Spring MVC,你可以寫一個自己的配置類,同時注解上@Configuration 和 @EnableWebMvc。
關於OrderedHttpPutFormContentFilter , 大致是這樣的:
protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && this.isFormContentType(request)) { HttpInputMessage inputMessage = new ServletServerHttpRequest(request) { public InputStream getBody() throws IOException { return request.getInputStream(); } }; MultiValueMap<String, String> formParameters = this.formConverter.read((Class)null, inputMessage); if (!formParameters.isEmpty()) { HttpServletRequest wrapper = new HttpPutFormContentFilter.HttpPutFormContentRequestWrapper(request, formParameters); filterChain.doFilter(wrapper, response); return; } } filterChain.doFilter(request, response); }
也就是把put/patch請求的 body解析為請求參數,body 應該是這個格式: aa=11&bb=22; 使用 & 和= 來分隔。就跟我們的get 的querystring是一樣的。然后使用 URLDecoder.decode 來解碼。 不太懂這里的FormHttpMessageConverter 為什么這么設計,感覺怪怪的。