SpringBoot MVC配置

2023-05-16

SpringBoot MVC配置

在使用 SpringBoot 进行实际的项目开发前,最后再了解一下 SpringBoot 中对于 MVC 的配置!仍对应 SpringBoot-03-Web 项目。

1. MVC配置简介

SpringBoot 对 MVC 进行了许多的配置,查看官方文档可以看到其中的介绍

Spring MVC Auto-configuration
// Spring Boot 为 Spring MVC 提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在 Spring 默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含了视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径和 WebJars
Support for serving static resources, including support for WebJars 
// 自动注册了 Converter(转换器),作用是将网页提交到后台的数据封装为对象
// 如提交字符串"1" 就会转换为 int 类型的 1
// Formatte(格式化器),可以将如2021-9-1格式的数据格式化为 Date 对象
Automatic registration of Converter, GenericConverter, and Formatter beans.
// 提供了 HttpMessageConverters支持
// 它是 SpringMVC 用来转换 Http 请求和响应的的,如将一个User对象转换为JSON字符串(详细的请查看文档)
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:把请求数据绑定到 JavaBean 中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

接下来就分析一下其中的几个配置!

2. 视图解析器

2.1 配置

SpringBoot 对 MVC 中的视图解析器进行了自动配置,首先查看 Web 的自动配置类 WebMvcAutoConfiguration,在其中可以看到视图解析器的构造方法

@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    ...
    return resolver;
}

它创建了一个 ContentNegotiatingViewResolver 类型的视图解析器,点进这个类,首先可以看到它实现了 ViewResolver 接口,所以它就是一个视图解析器!

public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
		implements ViewResolver, Ordered, InitializingBean

再找到它关于视图解析的部分,是一个方法 resolveViewName,返回了一个视图

@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }

    String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
    if (this.useNotAcceptableStatusCode) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
        }

        return NOT_ACCEPTABLE_VIEW;
    } else {
        this.logger.debug("View remains unresolved" + mediaTypeInfo);
        return null;
    }
}

其中有两句关键的代码

// 获取所有候选视图
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择最好的视图(何谓最好?)
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);

查看获取所有候选视图方法 getCandidateViews,可以看到它遍历了所有的视图解析器,向候选视图列表中添加视图

List<View> candidateViews = new ArrayList<>();
...
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);
            }
        }
    }
}

这就是 SpringBoot 默认的关于视图解析器的配置!

2.2 自定义

此时我们先尝试自定义一个视图解析器,在 com.qiyuan 包下创建 config 包,并在其中创建 MyMvcConfig 类,添加 @Configuration 注解并实现 WebMvcConfigurer 接口,表明它是 WebMvc 的配置类

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
}

在其中创建一个静态内部类 MyViewResolver,实现 ViewResolver 接口,即是一个视图解析器;再使用 @Bean 将它作为组件放入容器中

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    // 自定义的视图解析器
    public static class MyViewResolver implements ViewResolver{

        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

这样我们自定义的视图解析器就放入 Spring 中了,运行的时候就会被检测到!

2.3 测试

查看完 SpringBoot 的视图解析器配置,并且有自定义了一个视图解析器的配置后,就可以来测试一下了。

首先在 SpringMVC 中我们知道,任何请求都要经过 DispatcherServlet,全局搜索这个类,并找到其中的核心方法 doDispatcher

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception

具体内容是什么不用关心,先在这个方法上打上断点,然后开始程序调试。访问 localhost:8080,程序进入了断点。此时查看 DispatcherServlet 类中的变量,可以看到 viewResolver

请添加图片描述
这里就出现了四个见过的面孔!ContentNegotiatingViewResolverBeanNameViewResolver 是 SpringBoot 中的 MVC 配置包含的,ThymeleafViewResolver 则是上节导入 Thymeleaf 而产生的,最后是 MyMvcConfig 中的( $ 符号应该是表示类中的对象)MyViewResolver ,我们自定义的视图解析器!

2.4 小结

从上面的测试中我们就可以知道,SpringBoot 中有默认的配置,它们会被自动装配为组件(如上面的0和 1);如果我们引入了别的配置,它也会被装配为一个组件(如上面的2,Thymeleaf );如果我们想自定义地为 SpringBoot 添加组件,只要写好对应的配置类,将这个组件放到 Spring 容器中,它也会自动地被装配(如上面的3,MyViewResolver )!

简而言之,如果我们想要进行一些功能的定制,只要给 Spring 容器添加这个功能的组件就行了!

3. 扩展MVC功能

现在来尝试扩展 MVC 的功能,在官方文档中,还有这么一段话

/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),
则可以添加自己的@configuration类,类型为webmvcconfigurer,但不添加@EnableWebMvc。
如果希望提供RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例,
则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
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.

即如果需要添加功能,即创建一个 WebMvcConfigurer 类并加上 @Configuration 注解,也就是上面创建视图解析器的过程,当然也可以创建拦截器、视图控制器等组件。

现在尝试创建一个视图控制器,在 MyMvcConfig 类中重写 addViewControllers 方法

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    ...

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 将 /test 请求定向到 hello 视图
        registry.addViewController("/test").setViewName("hello");
    }

}

