和开振学Spring boot 3.0之Spring MVC:③Spring MVC的配置

我们前面两篇做了基本的开发,相信大家对Spring MVC的流程有了基本的了解,这些我们来确认一下一些细节。

1、Spring MVC是如何初始化的

在Servlet 3.0规范中,web.xml再也不是一个必需的配置文件。为了适应这个规范,Spring MVC从3.1版本开始也进行了支持,也就是我们已经不再需要通过任何的XML去配置Spring MVC的运行了。为了支持对于Spring MVC的配置,Spring提供了接口WebMvcConfigurer,其大部分方法都是default类型的空实现,这样开发者只需要实现这个接口,重写需要自定义的方法即可,这样就很方便进行开发了。在Spring Boot中,自定义是通过配置类WebMvcAutoConfiguration定义的,它有一个静态的内部类WebMvcAutoConfigurationAdapter,通过它Spring Boot就可以自定义配置Spring MVC的初始化,它们之间的关系如下图所示。

图1 Spring MVC的配置器

这里我们可以看到 WebMvcAutoConfigurationAdapter的源码,如下。

@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

		private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);

		private final Resources resourceProperties;

		private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

		private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;

		private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;

		private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

		private ServletContext servletContext;

		public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = webProperties.getResources();
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}

		@Override
		public void setServletContext(ServletContext servletContext) {
			this.servletContext = servletContext;
		}

		@Override
		public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
			this.messageConvertersProvider
				.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
		}

		@Override
		public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
			if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
				Object taskExecutor = this.beanFactory
					.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
				if (taskExecutor instanceof AsyncTaskExecutor asyncTaskExecutor) {
					configurer.setTaskExecutor(asyncTaskExecutor);
				}
			}
			Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
			if (timeout != null) {
				configurer.setDefaultTimeout(timeout.toMillis());
			}
		}

		@Override
		public void configurePathMatch(PathMatchConfigurer configurer) {
			if (this.mvcProperties.getPathmatch()
				.getMatchingStrategy() == WebMvcProperties.MatchingStrategy.ANT_PATH_MATCHER) {
				configurer.setPathMatcher(new AntPathMatcher());
				this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
					String servletUrlMapping = dispatcherPath.getServletUrlMapping();
					if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
						UrlPathHelper urlPathHelper = new UrlPathHelper();
						urlPathHelper.setAlwaysUseFullPath(true);
						configurer.setUrlPathHelper(urlPathHelper);
					}
				});
			}
		}

		private boolean singleDispatcherServlet() {
			return this.servletRegistrations.stream()
				.map(ServletRegistrationBean::getServlet)
				.filter(DispatcherServlet.class::isInstance)
				.count() == 1;
		}

		@Override
		public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
			WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
			configurer.favorParameter(contentnegotiation.isFavorParameter());
			if (contentnegotiation.getParameterName() != null) {
				configurer.parameterName(contentnegotiation.getParameterName());
			}
			Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
			mediaTypes.forEach(configurer::mediaType);
		}

		@Bean
		@ConditionalOnMissingBean
		public InternalResourceViewResolver defaultViewResolver() {
			InternalResourceViewResolver resolver = new InternalResourceViewResolver();
			resolver.setPrefix(this.mvcProperties.getView().getPrefix());
			resolver.setSuffix(this.mvcProperties.getView().getSuffix());
			return resolver;
		}

		@Bean
		@ConditionalOnBean(View.class)
		@ConditionalOnMissingBean
		public BeanNameViewResolver beanNameViewResolver() {
			BeanNameViewResolver resolver = new BeanNameViewResolver();
			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
			return resolver;
		}

		@Bean
		@ConditionalOnBean(ViewResolver.class)
		@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
		public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
			ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
			resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
			// ContentNegotiatingViewResolver uses all the other view resolvers to locate
			// a view so it should have a high precedence
			resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
			return resolver;
		}

		@Override
		public MessageCodesResolver getMessageCodesResolver() {
			if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
				DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
				resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
				return resolver;
			}
			return null;
		}

		@Override
		public void addFormatters(FormatterRegistry registry) {
			ApplicationConversionService.addBeans(registry, this.beanFactory);
		}

		@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
					"classpath:/META-INF/resources/webjars/");
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (this.servletContext != null) {
					ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
					registration.addResourceLocations(resource);
				}
			});
		}

		private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
			addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
		}

		private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
				Consumer<ResourceHandlerRegistration> customizer) {
			if (registry.hasMappingForPattern(pattern)) {
				return;
			}
			ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
			customizer.accept(registration);
			registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
			registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
			registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
			customizeResourceHandlerRegistration(registration);
		}

		private Integer getSeconds(Duration cachePeriod) {
			return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
		}

		private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
			if (this.resourceHandlerRegistrationCustomizer != null) {
				this.resourceHandlerRegistrationCustomizer.customize(registration);
			}
		}

		@Bean
		@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
		@ConditionalOnMissingFilterBean(RequestContextFilter.class)
		public static RequestContextFilter requestContextFilter() {
			return new OrderedRequestContextFilter();
		}

	}

