承接前文 springboot 情操陶冶 - web 配置 (一), 本文将在前文的基础上分析下 mvc 的相关应用
MVC 简单例子
直接编写一个 Controller 层的代码, 返回格式为 json
- 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 配置的过程
DispatcherServletAutoConfiguration
首先需要配置 DispatcherServlet 组件, 分为几个步骤来看
No.1 脑头注解了解下
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
- @Configuration
- @ConditionalOnWebApplication(type = Type.SERVLET)
- @ConditionalOnClass(DispatcherServlet.class)
- @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
- @EnableConfigurationProperties(ServerProperties.class)
- public class DispatcherServletAutoConfiguration {
- }
由以上的注解可得知, 其需要在 ServletWebServerFactoryAutoConfiguration 类注入至 bean 工厂后方可继续, 这就和前文关联起来了.
No.2 DispatcherServletConfiguration 内部类
- @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 集合中呢
No.3 DispatcherServletRegistrationConfiguration 内部类
- @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 来实现的, 具体的本文不展开, 不过如果用户想把 Servlet 或者 Filter 注入至 tomcat, 则常用此 Bean 来操作即可
WebMvcAutoConfiguration
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();
- 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
- **/
- 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);
- }
- }
- }
来源: https://www.cnblogs.com/question-sky/p/9585827.html