Springboot 2.x 欢迎页配置

Springboot 2.x 对于欢迎页的配置都是由 WebMvcAutoConfiguration 这个类完成的,我们这里选取 Springboot-2.3.7.RELEASE 这个版本来探究一下 Springboot 底层对于欢迎页是如何定义的

一、欢迎页配置原理

在 WebMvcAutoConfiguration 配置类中有一个静态内部类 EnableWebMvcConfiguration ,由该类完成对 Springboot 欢迎页的配置

// 1、标记该类是个配置类, proxyBeanMethods 默认值为 true , 由于组件之间没有依赖关系,为了加快 Spring 检索效率,设置其值为 false
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
	// 成员变量
	private final ResourceProperties resourceProperties;
	private final WebMvcProperties mvcProperties;
	private final ListableBeanFactory beanFactory;
	private final WebMvcRegistrations mvcRegistrations;
	private ResourceLoader resourceLoader;
	
  // 2、这里有一个知识点,由于该类只有一个有参数的构造方法,所以构造方法中的参数值都是从 IOC 容器中获取的
	public EnableWebMvcConfiguration(
			// 2.1、与 ResourceProperties 进行配置绑定 ----> 详情见代码块一
			ResourceProperties resourceProperties,
			// 2.2、与 WebMvcProperties 进行配置绑定 ----> 详情见代码块二
			ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
			// 2.3、Web 模块注册的接口
			ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, 
			// 2.4、IOC 容器
			ListableBeanFactory beanFactory) {
		// 2.5、从 IOC 容器中获取到组件,并且将这些组件赋值给 EnableWebMvcConfiguration 的成员变量
		this.resourceProperties = resourceProperties;
		this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
		this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
		this.beanFactory = beanFactory;
	}

	// 3、向容器中注册欢迎页处理器映射组件
	@Bean
	public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
			FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
		// 3.1、获取欢迎页处理器映射 -----> 详情见代码块七
		WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
				new TemplateAvailabilityProviders(applicationContext), applicationContext, 
				// 3.2、获取欢迎页面 ----> 详情见代码块三
				getWelcomePage(),
				// 3.3、获取静态资源前缀 ----> 详情见代码块六
				this.mvcProperties.getStaticPathPattern());
		// 3.4、设置拦截器
		welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
		// 3.5、设置 CorsConfiguration
		welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
		// 3.6、返回欢迎页处理器映射
		return welcomePageHandlerMapping;
	}

	private Optional<Resource> getWelcomePage() {
		String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
		return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
	}

	private Resource getIndexHtml(String location) {
		return this.resourceLoader.getResource(location + "index.html");
	}
	
	.......注册其它的一些组件
}

代码块一、与 ResourceProperties 进行配置绑定

打开 ResourceProperties 这个类,该类的声明如下

// 1、与 application.properties 配置文件中以 spring.resources 开头的配置项进行属性绑定
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
	// 2、默认的静态资源路径
	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
			"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

	 // 3、是否开启默认的静态资源配置
	private boolean addMappings = true;

	private final Chain chain = new Chain();

	private final Cache cache = new Cache();

	......
}

代码块二、与 WebMvcProperties 进行配置绑定

打开 WebMvcProperties 这个类,该类的声明如下

// 1、与 application.properties 配置文件中以 spring.mvc 开头的配置项进行属性绑定
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
	/**
	 * Formatting strategy for message codes. For instance, `PREFIX_ERROR_CODE`.
	 */
	private DefaultMessageCodesResolver.Format messageCodesResolverFormat;

	/**
	 * Locale to use. By default, this locale is overridden by the "Accept-Language"
	 * header.
	 */
	private Locale locale;

	......
}

代码块三、getWelcomePage():获取欢迎页面

private Optional<Resource> getWelcomePage() {
	// 1、this.resourceProperties.getStaticLocations():获取静态资源路径 ----> 详情见代码块四
	// 2、getResourceLocations:获取最终的映射路径 ----> 详情见代码块五
	String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
  // 3、通过筛选、过滤等操作,遍历上述所有的路径查看这些路径是否存在 index.html 页面,如果有则将该页面作为可选择的欢迎页面
	return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}

代码块四、this.resourceProperties.getStaticLocations()

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
	// 1、Springboot 默认的静态资源路径
	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
			"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

	public String[] getStaticLocations() {
		return this.staticLocations;
	}
	
	......
}

代码块五、getResourceLocations(...)

static String[] getResourceLocations(String[] staticLocations) {
	// 1、创建一个 String 类型的路径数组,其长度为静态资源路径长度 + 1
	// staticLocations 为静态资源路径,SERVLET_LOCATIONS 为常量 / ,其长度为 1
	String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
	// 2、将静态资源路径数组复制到新数组中
	System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
	// 3、将 SERVLET_LOCATIONS 复制到新数组中
	System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
	// 4、返回新的路径数组,该数组包含了 springboot 的静态资源路径和 /
	return locations;
}

代码块六、this.mvcProperties.getStaticPathPattern()

