在该例子中使用SpringBoot默认的静态资源文件夹,我们将相关的js、css、bootstrap.css等文件放到resources\static文件夹中。
SpringBoot默认支持静态首页访问,该首页(index.html)必须在静态资源文件夹下,而SpringBoot推荐使用的模板引擎Thymeleaf只会对templates文件夹下的html文件进行解析,因此我们一定要区分静态资源与html文件所放置的位置。
下面将给出默认情况下资源引入后的目录截图。
在html文件中,我们可以使用Thymeleaf提供的资源引用方式来引用静态资源,如此我们可以在pom.xml文件中引入jQuery、bootstrap等的依赖,而不用再在静态文件夹中放置相关文件了。
相关依赖可以在WebJars官网进行查询。
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>bootstrapartifactId>
<version>4.0.0version>
dependency>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstraptitle>
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
head>
<body class="text-center">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
body>
html>
注:使用th:** 的方式来引入资源的好处是如果项目的访问名变化了,其会自动加上更改后的项目名。
由于SpringBoot的静态首页访问是访问静态文件夹里的index.html文件,因此我们为了让默认访问的首页是templates文件夹下的指定文件就需要进行如下三种处理。
@Controller
public class HelloController {
@RequestMapping({"/", "/index.html"})
public String index() {
return "index";
}
}
为了一个页面写一个空方法,显然并不是好的解决办法,我们可以利用扩展SpringMVC的方式来达到目的。下面将给出实现方式。
// 使用WebMvcConfigurerAdapter可以扩展SpringMVC的功能
// @EnableWebMvc 加上该注解将全面接管SpringMVC,默认的配置将失效
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送"http://localhost:8080/"请求将访问index页面
//SpringBoot会根据Thymeleaf的相关配置访问templates目录下的index.html文件并经由Thymeleaf解析
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.xml").setViewName("index");
}
}
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
// 该WebMvcConfigurerAdapter和默认存在的WebMvcConfigurerAdapter都会起作用
@Bean // 一定要使用该注解将该组件添加到容器中
public WebMvcConfigurerAdapter addWebMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.xml").setViewName("index");
}
};
return adapter;
}
}
在SpringMVC中国际化需要以下几个步骤:
1. 编写国际化配置文件:***.properties文件
2. 使用ResourceBundleMessageSource管理国际化资源文件。
3. 在页面使用fmt:message取出国际化内容。
在SpringBoot中我们只需要编写国际化配置文件即可,其他都已经自动配置完成,下面将给出在SpringBoot中国际化的步骤。
1. 编写国际化配置文件,抽取页面需要显示的国际化消息。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstraptitle>
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Please sign inh1>
<label class="sr-only">Usernamelabel>
<input type="text" class="form-control" placeholder="Username" required="" autofocus="">
<label class="sr-only">Passwordlabel>
<input type="password" class="form-control" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
label>
div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign inbutton>
<p class="mt-5 mb-3 text-muted">© 2017-2018p>
<a class="btn btn-sm">中文a>
<a class="btn btn-sm">Englisha>
form>
body>
html>
新建国际化配置文件,如下:
login.properties
login.password=密码~
login.tip=请登录~
login.username=用户名~
login.remember=记住我~
login.btn=登录~
login_en_US.properties
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username
login.btn=Sign in
login_zh_CN.properties
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
login.btn=登录
2. SpringBoot自动配置好了管理国际化资源文件的组件
在MessageSourceAutoConfiguration 中有对国际化的配置,部分源码如下:
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}
我们将国际化的配置文件放到了i18n目录下,若要使相关文件生效则必须在SpringBoot的配置文件中指定国际化文件,如下:
spring.messages.basename=i18n.login
3. 在页面获取国际化的值
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstraptitle>
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
<label class="sr-only" th:text="#{login.username}">Usernamelabel>
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Passwordlabel>
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
label>
div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign inbutton>
<p class="mt-5 mb-3 text-muted">© 2017-2018p>
<a class="btn btn-sm">中文a>
<a class="btn btn-sm">Englisha>
form>
body>
html>
国际化的实现由一个重要的对象是Locale(区域信息对象),在SpringMVC中获取Locale对象时需要需要通过LocaleResolver对象。SpringBoot在WebMvcAutoConfiguration中默认配置了一个LocaleResolver,源码如下:
@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;
}
public class AcceptHeaderLocaleResolver implements LocaleResolver {
private final List<Locale> supportedLocales = new ArrayList<>(4);
@Nullable
private Locale defaultLocale;
public void setDefaultLocale(@Nullable Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
// 解析区域信息
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
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);
}
}
根据源码可以看出,默认根据请求头带来的区域信息获取Locale进行国际。
默认的LocaleResolver是根据请求头的信息来获取的区域信息进行国际化的,我们可以通过用自己定义的LocaleResolver实现点击链接切换国际化。
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>
package com.sk.springbootweb.component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocalResolver implements LocaleResolver {
// 解析区域信息
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 从请求中获取参数,得到我们指定的国际化信息
String l = request.getParameter("l");
// 实例化一个默认的区域信息对象,目的是l为空时使用默认的区域信息
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(l)){
// zh_CN:zh是语言标识,CN是国家标识
String[] split = l.split("_");
// 如果l参数不为空就根据参数信息实例化区域信息
// new Locale(language,country)
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
想要让我们自己的LocaleResolver起作用还需要将其添加到容器中,这就需要在我们的配置类MyMvcConfig中将我们的LocaleResolver添加到容器中,方法就是在MyMvcConfig中添加一个方法,如下:
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}
开发期间模板引擎页面修改以后,要实时生效需要如下两个步骤:
- 在SpringBoot的配置文件中使用spring.thymeleaf.cache=false 禁用模板引擎的缓存
- 页面修改完成以后ctrl+f9:重新编译
在登录失败时需要进行错误信息提示,此时我们可以使用Thymeleaf来获取request中存放的错误信息。
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
我们可以使用重定向来解决表单重复提交的问题。
return "redirect:/main.html";
registry.addViewController("/main.html").setViewName("dashboard.html");
package com.sk.springbootweb.component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptor implements HandlerInterceptor {
// 目标方法执行之前做一个预检查
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginuser = request.getSession().getAttribute("loginUser");
if (loginuser == null){
// 未登录
request.setAttribute("msg","没有权限,请登录!");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
// 已登录
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
package com.sk.springbootweb.config;
import com.sk.springbootweb.component.LoginHandlerInterceptor;
import com.sk.springbootweb.component.MyLocalResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
// 使用WebMvcConfigurerAdapter可以扩展SpringMVC的功能
// @EnableWebMvc 加上该注解将全面接管SpringMVC,默认的配置将失效
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public WebMvcConfigurerAdapter addWebMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard.html");
}
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 这里我们不用排除静态资源的路径,因为SpringBoot已经对静态资源进行了映射。
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
};
return adapter;
}
}
1、抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
div>
2、引入公共片段
<div th:insert="~{footer :: copy}">div>
~{templatename::selector}:模板名::选择器,id用#
~{templatename::fragmentname}:模板名::片段名
Thymeleaf中有三种引入公共片段的th属性,具体内容如下所示。
抽取的片段内容:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
footer>
1. th:insert**:将公共片段整个插入到声明引入的元素中
引入方式
<div th:insert="footer :: copy">div>
效果
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
footer>
div>
2. th:replace**:将声明引入的元素替换为公共片段
引入方式
<div th:replace="footer :: copy">div>
效果
<footer>
© 2011 The Good Thymes Virtual Grocery
footer>
2. th:include**:将被引入的片段的内容包含进这个标签中
引入方式
<div th:include="footer :: copy">div>
效果
<div>
© 2011 The Good Thymes Virtual Grocery
div>
注意:如果使用th:insert等属性进行引入,可以省略掉~{},而在行内写法中不能省略。
行内写法:
[[~{}]]:转义,显示字符串
[(~{})]不转义,会显示样式效果
示例:
比如dashboard.html和list.html的顶部栏是一样的,此时我们可以在dashboard.html中使用th:fragment="topbar"标记抽取的片段。
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
li>
ul>
nav>
之后在list.html的相应位置引用上述抽取的片段。
<div th:replace="~{dashboard.html::topbar}">div>
为了方便我们可以将需要抽取的公共部分放到一个html文件中,之后所有的引用都是从该文件中进行引用。
在抽取的部分对参数进行判断
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a th:href="@{/emps}" th:class="${activeUri=='emps'?'nav-link active':'nav-link'}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
<circle cx="9" cy="7" r="4">circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
svg>
员工管理
a>
li>
ul>
div>
nav>
引用的时候传入参数:
<div th:replace="commons/bar::#sidebar(activeUri='emps')">div>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}">td>
<td th:text="${emp.lastName}">td>
<td th:text="${emp.email}">td>
<td th:text="${emp.gender}==0?'女':'男'">td>
<td th:text="${emp.department.departmentName}">td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}">td>
tr>
tbody>
日期的格式有2017-12-12、2017/12/12、2017.12.12,SpringMVC中需要将页面提交的值转换为指定的类型,此时我们可以在SpringBoot的配置文件中指定时间的格式。
spring.mvc.date-format=yyyy-MM-dd
源码链接:https://gitee.com/sk_anruo/spring-boot-04-web-restfcrud.git