[SpringBoot-web系列】前文
SpringBoot-web开发(一): 静态资源的导入(源码分析)
SpringBoot-web开发(二): 页面和图标定制(源码分析)
SpringBoot-web开发(三): 模板引擎Thymeleaf


目录

  • 一. 解读官方文档
  • 二. 拓展SpringMVC
    • 1. 拓展原理
    • 2. 环境搭建:编写拓展配置类
    • 3. 示例:拓展视图解析器
      • 1. 默认视图解析器源码分析
      • 2. 自定义视图解析器
    • 4. 修改SpringBoot默认配置
      • 示例:修改默认日期格式
    • 5. 总结
  • 三. 全面接管SpringMVC
    • 1. 什么是全面接管?
    • 2. 测试
    • 3. @EnableWebMvc原理

一. 解读官方文档

SpringBoot在底层对我们的SpringMVC新增了很多配置,我们接下来需要了解如何扩展,如何定制自己的配置

官方文档点击这里:官方文档

Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合

自动配置在Spring的默认设置之上添加了以下功能

  • 包含ContentNegotiatingViewResolverBeanNameViewResolverbeans(视图解析器)
  • 支持服务静态资源,包括对WebJars的支持
  • 自动注册ConverterGenericConverter(类型转换器)和Formatter (格式化器)beans
  • HttpMessageConverters(消息转换,转换Http请求和响应)的支持
  • 自动注册MessageCodesResolver(生成绑定错误消息)
  • 静态index.html支持(首页映射)
  • 自定义Favicon支持(图标自定义)
  • 自动使用ConfigurableWebBindingInitializer bean(数据web的初始化绑定)

使用方法

如果要保留这些SpringBoot MVC特点并添加更多的MVC功能(拦截器,格式化程序,视图控制器和其他功能),则将@Configuration注解添加到类型为WebMvcConfigurer的类上,但不添加@EnableWebMvc注解


如果要提供RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver的自定义实例,并且仍然保留Spring Boot MVC自定义,则可以声明WebMvcRegistrations类型的bean,并使用它提供这些组件的自定义实例


如果要完全控制Spring MVC,则可以添加用@EnableWebMvc注解的自己的@Configuration,或者按照@EnableWebMvc的Javadoc中的说明添加自己的@Configuration注解的DelegatingWebMvcConfiguration


二. 拓展SpringMVC

根据官方文档:如果要保留这些SpringBoot MVC特点并添加更多的MVC功能(拦截器,格式化程序,视图控制器和其他功能),则将@Configuration注解添加到类型为WebMvcConfigurer的类上,但不添加@EnableWebMvc注解

1. 拓展原理

我们查看SpringBoot底层webmvc自动配置类WebMvcAutoConfiguration中的自动适配类WebMvcAutoConfigurationAdapter

可以看到这样一个注解@Import(EnableWebMvcConfiguration.class)

也就是导入了EnableWebMvcConfiguration这个类

我们继续查看该类源码,发现它继承了一个父类DelegatingWebMvcConfiguration
我们继续查看DelegatingWebMvcConfiguration的源码,可以找到这样一个方法

@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers);}
}

该方法就是从Spring容器中获取所有的webmvcConfigurer,及所有的配置类

也就是SpringBoot在底层自动获取了所有的配置类,包括默认的配置类以及我们自定义的配置类,这也就是我们拓展的原理,我们可以新增自己配置类,注入到Spring容器中,然后SpringBoot即可自动配置


2. 环境搭建:编写拓展配置类

接下来,我们搭建一个拓展配置类环境进行实验:

首先在主程序同级目录下新建一个congfig包,用来放置的配置类,其中新建配置类MyMvcConfiguration用来拓展装配MVC的配置

通过官方文档的介绍,我们需要将@Configuration注解添加到类型为WebMvcConfigurer的类上,但不添加@EnableWebMvc注解

因此我们现在IDEA中搜索(连按shift)一下WebMvcConfigurer,可以发现它是一个接口

因此我们需要自定义的配置类MyMvcConfiguration需要实现这个接口

package com.zsr.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MyMvcConfig implements WebMvcConfigurer {}

3. 示例:拓展视图解析器

搭建好配置类环境后,我们接下来以官方文档中的第一条视图解析器为例,配置拓展一个自定义的视图解析器

在SpringMVC中,我们在其配置文件中手动配置视图解析器

