和开振学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,我就讲述完成了,下面我们需要在深入细节去讨论。

猜你喜欢

转载自blog.csdn.net/ykzhen2015/article/details/129931924