Day29——SpringMVC自动配置原理

本文详细解析SpringBoot中SpringMVC的自动配置原理,包括视图解析、静态资源处理、类型转换、消息转换及数据绑定等关键组件的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 知识储备

了解SpringBoot的SpringMVC自动配置原理可以从官方文档和源码下手。如下:

在这里插入图片描述
下面就可以见到SpringBoot默认配置的SpringMVC
在这里插入图片描述

1.1 ContentNegotiatingViewResolver & BeanNameViewResolver

这两个的作用是自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象,视图对象决定如何渲染(即转发或重定向)

ContentNegotiatingViewResolver作用:组合所有的视图解析器

分析:

我们在idea搜索WebMvcAutoConfiguration,如下:
在这里插入图片描述
在WebMvcAutoConfiguration中搜索ContentNegotiatingViewResolver,看到有如下方法:
在这里插入图片描述
这个方法里面返回一个ContentNegotiatingViewResolver类型的对象,我们点进去这个ContentNegotiatingViewResolver类是干了什么的?,如下:
在这里插入图片描述
既然是视图解析器(ViewResolver),那么它是怎么解析视图的?我们在ContentNegotiatingViewResolver中找到解析视图的这个方法resolveViewName(),如下:

@Override
	@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 = 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;
		}
	}

可以看到有3处return语句,显然主要起作用的是第一处的return语句,所以我们看它return的是什么?return的是一个View,这个View通过getBestView()获取的,而getBestView()根据candidateViews, requestedMediaTypes, attrs获取,显然requestedMediaTypes,attrs是请求参数之类的,我们仔细看candidateViews。candidateViews是通过getCandidateViews获取的。大概意思是获取候选视图对象。我们点击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;
	}

看到上面的方法中,拿到所有的视图对象进行挨个解析

总结:所以ContentNegotiatingViewResolver的作用是组合所有的视图解析器。

疑问:这些所有的视图解析器它是从哪获得的?
答:在ContentNegotiatingViewResolver里面有一个initServletContext()方法,就是这个方法从BeanFactoryUtils中获取所有的视图解析器。如下:

@Override
	protected void initServletContext(ServletContext servletContext) {
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
		if (this.viewResolvers == null) {
			this.viewResolvers = new ArrayList<>(matchingBeans.size());
			for (ViewResolver viewResolver : matchingBeans) {
				if (this != viewResolver) {
					this.viewResolvers.add(viewResolver);
				}
			}
		}
		else {
			for (int i = 0; i < this.viewResolvers.size(); i++) {
				ViewResolver vr = this.viewResolvers.get(i);
				if (matchingBeans.contains(vr)) {
					continue;
				}
				String name = vr.getClass().getName() + i;
				obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
			}

		}
		AnnotationAwareOrderComparator.sort(this.viewResolvers);
		this.cnmFactoryBean.setServletContext(servletContext);
	}

总结:从上面的结论ContentNegotiatingViewResolver组合所有的视图解析器,ContentNegotiatingViewResolver通过initServletContext()方法扫描所有视图解析器(即ViewResolver),进而组合所有视图解析器 所以我们定制视图解析器,只需在容器中添加一个视图解析器,ContentNegotiatingViewResolver自动将其组合进来。 详情看自己定制一个简单的ViewResolver视图解析器

1.2 自动配置相关的静态资源信息

有如下:

  1. 静态资源文件夹,webjars
  2. index.html静态首页访问
  3. 网页图标favicon.ico

1.3 Converter GenericConverter Formatter

1.3.1 Converter 转换器

比如有如下controller方法

public String addUser(User user){
...
}

解释: 假如浏览器传来的参数有一个是文本类型的18,假如这个18对应user的age(Integer类型),则Converter会将类型自动转换,将文本类型转为Integer类型。

1.3.2 Formatter 格式化器

有时日期的格式有2020/12/12或者2020.12.12或者2020-12-12,则我们需要将日期转换为一定的格式:
日期==》Date

在WebMvcAutoConfiguration中有注册格式化日期的方法,在WebMvcAutoConfiguration中搜getDateFormat,搜到如下方法:

	@Bean
		@Override
		public FormattingConversionService mvcConversionService() {
			WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
			addFormatters(conversionService);
			return conversionService;
		}

getDateFormat()方法是来自this.mvcProperties,我们查看它的类里面有什么,按ctrl+单击 点进去,来到这里

private final WebMvcProperties mvcProperties;

再继续点进去看看这个类里面有什么,如下:

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
...
/**
	 * Date format to use. For instance, `dd/MM/yyyy`.
	 */
	private String dateFormat;
	...

所以我们可以在properties文件指定日期格式化的规则spring.mvc.dateFormat=xxx,这样就设置了日期格式。

自己添加的格式化器转换器,我们只需放在容器中即可。

1.4 HttpMessageConverters