这样就为我们的 MVC 配置添加了一个视图控制器,运行程序 ,访问 localhost:8080/test,成功进入之前的 hello.html 页面,当然因为没有设置模型,所以没有内容。

为什么我们写的配置类也会被加载呢?下面分析一下原理!

  1. 首先,Web 的自动配置需要 WebMvcAutoConfiguration 组件,它里面有一个内部类 WebMvcAutoConfigurationAdapter,即配置适配器类

    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    ...
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware
    
  2. 这个配置适配器类通过注解 @Import(EnableWebMvcConfiguration.class) 又导入了一个类,即开启配置类

    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties(WebProperties.class)
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware
    
  3. 这个开启配置类继承了一个父类 DelegatingWebMvcConfiguration,即删除配置类(?),它的开头就有这么一段代码

    @Configuration(proxyBeanMethods = false)
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
        // 从容器中获取所有 WebMvcConfigurer
    	@Autowired(required = false)
    	public void setConfigurers(List<WebMvcConfigurer> configurers) {
    		if (!CollectionUtils.isEmpty(configurers)) {
    			this.configurers.addWebMvcConfigurers(configurers);
    		}
    	}
        
        ... 
    }
    

    可以看到它获取并设置了所有的 WebMvcConfigurer 配置类!

  4. DelegatingWebMvcConfiguration 类中的 addViewControllers 方法为例(之前的例子就是添加了一个视图控制器)

    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
    	this.configurers.addViewControllers(registry);
    }
    
    
  5. 继续查看它调用的 addViewControllers 方法,跳转到了 WebMvcConfigurerComposite 类中的 addViewControllers 方法,这个类就是 3 中的 configurers 的类型!

    private final List<WebMvcConfigurer> delegates = new ArrayList<>();
    ...
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            // 调用了所有的 WebMvcConfigurer 配置,包括 Spring 自带的和我们自己配置的!
            delegate.addViewControllers(registry);
        }
    }
    

    这里就可以看到,所有的 WebMvcConfigurer 都会被调用加载,包括 SpringBoot 自带的和我们自己配置的!(这里有点乱,但先这样吧)

4. 全面接管MVC

在官方文档中还有一小段不起眼的话

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

意思就是,只要在自定义的配置类( @Configuration )上添加了 @EnableWebMvc,就可以由我们全面接管 SpringMVC!

如在 MyMvcConfig 类上加上 @EnableWebMvc 注解

@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
    ...
}

此时再运行程序,访问 localhost:8080(注意不是 /test 也不是 /hello,因为之前自定义了配置,所以这两个是能请求到的),就会产生 404 错误,因为默认的配置已经被屏蔽掉了!

为什么加了一个 @EnableWebMvc 注解自动配置就失效了呢?再分析一下原理!

  1. 点进 @EnableWebMvc 注解,可以看到它导入了删除配置类 DelegatingWebMvcConfiguration 的组件,这个类在上面也见过了!

    @Import(DelegatingWebMvcConfiguration.class)
    public @interface EnableWebMvc {
    }
    
  2. 点进 DelegatingWebMvcConfiguration 类,可以看到它也继承了一个父类 WebMvcConfigurationSupport

    @Configuration(proxyBeanMethods = false)
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    
  3. 这时回到最开始的配置类 WebMvcAutoConfiguration 上,可以看到生效场景的注解 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    ...
    public class WebMvcAutoConfiguration
    

    说明这个自动配置类只有在没有导入 WebMvcConfigurationSupport 类的场景下才会被自动装配!而使用 @EnableWebMvc 注解正是导入了这个类,导致了自动配置类的失效,回到了最原始的状态!

总而言之,@EnableWebMvc 引入了 WebMvcConfigurationSupport 类,而这个类只有 SpringMVC 最基本的支持!

5. 总结

这节又深入了解了 SpringBoot 中 MVC 的一些配置,以及如何去扩展 MVC 的配置,如拦截器、视图解析器、视图控制器等,这就是编写实际项目前的最后一块拼图😤!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot MVC配置 的相关文章