<!--视图解析器:DispatcherServlet给他的ModelAndView1.获取了ModelAndView的数据2.解析ModelAndView的视图名字3.拼接视图名字,找到对应的视图 hello4.将数据渲染到这个视图上
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>

而在SpringBoot,自动配置了视图解析器;我们接下来查看源码,分析一下其配置好的的视图解析器;

1. 默认视图解析器源码分析

官网文档中提到SpringBoot默认的一个视图解析器ContentNegotiatingViewResolver,我们来分析分析

我们在IDEA中搜索ContentNegotiatingViewResolver

发现它实现了ViewResolver接口,我们继续查看ViewResolver的源码

其中有一个解析视图名称方法resolveViewName

我们查看ContentNegotiatingViewResolver继承ViewResolver接口实现的该方法

public View resolveViewName(String viewName, Locale locale) throws Exception {RequestAttributes attrs = RequestContextHolder.getRequestAttributes();Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());if (requestedMediaTypes != null) {// 获取候选的视图对象List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);// 选择一个最适合的视图对象,然后把这个对象返回View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);if (bestView != null) {return bestView;}}String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?" given " + requestedMediaTypes.toString() : "";if (this.useNotAcceptableStatusCode) {if (logger.isDebugEnabled()) {logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);}return NOT_ACCEPTABLE_VIEW;}else {logger.debug("View remains unresolved" + mediaTypeInfo);return null;}
}

