静态资源导入
WebMvcAutoConfiguration
类下的内部类WebMvcAutoConfigurationAdapter
存在如下方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//如果静态资源属性被自定义了,那么记录日志,并return
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
//在这里注册了资源路径"/webjars/**",具体路径为"classpath:/META-INF/resources/webjars/"
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//如果没有注册staticPathPattern路径,将该路径进行注册
//首先就是staticPathPattern,还有就是this.resourceProperties.getStaticLocations()
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
webjars
webjars有一个官网,存在一些前端的jar包,并提供maven引入的方式
我们可以引入jQuery的依赖,可以发现注册的目录结构与jar包中的目录结构一致
启动项目,在地址栏输入http://localhost:8082/webjars/jquery/3.5.1/jquery.js,就可以访问jquery.js文件
第三种方式是注册staticPathPattern路径,在WebMvcProperties
类可以得知staticPathPattern的值
//当前目录下的所有资源都可以识别
private String staticPathPattern = "/**";
在ResourceProperties
类中得知,以下四个路劲也可以访问静态资源
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
我们可以将三个目录都补齐,如下
然后分别创建"1.js"用于测试,"1.js"文件内容不同
在地址栏输入http://localhost:8080/1.js,访问"1.js"文件,可得到不同目录的优先级:
resources > static > public
一般在static目录下存放静态资源
总结:
1、首先可以在配置文件中定义staticPathPattern路径,但是一旦定义那么其他两个路径失效,所以最好不要定义
2、classpath:/META-INF/resources/webjars/
3、“classpath:/META-INF/resources/”、“classpath:/resources/”、classpath:/static/、“classpath:/public/”、"/**"
首页
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
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");
}
通过读源码可得首先,可以自己配置首页存放的路径,因为他需要获取StaticPathPattern
,通过getWelcomePage()
方法以及getIndexHtml()
方法获取"index.html"页面作为首页,可以将"index.html"文件放在static目录下,这样可以在浏览器中访问
模板引擎
模板引擎的作用就是,我们写一个页面模板,比如某些值是动态的,我们把模板和数据交给模板引擎,模板引擎按照数据帮你把表达式解析,填充到指定位置。jsp就是一个模板引擎,模板引擎都是这样的思想。我们在SpringBoot中可以使用thymeleaf作为模板引擎。
ThymeleafProperties
类,该类下指定了页面的前缀和后缀
//templates目录下的所有资源只能通过Controller跳转
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
通过模板引擎,我们就可以在Controller中访问其他页面
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(Model model){
model.addAttribute("msg","Hello SpringBoot");
return "hello";
}
}
使用thymeleaf在页面中获取域中的值
<!DOCTYPE html>
<!--引入Thymeleaf命名空间:xmlns:th="https://www.thymeleaf.org"-->
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素的属性都可以被thymeleaf接管,即:th:元素名-->
<h1 th:text="${msg}"></h1>
</body>
</html>
thymeleaf官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#using-texts
thymeleaf循环取值
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(Model model){
//向域中存入一个集合
model.addAttribute("msgs", Arrays.asList("aaa","bbb","ccc"));
return "hello";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--th:each="元素:${集合名}"-->
<h1 th:each="msg:${msgs}" th:text="${msg}"></h1>
</body>
</html>
扩展SpringMVC
如果你想保留这些Spring Boot MVC的配置,并进行更多的MVC配置(拦截器、格式化、视图控制器和其他功能),你可以添加@Configuration
到继承了WebMvcConfigurer
接口的自定义类上,但不要加@EnableWebMvc
扩展视图解析器
ContentNegotiatingViewResolver
类下的resolveViewName()
方法中存在如下一段代码
//如果requestedMediaTypes != null
if (requestedMediaTypes != null) {
//获取候选视图
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//获取最佳试图
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
//返回最佳试图
return bestView;
}
}
在getCandidateViews()
方法(ContentNegotiatingViewResolver
类下)中,会遍历视图解析器,并将视图加入视图候选中,并将视图候选的集合返回。
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;
}
自定义视图解析器
//扩展SpringMVC
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
//将自定义的视图解析器,交给Spring容器
@Bean
public MyViewResolver getMyViewResolver(){
return new MyViewResolver();
}
//自定义一个视图解析器
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
所有请求都会经过DispatcherServlet
,在该类中存在方法doDispatch()
,我们在该方法处打上断点,debug启动程序
点开this可以看到有一个ViewResolvers,点开后发现存在ThymeleafViewResolver(因为配置了Thymeleaf模板引擎),还存在我们自己的ViewResolver
为什么不能加@EnableWebMvc
//该注解核心是导入了一个类:DelegatingWebMvcConfiguration
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
DelegatingWebMvcConfiguration
类继承了WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
而WebMvcAutoConfiguration
类上存在注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,即如果存在WebMvcConfigurationSupport
类实例,WebMVC的自动配置失效。
Web开发
配置主页
扩展SpringMVC
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//使指定请求都可以跳到index页面
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
静态资源访问问题
一些js、css静态文件的链接在thymeleaf模板下需要使用@{…}引入
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
国际化
首先需要将这些编码改为"UTF-8"
国际化配置文件
然后新建文件夹"i18n",用来存储国际化有关的配置文件,目录结构如下图所示
login.properties
默认的语言
login.tip=请登录
login.password=密码
login.remember=记住我
login.username=用户名
login.btn=登陆
login_en_US.properties
英语语言
login.tip=Please sign in
login.password=Password
login.remember=Remember me
login.username=Username
login.btn=Sign in
login_zh_CN.properties
中文语言配置文件与login.properties
一样。
在以上配置文件中需要配置页面中需要要国际化的信息
在application.yaml
中进行如下配置,声明国际化配置文件
spring:
messages:
basename: i18n.login
页面显示
进行完配置后,需要将这些数据在页面中显示
<!--使用#{...}将数据显示在页面上-->
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}"></h1>
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
...
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
...
<button class="btn btn-lg btn-primary btn-block" type="submit" >[[#{login.btn}]]</button>
语言转换
在WebMvcAutoConfiguration
类中存在一个国际化解析方法
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
AcceptHeaderLocaleResolver
类实现了LocaleResolver
public class AcceptHeaderLocaleResolver implements LocaleResolver
AcceptHeaderLocaleResolver
类中还存在resolveLocale()
方法
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取默认的地区语言
Locale defaultLocale = getDefaultLocale();
//如果请求头中不存在"Accept-Language",那么使用默认的地区语言
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
//如果不为空,进行自定义的设置
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
我们可以自定义国际化解析类实现LocaleResolver
接口,重写resolveLocale
方法并仿照该方法自定义逻辑
首先需要前端传一个请求参数
<!--使用@{...}表示链接,在括号中传递参数-->
<a class="btn btn-sm" th:href="@{/index.html(language=zh_CN)}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(language=en_US)}">English</a>
后台对请求参数进行解析,并使用对应的地区语言
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的参数
String language = request.getParameter("language");
//如果不存在参数,使用默认的地区语言
Locale locale = Locale.getDefault();
//如果请求携带了国际化的参数
if(!StringUtils.isEmpty(language)){
String[] split = language.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
将MyLocaleResolver
交给Spring容器
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
//将bean交给Spring容器,自定义的国际化组件就生效了
//注意:方法名必须为localeResolver
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}