随机推荐

  • C语言中static修饰函数和变量用法

    static修饰函数 xff0c 局部变量和全局变量的用法 在c语言中static关键字可以修饰函数和变量 修饰变量又可以分为修饰全局变量和局部变量 static作用是限定变量的生命周期 xff0c 限定变量或函数的作用域 写在前面 xff
  • SOEM控制io超简洁程序

    SOEM控制io超简洁程序 我想用SOEM简单控制io模块 xff0c 因为我的io模块每个出入输出旁边都会有一个小灯 xff0c 所以这也算是点灯程序 xff0c 但是我看了例子并不知道怎么修改 xff0c 都说igh麻烦 xff0c 我
  • Deep Learning 最优化方法之Adam

    本文是Deep Learning 之 最优化方法系列文章的Adam方法 主要参考Deep Learning 一书 整个优化系列文章列表 xff1a Deep Learning 之 最优化方法 Deep Learning 最优化方法之SGD
  • SOEM控制伺服电机

    我只完成了pv模式 xff0c 对于csp模式我不知道是哪里出现了问题 xff0c 有知道的可以在下方评论 这个代码我的pv模式可以正常运行和控制电机 xff0c csp模式可以使能电机 xff0c 但是电机不转 span class to
  • c语言常用的打印/输出函数

    c语言中除了最开始接触的printf 函数 xff0c 还经常遇到其他函数 xff0c sprintf printk fprintf 等 1 xff0c printf 这个函数应该是用的最多的 xff0c 或者是最先接触的 xff0c 至少
  • stm32f103介绍

    完整学习一遍stm32开发板开发 xff0c 并打算坚持一直写笔记 这是第一课 xff0c stm32的介绍 1 什么是STM32 从字面意义来看 xff1a ST xff1a 意法半导体 xff0c 是一个公司的名字 M xff1a Mi
  • 数据结构之单链表操作

    数据结构 xff0c 单链表操作 xff0c 本来应该三年前就应该会的 xff0c 奈何上学的时候呼呼睡大觉 xff0c 最近看代码又接触到了 xff0c 花了几天时间自己重新写了一下 链表操作应该是基础的 xff0c 并且需要会的 xff
  • igh etherlab主站介绍

    一 xff0c 简单介绍 目前用的最多的开源ethercat主站是igh和soem xff0c igh主站功能更多 xff0c 结构较为复杂 xff1b soem功能相对没有那么完善 xff0c 实现更为简单一些 使用场景 xff1a 主站
  • U盘变小恢复方法

    在使用中经常由于使用不当 xff0c 导致u盘空间变小 xff0c 比如像我现在的情况 xff0c 我本来u盘是32G的 xff0c 现在显示只有三个多G xff0c 格式化之后还是这样 解决步骤如下 xff0c 不需要下载工具 1 xff
  • VirtualBox 中运行 CentOS 7 鼠标切换

    在 VirtualBox 中运行 CentOS 7 虚拟机时 xff0c 有时鼠标可能会被捕捉 xff0c 导致无法在虚拟机和主机之间切换 以下是如何在 VirtualBox 中实现与 CentOS 7 鼠标切换的步骤 xff1a 首先 x
  • C++11 生产者消费者模型

    C 43 43 11 生产者消费者模型 线程互斥 lock guard 使用lock guard管理互斥锁 在退出作用域后进行析构时就会自动解锁 xff0c 从而保证了互斥量的正确操作 xff0c 避免忘记 unlock 操作而导致线程死锁
  • PS照片处理尺寸参考表

    参考表 一 讲多少寸 xff0c 是指长边的英寸数 xff0c 比如5 x 3 5就是5寸 讲多少R xff0c 指短边的英寸数 xff0c 比如4R是6 X 4寸 xff0c 而3R就是5寸的5 X 3 5寸 R 的意思的 rectang
  • 数据库习题及答案5

    模拟测验1 一 1 2 3 4 5 6 7 8 9 10 A D C c D A C A A C 一 选择题 xff08 在每个小题四个备选答案中选出一个正确答案 xff0c 填在题末的括号中 xff09 xff08 本大题共10小题 xf
  • Attention Model(mechanism) 的 套路

    最近刷了一些attention相关的paper 照着here的列表 43 自己搜的paper xff0c 网上相关的资料也有很多 xff0c 在此只讲一讲自己对于attention的理解 xff0c 力求做到简洁明了 一 attention
  • springMVC常用注解

    在java框架中 xff0c 使用注解的作用就是注入属性 一 Spring常用注解 64 Component xff1a 标注一个普通的Spring Bean类 64 Controller xff1a 标注一个控制器组件类 64 Servi
  • Ubuntu16.04运行.sh文件

    前言 xff1a 最近在学 Linux内核分析 xff0c 实验做的是哈工大的oslab Linux 0 11 xff0c 然后下载了相应的压缩包 解压之后发现需要运行setup sh文件 xff0c 原先以为是因为没有切换到root命令所
  • 服务器conda,pip命令用不了解决方法

    服务器创建用户后 xff0c 不知道为啥基本命令可以用 xff0c 但是conda xff0c pip等不能使用 xff0c 度娘后一行命令解决 xff0c 命令如下 source span class token operator spa
  • Base64资源

    Base64资源 在线转Base64工具 http www jsons cn img2base64 鲸鱼 maskImage src 61 39 data image png base64 iVBORw0KGgoAAAANSUhEUgAAA
  • Linux驱动开发

    本文为一个简单的字符设备驱动 xff0c 涉及驱动编写 测试程序编写 Makefile编写 驱动加载 卸载 xff0c 运行于Linux虚拟机 xff0c 不涉及底层配置 撰写本文的主要目的为记录一下驱动的开发流程 xff0c 参考了正点原
  • SpringBoot MVC配置

    SpringBoot MVC配置 在使用 SpringBoot 进行实际的项目开发前 xff0c 最后再了解一下 SpringBoot 中对于 MVC 的配置 xff01 仍对应 SpringBoot 03 Web 项目 1 MVC配置简介