你可以看到很多组件的初始化就在这里。

如果我们以后需要定制也可以继承WebMvcConfigurer对相关组件进行初始化,关于这点,我们未来会看到。

2、在Spring MVC中,我们能配置些什么??

看回上节的代码,里面存在两个配置文件WebMvcProperties和WebProperties,WebMvcProperties主要配置MVC的内容,而WebProperties主要配置网页资源的内容。

下面是Spring MVC常见的配置项(可以参考文件WebMvcProperties),如下:

# SPRING MVC (WebMvcProperties)
# 异步请求超时时间(单位为毫秒)
spring.mvc.async.request-timeout=
#  是否使用请求参数(默认参数为"format")来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-parameter=false 
# 是否使用URL中的路径扩展来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-path-extension=false 
# 设置内容协商向媒体类型映射文件扩展名。例如,YML文本/YAML
spring.mvc.contentnegotiation.media-types.*= 
# 当启用favor-parameter参数是,自定义参数名
spring.mvc.contentnegotiation.parameter-name= 
# 日期格式配置,如yyyy-MM-dd
spring.mvc.date-format=
# 是否让FrameworkServlet doService()方法支持TRACE请求
spring.mvc.dispatch-trace-request=false 
# 是否启用 FrameworkServlet doService 方法支持OPTIONS请求
spring.mvc.dispatch-options-request=true 
# spring MVC的图标是否启用
spring.mvc.favicon.enabled=true 
# Servlet规范要求表格数据可用于HTTP POST而不是HTTP PUT或PATCH请求,这个选项将使得过滤器拦截
# HTTP PUT和PATCH,且内容类型是application/x-www-form-urlencoded的请求,
# 并且将其转换为POST请求
spring.mvc.formcontent.putfilter.enabled=true 
# 如果配置为default,那么它将忽略模型重定向的场景
spring.mvc.ignore-default-model-on-redirect=true 
# 默认国际化选项,默认取Accept-Language
spring.mvc.locale=
# 国际化解析器,如果需要固定可以使用fixed
spring.mvc.locale-resolver=accept-header
# 是否启用警告日志异常解决
spring.mvc.log-resolved-exception=false
# 消息代码的格式化策略。例如,' prefix_error_code '
spring.mvc.message-codes-resolver-format=  
# 是否对spring.mvc.contentnegotiation.media-types.*注册的扩展采用后缀模式匹配
spring.mvc.pathmatch.use-registered-suffix-pattern=false 
# 当匹配模式到请求时,是否使用后缀模式匹配(.*)
spring.mvc.pathmatch.use-suffix-pattern=false
# 启用Spring Web服务Serlvet的优先顺序配置
spring.mvc.servlet.load-on-startup=-1  
# 指定静态资源路径
spring.mvc.static-path-pattern=/**
# 如果请求找不到处理器,是否抛出 NoHandlerFoundException异常
spring.mvc.throw-exception-if-no-handler-found=false 
# Spring MVC视图前缀                         
spring.mvc.view.prefix=
# Spring MVC视图后缀
spring.mvc.view.suffix=    

# Thymeleaf模板常用配置项
# 是否启用Thymeleaf模板机制
spring.thymeleaf.enabled=true
# Thymeleaf模板前缀
spring.thymeleaf.prefix=classpath:/templates/
# Thymeleaf模板后缀名
spring.thymeleaf.suffix=.html

3、DispatcherServlet的流程源码

好了,这里配置也谈到了,下面我们来探访DispatcherServlet的源码,主要是流程的代码,也就是doDispatch()方法,我们不妨尝试看看它。

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 将HttpServletRequest转换为MultipartHttpServletRequest,一种处理文件上传的请求
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // 找到对应的处理器,也就是HandlerMapping机制
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) { // 找不到处理器的处理
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 找到对应的HandlerAdapter准备执行(未执行)处理器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // 请求头处理
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            // 拦截器前置方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 使用HandlerAdapter执行处理器
            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            // 视图解析器,定位视图
            applyDefaultViewName(processedRequest, mv);
            // 拦截器后置方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new ServletException("Handler dispatch failed: " + err, err);
        }
        // 处理结果,主要是视图渲染
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // 执行拦截器完成方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        // 执行拦截器完成方法
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new ServletException("Handler processing failed: " + err, err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                // 执行拦截器完成方法
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

这个方法已经标注了@SuppressWarnings("deprecation"),说明将来会被取代掉。

我在这个方法中只是添加了中文注释,目的是让你更好的查看源码,请依据我的注释看回Spring MVC流程图,相信你会有更深的认识。

Spring MVC流程图

到这里,基础的Spring MVC,我就讲述完成了,下面我们需要在深入细节去讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/6738.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】七种常见的排序

目录 1、排序的概念即运用 1.1、排序的概念 1.2、常见排序算法的分类 2、插入排序 2.1、排序原理 2.2、直接插入排序 2.3、希尔排序&#xff08;缩小增量排序&#xff09; 3、选择排序 3.1、直接选择排序 3.2、堆排序 4、选择排序 4.1、冒泡排序 4.2、快速排序 …

Android---Jetpack之Room

目录 应用实现 数据库升级 异常处理 Schema 文件 销毁和重建策略 预填充数据库 Android 采用 SQLite 作为数据库存储&#xff0c;开源社区常见的 ORM(Object Relational Mapping)库有ORMLite、GreenDAO等。Room 和其它库一样&#xff0c;也是在 SQLite 上提供了一层封装。…

一文懂交叉熵Cross-Entropy

本文翻译自https://naokishibuya.medium.com/demystifying-cross-entropy-e80e3ad54a8 交叉熵由交叉&#xff08;Cross&#xff09;和熵&#xff08;Entropy&#xff09;两部分组成&#xff0c;在机器学习中常常被定义为损失函数的目标。在二分类任务中&#xff0c;更有二分类交…

QT学习笔记(智能家居物联网项目实验2)

物联网项目综合测试 打开 4/01_smarthome/01_smarthome/01_smarthome.pro 项目&#xff0c;此项目为智能家居物联网 UI 界面控制端。 打开 4/01_smarthome/esp8266/esp8266.pro 项目&#xff0c;此项目设备端&#xff08;被控端&#xff09;。 打开上面两个项目如下。 项…

ToBeWritten之MOST协议、Flex Rat总线、车载以太网

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…

C/C++每日一练(20230402)

目录 1. 找最大数和最小数 ※ 2. 数组排序 ※ 3. 按要求完成数据的输入输出 ※ &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 标注 ※ 为入门基础题&#xff0c;今天什么好日子CSDN…

一个有完整业务连的淘宝API接口

支持的业务类型 1、卖家平台&#xff08;包括淘宝网&#xff0c;天猫等&#xff09;&#xff1a;搜索、店铺信息维护、交易订单处理、发货管理、数据查询与统计分析。 2、买家平台&#xff08;包括淘宝&#xff0c;天猫等&#xff09;&#xff1a;搜索&#xff0c;发布信息&a…

银行数字化转型导师坚鹏:金融科技如何赋能银行数字化转型

金融科技如何赋能银行数字化转型课程背景&#xff1a; 数字化背景下&#xff0c;很多银行存在以下问题&#xff1a; 不清楚5G如何赋能银行数字化转型&#xff1f; 不清楚金融科技如何赋能银行数字化转型&#xff1f; 不了解银行数字化转型标杆成功案例&#xff1f; 课程特色…

旅游市场迎来“开门红”,VR云游带来全新体验

旅游业是一个充满活力和吸引力的行业&#xff0c;可以促进当地经济发展和提高生活水平。在清明时节&#xff0c;春暖花开&#xff0c;各地旅游市场正在回暖&#xff0c;而各大景区也纷纷推出了优惠措施&#xff0c;吸引大批的游客前来游玩&#xff0c;旅游市场迎来了“开门红”…

ServletAPI的使用

目录 一、HttpServlet 1.1 HttpServlet的核心方法 1.2 Servlet的生命周期 1.3 代码示例&#xff1a;通过postman来发送请求 1.4 代码示例&#xff1a;通过ajax来发送请求 二、HttpServletRequest 2.1 代码示例&#xff1a;打印请求信息&#xff1a; 2.2 代码示例&#…

强化学习——初探强化学习

本文引自&#xff1a;《 动手学强化学习 》 第 1 章 初探强化学习 1.1 简介 亲爱的读者&#xff0c;欢迎来到强化学习的世界。初探强化学习&#xff0c;你是否充满了好奇和期待呢&#xff1f;我们想说&#xff0c;首先感谢你的选择&#xff0c;学习本书不仅能够帮助你理解强…

COI实验室技能:python控制相机的方法——采集、处理、显示、实时

COI实验室技能&#xff1a;python控制相机的方法——采集、处理、显示、实时本文介绍如何利用python控制办公摄像头、工业相机和科研相机。将数据采集和处理统一到python代码中。   主要围绕解决采用什么库、掌握这个库的控制相机方法(参数配置、读取数据等等)、结合自己的算…

Go 反射

目录 什么是反射 反射的弊端 reflect 包 Go 提供的反射方法 type Type 类型 type Kind 类型 TypeOf ValueOf 什么是反射 ​反射&#xff08;reflection&#xff09;是在 Java 出现后迅速流行起来的一种概念&#xff0c;通过反射可以获取丰富的类型信息&#xff0c;并可…

实战!项目管理实施过程的五大难点和痛点

作为一个在项目摸爬滚打十余年的管理人员&#xff0c;对项目管理的难点和痛点深有体会&#xff0c;这就结合我自身体验来说一说。 我认为&#xff0c;项目管理实施中的难点和痛点其实可以归结为两类&#xff1a;一类是对于项目任务本身&#xff0c;另一类则涉及到团队内部的管…

2023年,转行学Java还是web前端?

2023年要想顺利入行IT建议选择Java。 理由很简单&#xff0c;前端开发岗位需求大量减少&#xff0c;大厂裁员导致大量有经验的前端开发人员或者初级后端开发人员流入就业市场&#xff1b;作为新人缺乏技能优势和项目优势&#xff0c;而用人单位也更愿意招聘熟手&#xff0c;或…

Python 自动化指南(繁琐工作自动化)第二版:八、输入验证

原文&#xff1a;https://automatetheboringstuff.com/2e/chapter8/ 输入验证代码检查用户输入的值&#xff0c;比如来自input()函数的文本&#xff0c;格式是否正确。例如&#xff0c;如果您希望用户输入他们的年龄&#xff0c;您的代码不应该接受无意义的答案&#xff0c;如负…

chatgpt大模型赋能人形机器人之我见

我个人的看法&#xff08;不涉及任何和他项目相关的细节或商业机密&#xff0c;仅仅是我个人的泛泛而谈&#xff09;&#xff1a; 1、从大趋势来说&#xff0c;人形机器人的灵活度和通用性确实非常的高&#xff0c;是有前景的。另外轮式足式也不是他独一例&#xff0c;像 ETH …

【Redis学习】Redis管道

理论简介 问题由来 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果)&#xff0c;并监听Socket返回&#xff0c;通常以阻塞模式等待服务端响应。 服务端处理命令&#xff0c;并将结果返回给客户端。 上述两步称为:Round Trip Time(简称RTT,数据包往返…
最新文章