承接前文springboot情操陶冶-web配置(二),本文將在前文的基礎上分析下mvc的相關應用css
直接編寫一個Controller層的代碼,返回格式爲jsonhtml
package com.example.demo.web.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; import java.util.Map; /** * @author nanco * ------------- * ------------- * @create 2018/9/4 **/ @Controller @RequestMapping("/boot") @ResponseBody public class DemoController { @RequestMapping(value = "/hello", method = RequestMethod.GET) public Map<String, String> helloWorld() { Map<String, String> result = new HashMap<>(); result.put("springboot", "hello world"); return result; } }
運行以後,客戶端工具HTTP訪問連接http://127.0.0.1:9001/demoWeb/boot/hello即可獲得如下的簡單結果前端
{"springboot":"hello world"}
咱們都知道springmvc最核心的組件即是DispatcherServlet,其本質是個Servlet組件,也包含了處理前端請求的邏輯,具體的可參照SpringMVC源碼情操陶冶-DispatcherServlet。本文則講解Springboot建立DispatcherServlet以及MVC配置的過程java
首先須要配置DispatcherServlet組件,分爲幾個步驟來看jquery
No.1 腦頭註解瞭解下web
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(DispatcherServlet.class) @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) @EnableConfigurationProperties(ServerProperties.class) public class DispatcherServletAutoConfiguration { }
由以上的註解可得知,其須要在ServletWebServerFactoryAutoConfiguration類注入至bean工廠後方可繼續,這就和前文關聯起來了。spring
No.2 DispatcherServletConfiguration內部類json
@Configuration @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) protected static class DispatcherServletConfiguration { // 引入了spring.mvc爲開頭的配置 private final WebMvcProperties webMvcProperties; private final ServerProperties serverProperties; public DispatcherServletConfiguration(WebMvcProperties webMvcProperties, ServerProperties serverProperties) { this.webMvcProperties = webMvcProperties; this.serverProperties = serverProperties; } // 直接建立DispatcherServlet並注入至bean工廠 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet() { DispatcherServlet dispatcherServlet = new DispatcherServlet(); // 對應spring.mvc.dispatch-options-request dispatcherServlet.setDispatchOptionsRequest( this.webMvcProperties.isDispatchOptionsRequest()); // 對應spring.mvc.dispatch-trace-request dispatcherServlet.setDispatchTraceRequest( this.webMvcProperties.isDispatchTraceRequest()); // 對應spring.mvc.throw-exception-if-no-handler-found dispatcherServlet.setThrowExceptionIfNoHandlerFound( this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); return dispatcherServlet; } // 建立名爲multipartResolver的用於文件請求 @Bean @ConditionalOnBean(MultipartResolver.class) @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) public MultipartResolver multipartResolver(MultipartResolver resolver) { // Detect if the user has created a MultipartResolver but named it incorrectly return resolver; } // 獲取server.servlet.path代表DispatcherServlet的攔截路徑 @Bean public DispatcherServletPathProvider mainDispatcherServletPathProvider() { return () -> DispatcherServletConfiguration.this.serverProperties.getServlet() .getPath(); } }
很簡單,就是建立了DispatcherServlet,那麼如何被注入至tomcat的servlet集合中呢tomcat
No.3 DispatcherServletRegistrationConfiguration內部類springboot
@Configuration @Conditional(DispatcherServletRegistrationCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration { private final ServerProperties serverProperties; private final WebMvcProperties webMvcProperties; private final MultipartConfigElement multipartConfig; public DispatcherServletRegistrationConfiguration( ServerProperties serverProperties, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfigProvider) { this.serverProperties = serverProperties; this.webMvcProperties = webMvcProperties; this.multipartConfig = multipartConfigProvider.getIfAvailable(); } // 對DispatcherServlet注入至tomcat等容器中 @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { // 同server.servlet.path,默認爲/ ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>( dispatcherServlet, this.serverProperties.getServlet().getServletMapping()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); // 讀取spring.mvc.servlet.load-on-startup,默認爲-1 registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; } }
由上述代碼得知,將servlet注入至tomcat容器是經過ServletContextInitializer接口的實現類ServletRegistrationBean<T extends Servlet>來實現的,具體的本文不展開,不過若是用戶想把Servlet或者Filter注入至tomcat,則經常使用此Bean來操做便可
DispatcherServlet組件建立並注入至web容器後,接下來即是對mvc的相關配置,筆者也按幾個步驟來分析
No.1 腦袋註解看一下
@Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { }
此配置也是根據上文中的DispatcherServletAutoConfiguration注入至bean工廠後再生效。
No.2 Filter集合
1.HiddenHttpMethodFilter-隱性傳播PUT/DELETE/PATCH請求
@Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { // 默認對post請求的包讀取_method參數指定的方法,而後再做轉換 return new OrderedHiddenHttpMethodFilter(); }
隱性的經過methodParam參數來傳播PUT/DELETE/PATCH請求,默認參數名爲_method
,也可用戶自行配置
2.HttpPutFormContentFilter-顯性響應PUT/DELETE/PATCH請求
// spring.mvc.formcontent.putfilter.enabled不指定或者值不爲false則生效 @Bean @ConditionalOnMissingBean(HttpPutFormContentFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true) public OrderedHttpPutFormContentFilter httpPutFormContentFilter() { // 直接對PUT/DELETE/PATCH請求進行響應,其order值大於OrderedHiddenHttpMethodFilter return new OrderedHttpPutFormContentFilter(); }
其通常與上述的OrderedHiddenHttpMethodFilter搭配使用,其order值大於前者因此排在後面響應PUT等請求。 舒適提示:此處只是註冊了filter到bean工廠,並無被注入至tomcat等web容器中,用戶若是想支持上述的請求方法,能夠考慮經過ServletRegistrationBean/FilterRegistrationBean來進行注入
No.3 EnableWebMvcConfiguration內部類,其類同*@EnableWebMvc註解,類同咱們經常使用spring配置的mvc:annotation-driven*。因爲代碼過多,就挑選幾個來看
@Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { // 註冊RequestMappingHandlerAdapter組件 @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; } // 註冊RequestMappingHanlderMapping組件 @Bean @Primary @Override public RequestMappingHandlerMapping requestMappingHandlerMapping() { // Must be @Primary for MvcUriComponentsBuilder to work return super.requestMappingHandlerMapping(); } // 校驗器組件 @Bean @Override public Validator mvcValidator() { if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { return super.mvcValidator(); } return ValidatorAdapter.get(getApplicationContext(), getValidator()); } // 異常處理組件 @Override protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { if (this.mvcRegistrations != null && this.mvcRegistrations .getExceptionHandlerExceptionResolver() != null) { return this.mvcRegistrations.getExceptionHandlerExceptionResolver(); } return super.createExceptionHandlerExceptionResolver(); } }
主要是用來註冊響應前端請求的插件集合,具體的怎麼整合可見筆者置頂的spring文章,裏面有提,就不在此處展開了 舒適提示:筆者此處提醒下此類是DelegatingWebMvcConfiguration的實現類,其自己也被註解*@Configuration修飾,其內部的**setConfigurers()**方法有助於集結全部實現了WebMvcConfigurer*接口的集合,因此用戶可經過實現此接口來擴展mvc的相關配置
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }
No.4 WebMvcAutoConfigurationAdapter內部類(WebMvcConfigurer接口實現類)-在上述的MVC組件的基礎上新增其餘的組件,包含視圖組件、消息處理器組件等。 限於代碼過長,筆者此處也挑選幾個來看
// 消息處理器集合配置 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.addAll(this.messageConverters.getConverters()); } // 對路徑請求的配置 @Override public void configurePathMatch(PathMatchConfigurer configurer) { // 對應spring.mvc.pathmatch.use-suffix-pattern,默認爲false configurer.setUseSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseSuffixPattern()); // 對應spring.mvc.patchmatch.use-registered-suffix-pattern,默認爲false configurer.setUseRegisteredSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern()); } // 建立jsp視圖解析器 @Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // 對應spring.mvc.view.prefix,默認爲空 resolver.setPrefix(this.mvcProperties.getView().getPrefix()); // 對應spring.mvc.view.suffix,默認爲空 resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; } // 靜態文件訪問配置 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 對應spring.resource.add-mappings,默認爲true if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache() .getCachecontrol().toHttpCacheControl(); // 此處的靜態資源映射主要針對前端的一些文件,好比jquery/css/html等等 if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry .addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)) .setCacheControl(cacheControl)); } // 對應spring.mvc.static-path-pattern,默認爲/** String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration( registry.addResourceHandler(staticPathPattern) // 對應spring.resources.static-locations .addResourceLocations(getResourceLocations( this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)) .setCacheControl(cacheControl)); } } // 歡迎界面配置,通常可在static或者項目根目錄下配置index.html界面便可 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping( ApplicationContext applicationContext) { return new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); }
本文主要講解了mvc的springboot自動配置過程,讀者主要關注DispatcherServlet組件和消息處理等組件的bean工廠配置便可。若是用戶也想自定義去擴展mvc的相關配置,可自行去實現WebMvcConfigurer接口便可,樣例以下
package com.example.demo.web.config; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * @author nanco * ------------- * ------------- * @create 2018/9/5 **/ @Configuration public class BootWebMvcConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { } @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { } }
本文也講述了若是用戶想擴展相應的Filter或者Servlet,可以使用FilterRegistrationBean/ServletRegistrationBean,樣例以下
package com.example.demo.web.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author nanco * ------------- * ------------- * @create 2018/9/5 **/ @Configuration public class ServletFilterBeans { // only intercept /simple/ @Bean("simpleServlet") public ServletRegistrationBean<Servlet> simpleServlet() { return new ServletRegistrationBean<>(new SimpleServlet(), "/simple/"); } // intercept /simple、/simple/、/simple/ha etc. @Bean("simpleFilter") public FilterRegistrationBean<Filter> simpleFilter() { FilterRegistrationBean bean = new FilterRegistrationBean<>(); bean.setFilter(new SimpleFilter()); bean.addUrlPatterns("/simple/*"); return bean; } private static class SimpleServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doService path: " + req.getRequestURI()); super.doGet(req, resp); } } private static class SimpleFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { System.out.println("filter path: " + request.getRequestURI()); filterChain.doFilter(request, response); } } }