可以发现该方法,就是从候选的视图中筛选出最好的视图,我们点开getCandidateViews方法看看如何获取候选的视图

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)throws Exception {List<View> candidateViews = new ArrayList<>();if (this.viewResolvers != null) {Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");//遍历所有视图for (ViewResolver viewResolver : this.viewResolvers) {//将视图封装成一个对象View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {//添加到候选视图candidateViews.add(view);}for (MediaType requestedMediaType : requestedMediaTypes) {List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);for (String extension : extensions) {String viewNameWithExtension = viewName + '.' + extension;view = viewResolver.resolveViewName(viewNameWithExtension, locale);if (view != null) {candidateViews.add(view);}}}}}if (!CollectionUtils.isEmpty(this.defaultViews)) {candidateViews.addAll(this.defaultViews);}//返回候选视图return candidateViews;
}

那么所有的视图是从那里来的呢?我们可以找到initServletContext方法,该方法就是得到所有视图解析器的方法

@Override
protected void initServletContext(ServletContext servletContext) {//从BeanFactoryUtils工具类中获取容器中的所有视图解析器Collection<ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();//ViewRescolver.class 把所有的视图解析器来组合的if (this.viewResolvers == null) {this.viewResolvers = new ArrayList<>(matchingBeans.size());for (ViewResolver viewResolver : matchingBeans) {if (this != viewResolver) {this.viewResolvers.add(viewResolver);}}}//...
}

其中从BeanFactoryUtils工具类中获取容器中的所有视图解析器,然后再对其进行赋值,拿来组合

因此:SpringBoot默认的ContentNegotiatingViewResolver视图解析器就是用来组合所有的视图解析器的


2. 自定义视图解析器

上述默认的ContentNegotiatingViewResolver类通过在Spring容器中去找视图解析器并进行组合

那如果我们自己向Spring容器中去添加一个视图解析器,这个类也会帮我们自动的将它组合进来

这样是不是就实现了拓展一个自定义的视图解析器呢?我们可以试试!

在上述编写好的配置类MyMvcConfig类中编写一个自己的视图解析器静态内部类,实现视图解析器ViewResolver接口,重写其抽象方法

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {//将自定义视图解析器实现类对象注入到bean中@Beanpublic ViewResolver myViewResolver() {return new MyViewResolver();}//自定义视图解析器实现类static class MyViewResolver implements ViewResolver {@Overridepublic View resolveViewName(String viewName, Locale locale) throws Exception {return null;}}
}

接下来我们通过打断点查看我们自定义的视图解析器是否生效

我们给DispatcherServlet类中的doDispatch方法加个断点进行调试一下,因为所有的请求都会走到这个方法中

然后我们Debug主程序

程序启动后,访问localhost:8080,程序进入doDispatcher方法

我们点击this可以查看所有的视图解析器对象

`ContentNegotiatingViewResolver`:SpringBoot默认视图解析器
`BeanNameViewResolver`:SpringBoot默认视图解析器
`TymeleafViewResolver`:导入了Tymeleaf模板引擎后Tymeleaf的视图解析器
`MyViewResolver`:我们自定义的视图解析器

我们发现了自定义的视图解析器,证明ContentNegotiatingViewResolver成功将我们自定义的视图解析器组合进来;


4. 修改SpringBoot默认配置

上述我们通过拓展视图解析器的例子简单了解了如何在SpringBoot新增自定义功能

我们还可以直接通过修改默认的配置达到自己想要的效果,接下来我们以修改默认的日期格式为例,找寻修改默认配置的方法

示例:修改默认日期格式

SpringBoot底层的自动装配,都在WebMvcAutoConfiguration自动配置类中,可以在其中找到关于格式化的方法mvcConversionService()

找到格式化转换器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {//获取配置文件中的格式化规则   Format format = this.mvcProperties.getFormat();WebConversionService conversionService = new WebConversionService(new DateTimeFormatters().dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));addFormatters(conversionService);return conversionService;
}

可以发现是从配置文件中获取格式化的规则,然后我们按住ctrl点击mvcProperties

private final WebMvcProperties mvcProperties;

然后点击进入WebMvcPropertieswebMVC的配置文件类,可以找到关于日期格式化的方法

可以看到我们可以通过spring.mvc.format.date在配置文件中设置自定义日期格式,但是已经不推荐使用了

@Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
public String getDateFormat() {return this.format.getDate();
}

我们再点击getDate方法

public String getDate() {return this.date;
}

再点击date

public static class Format {/*** Date format to use, for example `dd/MM/yyyy`.*/private String date;...
}

可以看到默认的日期格式为dd/MM/yyyy

我们可以在配置文件中修改默认的格式,自定义日期格式,比如这里为dd-MM-yyyy

spring.mvc.format.date=dd-MM-yyyy

如果配置了自己的格式化方式,就会注册到Bean中生效,以后就必须按照自定义的日期格式书写

其余的默认配置亦是如此,我们都可以在源码中找到答案


5. 总结

通过上述拓展原理以及示例,我们可以得出以下结论:

  • SpringBoot的底层,大量用到了上述设计细节思想,很多的自动配置,原理都相同;
  • 如果我们想自定义一些功能组件,只需要给Spring容器中添加这个组件,然后SpringBoot就会帮我们自动配置了
  • SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
  • 如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

三. 全面接管SpringMVC

1. 什么是全面接管?

全面接管:SpringBoot对SpringMVC的自动配置不再需要,所有东西都是我们自己去配置!

  • 实际开发中,并不推荐使用全面接管SpringMVC
  • 而是推荐拓展配置,使用SpringBoot的自动配置和我们自己写的扩展配置相结合的方式进行开发

在官方文档中可以看到:如果要完全控制Spring MVC

  • 可以添加用@EnableWebMvc注解的自己的@Configuration

  • 或者按照@EnableWebMvc的Javadoc中的说明添加自己的@Configuration注解的DelegatingWebMvcConfiguration

2. 测试

根据官方文档,我们在配置类上添加@EnableWebMvc注解即实现全面接管SpringMVC

package com.zsr.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.Locale;@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {//将自定义视图解析器实现类对象注入到bean中@Beanpublic ViewResolver myViewResolver() {return new MyViewResolver();}//自定义视图解析器实现类static class MyViewResolver implements ViewResolver {@Overridepublic View resolveViewName(String viewName, Locale locale) throws Exception {return null;}}
}

我们重启主程序进行测试,访问localhost:8080

可以看到先前配置的主页已经失效,所有都回归到了最初的样子


3. @EnableWebMvc原理

为什么加了这个注解,自动配置就失效了,我们来一探究竟~

我们查看@EnableWebMvc注解源码,发现导入了类DelegatingWebMvcConfiguration

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}

进入该类看看,发现它继承了一个父类WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {//...
}

也就是说,我们使用了@EnableWebMvc注解,就相当于导入了WebMvcConfigurationSupport

我们再查看Webmvc自动配置类WebMvcAutoConfiguration

可以这样一个注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

意思是:在WebMvcConfigurationSupport类不存在的情况下生效

也就是如果这个类存在,则整个WebMvcAutoConfiguration自动配置类会失效,即SpringBoot的自动配置全部失效