消息转换器:SpringMVC用来转换请求和响应的,比如我要将User对象以json的格式响应

在WebMvcAutoConfiguration中搜索HttpMessageConverters,可以看到只有一个构造方法的形参中有一个是HttpMessageConverters,还看到有一个泛型为HttpMessageConverters的变量。如下:

private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
		}

如果一个类只有一个有参的构造方法,那么构造方法里面的参数需要从容器中获取。所以HttpMessageConverters参数需要从容器获取。我们点进去看看HttpMessageConverters类型里面有什么,如下:

public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
		this(Arrays.asList(additionalConverters));
	}

public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
		this(true, additionalConverters);
	}

可以看到有两个构造方法的形参分别是HttpMessageConverter消息转换器的可变数组或可变集合。即要从容器中获取所有的消息转换器

自己要给容器中添加HttpMessageConverter,只需要将自己的组件注册到容器中。(如使用@Bean或@Component)

1.5 MessageCodesResolver

我们在WebMvcAutoConfiguration中搜索MessageCodesResolver,可以看到如下方法:

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

可以看到if条件中有this.mvcProperties.getMessageCodesResolverFormat(),点击getMessageCodesResolverFormat()看这个方法,如下:

public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() {
		return this.messageCodesResolverFormat;
	}

再点击this.messageCodesResolverFormat中,看看是什么,如下:

private DefaultMessageCodesResolver.Format messageCodesResolverFormat;

点击进去看看Format里面是什么,如下:

 public static enum Format implements MessageCodeFormatter {
        PREFIX_ERROR_CODE {
            public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
                return toDelimitedString(new String[]{errorCode, objectName, field});
            }
        },
        POSTFIX_ERROR_CODE {
            public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
                return toDelimitedString(new String[]{objectName, field, errorCode});
            }
        };

至此,我们看到这个MessageCodesResolver是用来定义错误代码的规则的,默认的有两个规则,如下:

errorCode, objectName, field //第一种
objectName, field, errorCode //第二种

总结:MessageCodesResolver是用来定义错误代码规则的

1.6 ConfiguableWebBindingInitializer

在WebMvcAutoConfiguration中搜索ConfigurableWebBindingInitializer,如下:

@Override
		protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
				FormattingConversionService mvcConversionService, Validator mvcValidator) {
			try {
				return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
			}
			catch (NoSuchBeanDefinitionException ex) {
				return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
			}
		}

看到第一个return是从beanFactory中获取Bean,所以 我们可以配置一个ConfigurableWebBindingInitializer来替换默认的,并添加到容器中。

第二个return是当出现NoSuchBeanDefinitionException,从父类中获取ConfigurableWebBindingInitializer,我们点进去看看是什么内容,如下:

/**
	 * Return the {@link ConfigurableWebBindingInitializer} to use for
	 * initializing all {@link WebDataBinder} instances.
	 */
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
			FormattingConversionService mvcConversionService, Validator mvcValidator) {

		ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
		initializer.setConversionService(mvcConversionService);
		initializer.setValidator(mvcValidator);
		MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
		if (messageCodesResolver != null) {
			initializer.setMessageCodesResolver(messageCodesResolver);
		}
		return initializer;
	}

可以从注释里面看到WebDataBinder,即ConfigurableWebBindingInitializer是用来初始化WebDataBinder(绑定器),这方法是在初始化一个ConfigurableWebBindingInitializer

ConfigurableWebBindingInitializer中有一个具体的initBinder()的方法

public void initBinder(WebDataBinder binder) {
        binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
        if (this.directFieldAccess) {
            binder.initDirectFieldAccess();
        }

        if (this.messageCodesResolver != null) {
            binder.setMessageCodesResolver(this.messageCodesResolver);
        }

        if (this.bindingErrorProcessor != null) {
            binder.setBindingErrorProcessor(this.bindingErrorProcessor);
        }

        if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {
            binder.setValidator(this.validator);
        }

        if (this.conversionService != null) {
            binder.setConversionService(this.conversionService);
        }

        if (this.propertyEditorRegistrars != null) {
            PropertyEditorRegistrar[] var2 = this.propertyEditorRegistrars;
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                PropertyEditorRegistrar propertyEditorRegistrar = var2[var4];
                propertyEditorRegistrar.registerCustomEditors(binder);
            }
        }

    }

总结:ConfigurableWebBindingInitializer作用是用来将请求数据转换成JavaBean(将请求参数与controller方法中的Bean形参作数据绑定,而这个绑定又需要用到前面介绍的Converters类型转换器、Formatter格式化等等)

1.7 总结

SpringBoot中不仅仅只有以上的这些SpringMVC自动配置,还有很多自动配置。可以在依赖中查看到。如下:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
这个org.springframework.boot.autoconfigure.web里面有web的自动场景,通常是那些xxxAutotConfiguration

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值