// 1、该配置类中的属性与 application.properties 中的 spring.mvc 开头的配置项动态绑定
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
	// 2、staticPathPattern 默认值为 /** ,可以在配置 spring.mvc.static-path-pattern 来替换默认值 /**
	private String staticPathPattern = "/**";

	public String getStaticPathPattern() {
		return this.staticPathPattern;
	}

	......
}

代码块七、new WelcomePageHandlerMapping(...)

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
		ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
	
	// 1、首先判断类路径和静态资源路径下是否存在 index.html 页面
	// 代码块三 第 3 小点中,如果静态资源路径和类路径 / 下面有 index.html 页面,那么 welcomePage.isPresent() 为 true
	// staticPathPattern 的值必须要为 /** ,如果我们改变了 staticPathPattern 的值,那么首页的配置将失效
	if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
		logger.info("Adding welcome page: " + welcomePage.get());
		setRootViewName("forward:index.html");
	}
	// 2、如果上述条件不成立,接着才从模板引擎下寻找是否存在 index.html 
	// 例如 Springboot 推荐的 thymeleaf (即 resources/templates/ 文件夹下是否存在 index.html ,如果有就把其作为首页
	else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
		logger.info("Adding welcome page template: index");
		setRootViewName("index");
	}
}

  

二、总结

1、默认情况下 Springboot 会去  /、 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/ 这五个路径下寻找是否存在 index.html ,如果存在就当其作为首页,

2、如果 staticPathPattern 的值改变了,那么首页配置将失效(注意,如果你想使用 Springboot 的首页功能,就不要去配置文件中配置 spring.mvc.static-path-pattern 选项 )

3、上述五个路径下如果找不到 index.html , Springboot 会尝试去模板引擎特定的文件夹下寻找 index.html ,如果存在就将其作为首页

三、案例

通过上面的分析,我们可以进行自定义静态资源的访问路径和首页的访问路径

注意:如果我们修改了 Springboot 的配置文件 application.properties 中的配置项,那么 Springboot 的默认值就会被覆盖

1、application.properties 不进行任何配置,全部使用 Springboot 默认的配置

可以看到,只要你将 index.html 放置在 Springboot 的默认静态资源文件夹下, Springboot 就能够识别到 index.html ,并且将其作为首页

2、修改 application.properties 中配置(修改了静态资源访问前缀、修改了默认的静态资源文件夹) 在 classpath:/static2/ 路径下放置 index.html

# 给静态资源访问配置一个前缀 /bluefatty/ ,配置了该配置项之后,Springboot 默认的首页访问将失效
spring.mvc.static-path-pattern=/bluefatty/**
# 修改 Springboot 默认的静态资源文件夹,启用了该配置之后 Springboot 默认的静态资源文件夹将失效
spring.resources.static-locations=classpath:/static2/,
                                  classpath:/public2/

浏览器发起请求 http://localhost:8080/  访问 index.html ,则会出现如下报错

为什么会出现这种原因呢? 因为我们在 application.properties 配置文件中修改了 staticPathPattern 的值,在不使用模板引擎的情况下 staticPathPattern 的值必须要为 /** (这个可以从代码块七中找到答案)否则就无法定位到静态资源文件夹下的 index.html 了

3、自定义配置

需求如下

上述需求该如何实现呢? 我们可以分析一下

通过 http://localhost:8080/ 能访问到首页,那么我们就不能改变 staticPathPattern 的值,因为改变了它的值之后代码七中的判断条件不成立,也就不能映射到静态资源文件夹下的 index.html,也就是我们就不能在 application.properties 中配置 spring.mvc.static-path-pattern 选项,因为在配置文件中配置的值会覆盖 Springboot 默认值

通过 http://localhost:8080/banner/xiaomaomao.txt 能访问到资源,那么就需要保留 Springboot 默认的静态资源文件夹,也就是不能在 application.properties 中配置 spring.resources.static-locations ,因为配置了该选项之后,Springboot 默认的静态资源文件夹会被覆盖,从而失效

通过 http://localhost:8080/slas/images/tiger.jpg 和 http://localhost:8080/slas/txt/Mango.txt 能够访问到对应的资源,我们可以考虑给其扩展额外的映射路径

综上:我们不能在 application.properties 中配置 spring.mvc.static-path-pattern 和 spring.resources.static-locations ,并且需要额外扩展静态资源的映射路径

那么只需要定义一个配置类

@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
    @Override
	// 添加静态资源映射路径,它不会改变 Springboot 默认的值它会和 Springboot 默认的 
	// /** 映射 {classpath:/META-INF/resources/、classpath:/resources/、
	// classpath:/static/、classpath:/public/}共同生效
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler("/slas/**")
                .addResourceLocations("classpath:slasResources/static/");
    }
}

测试都是生效的

4、改变首页的路径

需求如下

需要在 application.properties 中配置 spring.resources.static-locations ,配置之后 Springboot 默认的静态资源文件夹就被 /slasResources/static/ 替代了

# 修改 Springboot 默认的静态资源文件夹,启用了该配置之后 Springboot 默认的静态资源文件夹将失效
spring.resources.static-locations=classpath:/slasResources/static/

自定义配置类并注册组件

@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler("/slas/**")
                .addResourceLocations("classpath:/static/");
    }
}

测试如下

原文地址:https://www.cnblogs.com/xiaomaomao/p/14283524.html