而我们导入@EnableWebMvc注解,就导入了WebMvcConfigurationSupport类,因此SpringBoot所有的自动配置失效

SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)相关推荐

  1. spring源码分析第四天------springmvc核心原理及源码分析

    spring源码分析第四天------springmvc核心原理及源码分析 1.基础知识普及 2. SpringMVC请求流程 3.SpringMVC代码流程 4.springMVC源码分析 4.1 ...

  2. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  3. SpringBoot-web开发(一): 静态资源的导入(源码分析)

    目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...

  4. Anbox源码分析(四)——Anbox渲染原理(源码分析)

    Anbox源码分析(四) 上篇文章我们从源码分析了一下Anbox是怎样一步步的准备了OpenGL ES的渲染环境的,这篇文章,我们继续分析Android的渲染指令是如何到达宿主机进行渲染的. 宿主机端 ...

  5. Jaca集合(四)Vector集合底层源码分析

    Vector的基本介绍: (1)Vector类的定义说明:我们进入源码界面进行查看: public class Vector<E>extends AbstractList<E> ...

  6. EOS智能合约开发(二十三)nodeos调用mongo_db_plugin源码分析

    前几篇文章,我们已经深入了解了mongodb,我们从mongodb安装配置,history_plugin与mongo_db_plugin区别做了深入探讨.今天我们从源码角度分析,nodeos是如何调用 ...

  7. Springboot+vue开发的图书借阅管理系统项目源码下载-P0029

    前言 图书借阅管理系统项目是基于SpringBoot+Vue技术开发而来,功能相对比较简单,分为两个角色即管理员和学生用户,核心业务功能就是图书的发布.借阅与归还,相比于一些复杂的系统,该项目具备简单 ...

  8. Springboot快速开发-书本信息管理系统(项目源码)

    [我后续会发一个资源包,里面是所有代码,数据库表设计也有,大学生可以直接用,导入数据库运行,再导入后端项目和前端项目,再去网页运行就好了,效果图下面有] 1.考核要求: 数据库:MYSQL5.7+ 后 ...

  9. springMvc的执行流程(源码分析)

    1.在springMvc中负责处理请求的类为DispatcherServlet,这个类与我们传统的Servlet是一样的.我们来看看它的继承图 2. 我们发现DispatcherServlet也继承了 ...

最新文章

  1. 【转载】JDBC连接各种数据库的字符串
  2. 【c++】13.必须用指针取值的情况.md
  3. SqlServer 导出指定表数据 生成Insert脚本
  4. 数据库开篇简介整体常识
  5. Gradle 之语言基础 Groovy
  6. [react-router] React-Router的实现原理是什么?
  7. Linux epoll 笔记(高并发事件处理机制)
  8. 【在虚拟环境下完美解决】1698: error: (-215:Assertion failed) empty() in function cv::CascadeClassifier
  9. BugkuCTF-PWN题pwn5-overflow2超详细讲解
  10. 双十一!!作为程序员的你该如何拥有个人服务器和域名呢?
  11. SQL工作笔记-达梦(MySQL)将一个模式(库)中的一个表迁入到其他模式(库)
  12. Oracle容灾数据库-恢复演练方案
  13. abaqus推荐用哪一版本的_ABAQUS推荐资料合集(一)
  14. CocoaPods 简易教程 Alamofire请求数据 Swift
  15. 【GPU结构与CUDA系列2】GPU硬件结构及架构分析:流多处理器SM,流处理器SP,示例架构分析
  16. Windows字体文件存放位置
  17. C++——最长公共子串
  18. 三合一收款二维码原理以及源码
  19. 杰理之如果再开蓝牙一拖二的话。手机连接样机时,会出现,无法连接【篇】
  20. c语言例题15:折半查找

热门文章

  1. 2022-2028年中国煤化工行业市场前景分析预测报告
  2. IDEA的Docker插件实战(Dockerfile篇)
  3. 2022-2028年中国椎间孔镜行业市场研究及前瞻分析报告
  4. centos 安装 node12以及yarn
  5. 互联网笔试各种主流语言在OJ上的的标准输入输出
  6. 107. Binary Tree Level Order Traversal II
  7. linux vi编辑 整理
  8. python究竟要不要使用多线程
  9. 汇编语言将数据、代码、栈放入不同段基础
  10. OFRecord 数据集加载