1、简介
使用Spring Boot:
(1)、创建Spring Boot应用,添加需要的模块;
(2)、Spring Boot对于支持自动配置的模块已经加载完毕,只需要在配置文件中指定少量配置信息即可;
(3)、编写业务逻辑代码。
2、Spring Boot对静态资源的映射规则:
ResourceProperties:Spring Boot静态资源配置类
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware, InitializingBean {
//可以设置和静态资源有关的参数,缓存时间等。。。
private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
//staticLocations即为上面连个变量的合集
private String[] staticLocations = RESOURCE_LOCATIONS;
static {
RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
+ SERVLET_RESOURCE_LOCATIONS.length];
System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
SERVLET_RESOURCE_LOCATIONS.length);
System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
}
//other code...
}
WebMvcProperties:WebMvc配置类
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
//静态资源的路径pattern为/**
private String staticPathPattern = "/**";
//other code...
}
MVC的自动配置都在WebMvcAutoConfiguration中
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
//other code...
}
先来看看静态资源文件的映射规则:
WebMvcAutoConfiguration#addResourceHandlers方法:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
//如果是/webjars/**请求,则将其映射到classpath:/META-INF/resources/webjars/目录下
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
//如果是请求/**,将其映射到上述staticLocations指定的值
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
1)、所有/webjars/**资源映射请求都去classpath:/META-INF/resources/webjars/找资源
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
webjars:以jar包的方式引入的静态资源,http://www.webjars.org/
以jQuery为例:引入jQuery的jar包
<!-- 引入webjars-jQuery -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1-1</version>
</dependency>
请求jQuery正确的路径为:http://localhost:8080/webjars/jquery/3.3.1-1/jquery.js
__总结:所有以/webjars/**的资源映射请求会去classpath:/META-INF/resources/webjars/找资源。 __
2)、/** 访问当前项目的任何资源(静态资源的文件夹)
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
this.mvcProperties.getStaticPathPattern()
返回/**
this.resourceProperties.getStaticLocations()
返回
"/",
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
__总结:任何访问/**的请求,就会去静态资源文件夹下查找对应的资源名。 __
3)、欢迎页(首页)的资源映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
ResourceProperties#getWelcomePage方法:
public Resource getWelcomePage() {
for (String location : getStaticWelcomePageLocations()) {
Resource resource = this.resourceLoader.getResource(location);
try {
if (resource.exists()) {
resource.getURL();
return resource;
}
}
catch (Exception ex) {
// Ignore
}
}
return null;
}
ResourceProperties#getStaticWelcomePageLocations方法:
//获取所有静态资源文件夹下的欢迎页
private String[] getStaticWelcomePageLocations() {
String[] result = new String[this.staticLocations.length];
for (int i = 0; i < result.length; i++) {
String location = this.staticLocations[i];
if (!location.endsWith("/")) {
location = location + "/";
}
result[i] = location + "index.html";
}
return result;
}
__总结:从源码来看,静态资源文件夹下的所有index.html页面会被/**映射。 __
4)、所有的**/favicon.ico 都会在静态资源文件夹下查找:
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@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(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}
ResourceProperties#getFaviconLocations方法:
//ResourceProperties#getFaviconLocations()
List<Resource> getFaviconLocations() {
List<Resource> locations = new ArrayList<Resource>(
this.staticLocations.length + 1);
if (this.resourceLoader != null) {
for (String location : this.staticLocations) {
locations.add(this.resourceLoader.getResource(location));
}
}
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
__总结:任何路径下请求**/favicon.ico,都会去静态资源文件下查找。 __
3、模版引擎
JSP,Velocity,Freemarker,Thymeleaf
Spring Boot推荐的模版引擎:Thymeleaf。
3.1、引入Thymeleaf依赖
<!-- 修改Spring Boot的默认版本 -->
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<!-- 布局功能的支持程序
thymeleaf3 对应layout2版本
thymeleaf2 对应layout1版本
-->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
<!-- thymeleaf模版引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3.2、Thymeleaf的使用&语法
ThymeleafProperties配置类:
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
//默认编码
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
//文档类型
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
//模版位置
public static final String DEFAULT_PREFIX = "classpath:/templates/";
//模版后缀
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = DEFAULT_PREFIX;
private String suffix = DEFAULT_SUFFIX;
private String mode = "HTML5";
private Charset encoding = DEFAULT_ENCODING;
private MimeType contentType = DEFAULT_CONTENT_TYPE;
//缓存
private boolean cache = true;
private Integer templateResolverOrder;
private String[] viewNames;
private String[] excludedViewNames;
private boolean enabled = true;
//other code...
}
只要把模版html放置在classpath:/templates/目录下,thymeleaf就会自动渲染。
使用Thymeleaf:
(1)、导入thymeleaf的名称空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org"></html>
(2)、thymeleaf语法:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${hello}">这里是原来的文本!</div>
</body>
</html>
(3)、语法规则:
i)、th:text:改变当前元素里面的文本内容
th:任意html属性:用来替换原生html属性的值,例如id换成th:id, class换成th:class等等。
ii)、表达式:
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值,OGNL表达式
1)、获取对象的属性、调用方法
2)、使用内置的基本对象
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
3)、内置的工具对象
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they
would be obtained using #{…} syntax.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration)
Selection Variable Expressions: *{...}:选择表达式,跟${}功能类似
补充功能:配合th:object进行使用
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL链接
例子:@{/order/process(execId=${execId},execType='FAST')}
Fragment Expressions: ~{...}:片段引用表达式
Literals:
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
Text operations:
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations:
Binary operators: and , or
Boolean negation (unary operator): ! , not
Comparisons and equality:
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
Conditional operators:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
4、SpringMVC自动配置
4.1、Spring MVC auto-configuration
Spring Boot 提供了大多数SpringMVC应用常用的自动配置项。
以下是Spring Boot对SpringMVC的默认配置(来自官网,自行翻译):
- 自动配置了
ContentNegotiatingViewResolver
和BeanNameViewResolver
的Beans.- 给容器自定义添加一个视图解析器,该ContentNegotiatingViewResolver 的bean会自动组合进来。
- 自动配置了ViewResolver:ContentNegotiatingViewResolver是组合了所有的视图解析器
public class WebMvcAutoConfiguration {
//other code...
@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;
}
}
其中ContentNegotiatintViewResolver类实现ViewResoler接口:
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
//other code...
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//获取所有的MediaType,例如application/json,text/html等等。。。
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;
}
}
//other code...
}
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<View>();
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);
}
}
}
}
//other code...
return candidateViews;
}
}
- 支持静态资源文件夹和webjars
- 静态首页的访问。
- 自定义favicon图标
- 自动注册了
Converter
,GenericConverter
,Formatter
Beans.- Converter:转换器,用于类型转换
- Formatter:格式化器
@Bean
//如果配置了spring.mvc.date-format,则自动注册Formatter<Date>的Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());
}
自动添加的格式化转换器,只需添加到容器中即可。
- 支持
HttpMessageConverters
消息转换器。- HttpMessageConverter:SpringMVC用来转换Http请求和响应的
-
HttpMessageConverters
是从容器中获取的所有的HttpMessageConverter,如果需要给容器中添加HttpMessageConverter,只需要将自定义的组件注册在容器中即可。
- 自动配置
MessageCodesResolver
用于错误代码的生成规则。- PREFIX_ERROR_CODE: error_code + "." + object name + "." + field
- POSTFIX_ERROR_CODE:object name + "." + field + "." + error_code
-
自动使用
ConfigurableWebBindingInitializer
Bean。- 可以自定义配置一个
ConfigurableWebBindingInitializer
来替换默认的,需要添加到容器中。 - 初始化WebDataBinder:用于将请求数据绑定到数据模型等。
org.springframework.boot.autoconfigure.web:web的所有自动配置场景。
- 可以自定义配置一个
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration
class of type WebMvcConfigurerAdapter
, but without @EnableWebMvc
. If you wish to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
or ExceptionHandlerExceptionResolver
you can declare a WebMvcRegistrationsAdapter
instance providing such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
.
4.2、扩展SpringMVC
编写一个配置类,是WebMvcConfigurerAdapter类的子类,但是不能标注@EnableWebMvc注解。
既保留了所有的自动配置,也能使用自定义的扩展配置。
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送/cay请求,会直接跳到success页面。
registry.addViewController("/cay").setViewName("success");
}
}
原理:
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类,在内部维护了一个内部类WebMvcAutoConfigurationAdapter,该类又继承自WebMvcConfigurerAdapter,看定义:
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {}
而WebMvcConfigurerAdapter又实现了WebMvcConfigurer接口:
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
所以自定义的配置类(此例为MyMvcConfig)是个WebMvcConfigurer接口的实现类。
2)、在做WebMvcAutoConfigurationAdapter自动配置时会导入@Import(EnableWebMvcConfiguration.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {}
父类:
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
3)、容器中所有的WebMvcConfigurer都会一起起作用。
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<WebMvcConfigurer>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
@Override
public void addFormatters(FormatterRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addFormatters(registry);
}
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
//other code...
}
从源码中可以看到,从容器中获取的所有WebMvcConfigurer对象都会被调用对应的配置方法。
4)、最后可以结合第2点和第3点看出,自定义的配置类也会被调用。
总结:SpringMVC的自动配置和自定义的扩展配置都会起作用。
4.3、全面接管SpringMVC
在配置类上使用@EnableWebMvc注解,这样Spring Boot对SpringMVC的自动配置就失效了,所有都需要自定义配置。
原理:为什么使用了@EnableWebMvc后自动配置就失效了?
1)、EnableWebMvc注解的定义
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}
2)、导入了DelegatingWebMvcConfiguration组件配置
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}
3)、查看WebMvcAutoConfiguration自动配置类的签名
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
//如果容器中没有该组件的时候,这个自动配置类就自动生效。
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了,导致Spring Boot对SpringMVC的自动配置失效,即WebMvcAutoonfiguration配置类未注册成功。
5、如何修改SpringBoot的默认配置?
模式:
1)、Spring Boot在自动配置很多组件的时候,会先检查容器中是否有用户自定义配置的Bean或者组件。如果有,就使用用户自定义的;如果没有,Spring Boot才自动配置;如果有些组件可以有多个,用户可以自定义添加组件,并加入到容器中,这样Spring Boot会自动将用户配置的和默认的组合起来。
2)、在Spring Boot中会有很多的Configurer帮助用户进行扩展配置。
3)、在Spring Boot中会有很多的Customizer帮助用户进行定制配置。
6、Restful
6.1、设置默认访问首页的url
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
registry.addViewController("/cay").setViewName("success");
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/").setViewName("login");
}
}
6.2、国际化
Spring应用程序处理国际化资源的步骤:
1)、编写国际化配置文件
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤:
1)、编写国际化配置文件,抽取页面中需要进行显示的国际化信息。
2)、Spring Boot自动配置了管理国际化资源文件的组件:
@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
@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";
//other code...
@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;
}
}
默认情况下,国际化资源文件的基础名为messages,且存放在classpath根路径下,即messages.properties、messages_zh_CN.properties、messages_en_US.properties等等,这样就无需在配置文件中设置spring.messages.basename=...了,但是如果基础名不为messages或者不在classpath根路径下,则需要手动添加spring.messages.basename=文件名.自定义的基础名,如果有多个就用逗号分隔。
spring.messages.basename=i18n.login
3)、去页面获取国际化的值:
<!DOCTYPE html>
<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 Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.1.0/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" 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}" 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 in</h1>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.signin}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>
</html>
效果:根据浏览器语言设置的语言信息进行切换国际化语言。
4)、如何通过链接切换国际化语言?
原理:
国际化区域信息对象Locale,有个区域信息对象处理器LocaleResolver,在WebMvcAutoConfiguration自动配置类中配置了一个LocaleResolver的Bean。
@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;
}
- 如果在配置文件中配置了spring.mvc.locale-resolver=fixed,且指定了spring.mvc.locale的值,则页面使用的是指定且固定的国际语言(FixedLocaleResolver);
- spring.mvc.locale-resolver默认为accept-header,即如果不配置则通过浏览器请求头中的Accept-Language 作为判断依据(AcceptHeaderLocaleResolver):
public class AcceptHeaderLocaleResolver implements LocaleResolver {
@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);
}
//other code..
}
步骤:
i)、编写一个自定义的LocaleResolver类:
//使用链接的方法发送国际语言代码
public class MyLocaleResolver implements LocaleResolver{
private final Logger logger = LoggerFactory.getLogger(MyLocaleResolver.class);
@Override
public Locale resolveLocale(HttpServletRequest request) {
String lan = request.getParameter("lan");
Locale locale = Locale.getDefault();
if(StringUtils.hasText(lan)){
try{
String[] split = lan.split("_");
locale = new Locale(split[0], split[1]);
}catch (Exception e){
e.printStackTrace();
logger.error("错误信息:", e);
}
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
先从请求参数中获取lan数据,然后通过下划线分割成语言和国家,然后通过语言和国家来封装成一个Locale对象并返回,如果未指定lan参数,则默认使用default的Locale对象。
ii)、修改页面切换语言的链接,添加国际化语言代码:
<a class="btn btn-sm" th:href="@{/(lan='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/(lan='en_US')}">English</a>
iii)、将自定义的LocaleResolver覆盖默认的LocaleResolver:
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
//使用自定义的LocaleResolver来替换掉默认的LocaleResolver
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
//other code...
}
这样配置了自定义的LocaleResolver对象就会把WebMvcAutoConfiguration自动配置类中预定义的LocaleResolver的Bean屏蔽掉,Spring Boot就会使用自定义的LocaleResolver对象。
注意:一旦使用链接来切换国际化语言,则会导致通过浏览器语言切换国际化语言的功能失效了。
6.3、登录
模版引擎页面修改以后,要实时生效:
- 禁用模版引擎的缓存:
- spring.thymeleaf.cache=false
- 页面修改完成后,按下Ctrl+F9,重新编译。
登录错误消息的显示:
<p style="color:red;" th:text="${message}" th:if="${not #strings.isEmpty(message)}"></p>
6.4、拦截器进行登录检查
步骤:
i)、编写登录拦截器:
//登录检查
public class LoginHandlerInterceptor implements HandlerInterceptor {
//目标方法执行前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if(user == null){
//未登录,返回登录页面
request.setAttribute("message", "请先登录!");
request.getRequestDispatcher("/").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 {
}
}
ii)、配置拦截器:
@Override
public void addInterceptors(InterceptorRegistry registry) {
// super.addInterceptors(registry);
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/", "/login.html", "/user/login");
}
6.5、CRUD-员工实验
thymeleaf公共页面元素抽取:
1)、抽取公共片段
<div th:fragment="copy">
© 版权所有
</div>
2)、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename :: #selectorId} 表示:模版名::#选择器id
代码片段
<div id="copy-section">
© 2011 The Good Thymes Virtual Grocery
</div>
插入片段
<div th:insert="~{footer :: #copy-section}"></div>
~{templatename :: fragmentname} 表示:模版名::片段名
代码片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
插入片段
<div th:insert="~{footer :: copy}"></div>
3)、默认效果
insert的功能片段会被插入到div标签中。
如果使用th:insert等属性进行引入,可以不用写~{},可以直接写templatename::#selectorId/fragmentname。
但是如果是行内写法,必须加上~{}, 如[[~{}]]。
三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含到引入的元素中
代码片段
<footer th:fragment="copy">
© 版权所有
</footer>
引入方式
<div th:insert="~{footer::copy}"></div>
<div th:replace="~{footer::copy}"></div>
<div th:include="~{footer::copy}"></div>
实际效果:
th:insert:
<div>
<footer>
© 版权所有
</footer>
</div>
th:replace:
<footer>
© 版权所有
</footer>
th:include:
<div>
© 版权所有
</div>
在引入代码片段的时候,可以使用传递参数的方式,这样在代码片段中就可以使用传递过来的参数。
<div th:fragment="frag">
...
</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">
或
<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>
<div th:replace="::frag (${value1},${value2})">...</div>
使用命名参数时顺序不重要
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>
<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>
比如,在templates/commons/bar.html中定义了如下代码片段
<nav id="sidebar">
<a class="nav-link" th:class="${activeUrl == 'main' ? 'nav-link active' : 'nav-link'}" ...>
...
</a>
</nav>
在引入该代码片段的时候,可以使用传递参数的方式
<div th:replace="commons/bar::#sidebar(activeUrl='main')"></div>
或
<div th:replace="commons/bar::#sidebar(activeUrl='emps')"></div>
如果出现status=400,大多数是数据格式不正确,特别是日期格式。
默认日期格式化是按照将/形式的字符串转换成日期类型,可以通过spring.mvc.date-format来设置日期格式:
spring.mvc.date-format=yyyy-MM-dd