04.Spring Boot 之 MVC 装配原理

本文详细解析了SpringMVC在SpringBoot环境下的自动装配原理,包括WebMvcAutoConfiguration的作用,静态资源、欢迎页及图标配置,以及ContentNegotiatingViewResolver、FormattingConversionService等组件的工作机制。同时,介绍了如何通过实现WebMvcConfigurer接口来自定义MVC组件,以及如何完全禁用SpringMVC的自动装配。

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

1. Spring MVC 自动装配

1.1 WebMvcAutoConfiguration 装配原理

前文已经分析过,@EnableAutoConfiguration 这个注解会给容器中导入 AutoConfigurationImportSelector 组件,AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,所以这里会延时导入,之前版本没有实现这个接口,那么就会调用 selectImports() 方法,实现了就会调用 AutoConfigurationGroup#process() 方法,这个方法会把 META-INF\spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 所对应的类导入到 Spring 容器中,org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration 也是其中之一

Spring Boot 自动装配组件的条件(适用于大多数组件):先判断容器中是否有这个组件,如果没有就会加入一个默认的组件,如果有(即用户自定义了)就不会添加;如果这个组件允许有多个,那么就会将用户配置的和默认的组合起来使用,而且一般都会提供 customize() 方法

可以使用 debug=true 属性来让控制台打印自动配置报告,这样就可以知道哪些自动配置类生效

1.2 静态资源访问
1.2.1 静态资源文件夹映射

webjars 就是以 jar 包的方式引入静态资源,可以参考 https://www.webjars.org/

所有 /webjars/** 请求都去 classpath:/META-INF/resources/webjars/ 找资源

/** 访问当前项目的任何资源,都去静态资源的文件夹找映射,有 5 个静态资源路径:classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/// 表示当前项目根路径)

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	
	...
	
	// 映射 webjars 资源路径
	Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	CacheControl cacheControl = this.resourceProperties.getCache()
			.getCachecontrol().toHttpCacheControl();
	if (!registry.hasMappingForPattern("/webjars/**")) {
		customizeResourceHandlerRegistration(registry
				.addResourceHandler("/webjars/**")
				.addResourceLocations("classpath:/META-INF/resources/webjars/")
				.setCachePeriod(getSeconds(cachePeriod))
				.setCacheControl(cacheControl));
	}
	
	// 映射静态资源路径,staticPathPattern 是 /**
	// getResourceLocations() 包含了 5 个路径:"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/""/"(表示当前项目根路径)
	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
	if (!registry.hasMappingForPattern(staticPathPattern)) {
		customizeResourceHandlerRegistration(
				registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(
								this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod))
						.setCacheControl(cacheControl));
	}
}
1.2.2 配置欢迎页映射

静态资源文件夹下的所有 index.html 页面;被 /** 映射

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
		ApplicationContext applicationContext) {
	return new WelcomePageHandlerMapping(
			new TemplateAvailabilityProviders(applicationContext),
			applicationContext, getWelcomePage(),
			this.mvcProperties.getStaticPathPattern());
}
1.2.3 配置网站图标

所有的 **/favicon.ico 都是在静态资源文件下找

@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled",
		matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {

	private final ResourceProperties resourceProperties;

	private ResourceLoader resourceLoader;

	public FaviconConfiguration(ResourceProperties resourceProperties) {
		this.resourceProperties = resourceProperties;
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@Bean
	public SimpleUrlHandlerMapping faviconHandlerMapping() {
		SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
		mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
		mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
				faviconRequestHandler()));
		return mapping;
	}

	@Bean
	public ResourceHttpRequestHandler faviconRequestHandler() {
		ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
		requestHandler.setLocations(resolveFaviconLocations());
		return requestHandler;
	}

	private List<Resource> resolveFaviconLocations() {
		String[] staticLocations = getResourceLocations(
				this.resourceProperties.getStaticLocations());
		List<Resource> locations = new ArrayList<>(staticLocations.length + 1);
		Arrays.stream(staticLocations).map(this.resourceLoader::getResource)
				.forEach(locations::add);
		locations.add(new ClassPathResource("/"));
		return Collections.unmodifiableList(locations);
	}

}
1.3 Spring MVC 提供的自动配置组件
1.3.1 ContentNegotiatingViewResolver

它包含了所有的视图解析器,Spring 会自动选择一个最佳的视图解析器解析,如果想定制自己的视图解析器,只需要往容器中注入一个 ViewResolver 组件即可

@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;
}
1.3.2 FormattingConversionService

this.mvcProperties.getDateFormat() 添加了一个时间转换器,可以用 spring.mvc.dateFormat 控制

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

addFormatters 方法会调用如下方法,所以想添加自定义的格式化器和转换器,只需要放在容器中即可

public void addFormatters(FormatterRegistry registry) {
	for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
		registry.addConverter(converter);
	}
	for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
		registry.addConverter(converter);
	}
	for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
		registry.addFormatter(formatter);
	}
}
1.3.3 WebMvcAutoConfigurationAdapter

注意这个方法只有一个有参构造器,所以它的构造器参数都是来源于容器中,所以我们可以定制自己的 HttpMessageConverter,然后放到容器中即可

@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
		implements WebMvcConfigurer, ResourceLoaderAware {

	...

	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();
	}

2. Spring MVC 组件扩展

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-boot-core/tutorial-spring-boot-web 工程

编写一个配置类(@Configuration),是 WebMvcConfigurer 类型,不能标注 @EnableWebMvc

2.1 环境搭建
1. CustomSpringMvcConfig

通过实现 WebMvcConfigurer 接口,可以定制大量的 MVC 组件

@Configuration
class CustomSpringMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
    }

}
2.2 原理分析
1. WebMvcAutoConfigurationAdapter

因为 WebMvcAutoConfiguration 中的静态 WebMvcAutoConfigurationAdapter 类标注了注解 @Import(EnableWebMvcConfiguration.class),即向容器中导入了 EnableWebMvcConfiguration 组件

@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
		implements WebMvcConfigurer, ResourceLoaderAware {
2. EnableWebMvcConfiguration

EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration

@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
3. DelegatingWebMvcConfiguration

它会将所有的 WebMvcConfigurer 相关的配置一起调用

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

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

3. Spring MVC 禁用自动装配

Spring Boot 对 Spring MVC 的自动配置不需要了,所有的配置都是我们自己配,只需要标注 @EnableWebMvc 即可

3.1 环境搭建
@EnableWebMvc
@SpringBootApplication
public class WebApplication {
3.2 原理分析
1. EnableWebMvc

@EnableWebMvc 会向容器中导入 DelegatingWebMvcConfiguration 组件

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

WebMvcAutoConfiguration 启用的前提条件是 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) ,而 DelegatingWebMvcConfiguration 正好是 WebMvcConfigurationSupport 类型,所以此注解会导致 Spring Mvc 自动装配全部失效

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值