springboot web开发
springboot在开发web项目的时候具备天然的优势,现在的很多企业级开发都是依托于springboot的。
使用springboot的步骤:
1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好 2、手动在配置文件中配置部分配置项目就可以运行起来了 3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。
1、springboot整合servlet
很多同学在刚接触web开发的时候第一个接触的都是servlet,下面我们来使用springboot整合servlet
(1)编写servlet类
@WebServlet(name = "myServlet",urlPatterns = "/srv")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("111");
super.doGet(req, resp);
}
}
(2)在启动类上添加如下配置
@SpringBootApplication
@ServletComponentScan
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
/* 或者启动如下方式,servlet上不需要添加注解
@Bean
public ServletRegistrationBean<MyServlet> getServletRegistrationBean(){
ServletRegistrationBean<MyServlet> bean = new ServletRegistrationBean<>(new MyServlet());
ArrayList<String> url = new ArrayList<>();
url.add("/srv");
bean.setUrlMappings(url);
bean.setLoadOnStartup(1);
return bean;
}
*/
}
(3)编写filter类
@WebFilter(filterName = "MyFilter", urlPatterns = "/filter")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("filter");
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("destory");
}
}
(4)编写监听器
listener是servlet规范定义的一种特殊类,用于监听servletContext,HttpSession和ServletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件,用于在事件发生前、发生后做一些必要的处理。可用于以下方面:
- 1、统计在线人数和在线用户
- 2、系统启动时加载初始化信息
- 3、统计网站访问量
- 4、记录用户访问路径。
编写监听器类
package com.mashibing.config;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MyHttpSessionListener implements HttpSessionListener {
public static int online=0;
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("创建session");
online++;
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("销毁session");
}
}
添加到配置类
package com.mashibing.config;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
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.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public ServletListenerRegistrationBean listenerRegist(){
ServletListenerRegistrationBean srb = new ServletListenerRegistrationBean();
srb.setListener(new MyHttpSessionListener());
System.out.println("listener");
return srb;
}
}
添加控制层代码
package com.mashibing.controller;
import com.mashibing.config.MyHttpSessionListener;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Controller
public class ThymeleafController {
@RequestMapping("/login")
public String login(HttpServletRequest request){
HttpSession session = request.getSession(true);
return "login";
}
@RequestMapping("online")
@ResponseBody
public String online(){
return "当前在线人数:"+MyHttpSessionListener.online +"人";
}
}
先发送login请求,然后再发送online的请求
2、静态资源的配置
默认情况下,Spring Boot 将在 classpath 或者 ServletContext 根目录下从名为 /static (/public、/resources 或 /META-INF/resources)目录中服务静态内容。它使用了 Spring MVC 的 ResourceHttpRequestHandler,因此您可以通过添加自己的WebMvcConfigurerAdapter 并重写 addResourceHandlers 方法来修改此行为。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
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));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
(1)加载webjars文件
在pom文件中添加jquery的相关依赖,直接可以通过浏览器访问到http://localhost:8080/webjars/jquery/3.4.1/jquery.js
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
(2)加载静态资源
当查找静态资源的时候能够发现静态资源的路径是/**,会去ResourceProperties这个类,可以看到对应的资源目录。
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class 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;
private String[] appendSlashIfNecessary(String[] staticLocations) {
String[] normalized = new String[staticLocations.length];
for (int i = 0; i < staticLocations.length; i++) {
String location = staticLocations[i];
normalized[i] = location.endsWith("/") ? location : location + "/";
}
return normalized;
}
可以看到静态资源的目录一共有如下几个: "classpath:/resources/" "classpath:/static/" "classpath:/public/"
(3)首页的配置信息
@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));
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");
}
3、springmvc的扩展
springmvc框架是一个mvc的web框架,springmvc允许创建@controller和@RestController bean来处理传入的HTTP请求,控制器种的方法通过@RequestMapping注解映射到HTTP。
Springboot提供了适用于大多数Springmvc应用的自动配置。
引入 ContentNegotiatingViewResolver 和 BeanNameViewResolver bean视图解析器。
支持服务静态资源,包括对 WebJar 的支持。
自动注册 Converter(网页传入的数据封装成对象,完成数据类型的转化)、GenericConverter 和 Formatter bean(将日期转换成规定的格式)。
支持 HttpMessageConverter,用来转换http请求和响应。
自动注册 MessageCodesResolver,定义错误代码生成规则。
支持静态 index.html。
支持自定义 Favicon。
自动使用 ConfigurableWebBindingInitializer bean,将请求树绑定到javaBean中。
自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器:即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
//WebMvcAutoConfiguration
@Bean //我们在这里确实看到已经给容器中注册了一个bean
@ConditionalOnBean({ViewResolver.class})
@ConditionalOnMissingBean(
name = {"viewResolver"},
value = {ContentNegotiatingViewResolver.class}
)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
resolver.setOrder(-2147483648);
return resolver;
}
//ContentNegotiatingViewResolver
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
//获取候选的视图对象
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
//选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
} else {
this.logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
//getCandidateViews
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;
}
//initServletContext
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
通过上面的代码分析,我们知道了springboot是在容器中去找视图解析器,因此,我们可以给容器自定义添加视图解析器,这个类会帮我们将他组合起来。
//自定义视图解析器
@Bean //放到bean中
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
扩展使用springmvc
/*如果您想保留 Spring Boot MVC 的功能,并且需要添加其他 MVC 配置(interceptor、formatter 和视图控制器等),可以添加自己的 WebMvcConfigurerAdapter 类型的 @Configuration 类,
但不能带 @EnableWebMvc 注解。如果您想自定义 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 或者 ExceptionHandlerExceptionResolver 实例,可以声明一个 WebMvcRegistrationsAdapter 实例来提供这些组件。
如果您想完全掌控 Spring MVC,可以添加自定义注解了 @EnableWebMvc 的 @Configuration 配置类
*/
package com.mashibing.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/msb").setViewName("success");
}
}
注意:需要添加模板引擎
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
springboot配置文件详解
在之前的项目开发中,我们可以使用xml,properties进行相关的配置,这种配置方式比较简单,但是在应对复杂的商业需求下,多环境和编程化的配置无法得到满足,因此springboot为我们提供了YAML的配置方式丰富功能和简化开发。
1、Properties配置详解
通常情况下,我们可以使用properties文件进行相关的配置。
(1)在resources目录下创建application.properties
person.name=zhangsan
person.age=12
person.gender=男
person.desc=my name is ${person.name},my age is ${person.age}
(2)创建对应的实体类对象
@Component
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
private String sex;
@Value("${person.desc}")
private String desc;
public Person() {
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
(3)创建对应的测试类
@SpringBootTest
class ConfigApplicationTests {
@Autowired
Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
可以在properties文件中使用随机数
(1)在application.properties文件中添加如下属性
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}
(2)创建对应的实体类
@Component
public class My {
@Value("${my.secret}")
private String screct;
@Value("${my.number}")
private int number;
@Value("${my.bignumber}")
private long bignumber;
@Value("${my.uuid}")
private UUID uuid;
@Value("${my.number.less.than.ten}")
private int lessThanTen;
@Value("${my.number.in.range}")
private int numberInRangel;
@Override
public String toString() {
return "My{" +
"screct=" + screct +
", number=" + number +
", bignumber=" + bignumber +
", uuid=" + uuid +
", lessThanTen=" + lessThanTen +
", numberInRangel=" + numberInRangel +
'}';
}
}
(3)创建对应的测试类
@SpringBootTest
class ConfigApplicationTests {
@Autowired
My my;
@Test
void contextLoads() {
System.out.println(my);
}
}
多环境配置
在实际开发中,我们的一套代码可能会被同时部署到开发、测试、生产等多个服务器中,每个环境中诸如数据库密码等这些个性化配置是避免不了的,虽然我们可以通过自动化运维部署的方式使用外部参数在服务启动时临时替换属性值,但这也意味着运维成本增高。
1、在resources目录下创建多个配置文件
application-dev.properties
application-test.properties
application-prod.properties
application.properties
spring.profiles.active=dev/test/prod
2、YAML
YAML是“YAML Ain't Markup Language YAML不是一种标记语言”的外语缩写,但为了强调这种语言以数据做为中心,而不是以置标语言为重点,而用返璞词重新命名。它是一种直观的能够被电脑识别的数据序列化格式,是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。它是类似于标准通用标记语言的子集XML的数据描述语言,语法比XML简单很多。
基本原则:
1、大小写敏感
2、使用缩进表示层级关系
3、禁止使用tab缩进,只能使用空格键
4、缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
5、使用#表示注释
6、字符串可以不用引号标注
(1)使用yaml完成多环境配置:application.yaml
spring:
profiles:
active: test
---
spring:
profiles: dev
server:
port: 8080
---
spring:
profiles: test
server:
port: 8081
(2)使用yaml进行相关参数的设置
person:
name: zhangsan
age: 12
sex: 男
desc: my name is ${person.name},my age is ${person.age}
对应的实体类对象
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
// @Value("${person.name}")
private String name;
// @Value("${person.age}")
private int age;
private String sex;
// @Value("${person.desc}")
private String desc;
public Person() {
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
3、@ConfigurationProperties 与 @Value 对比
功能 | @ConfigurationProperties | @Value |
---|---|---|
松散绑定 | 是 | 否 |
元数据支持 | 是 | 否 |
spEL表达式 | 否 | 是 |
thymeleaf模板引擎的使用
在早期开发的时候,我们完成的都是静态页面也就是html页面,随着时间轴的发展,慢慢的引入了jsp页面,当在后端服务查询到数据之后可以转发到jsp页面,可以轻松的使用jsp页面来实现数据的显示及交互,jsp有非常强大的功能,但是,在使用springboot的时候,整个项目是以jar包的方式运行而不是war包,而且还嵌入了tomcat容器,因此,在默认情况下是不支持jsp页面的。如果直接以纯静态页面的方式会给我们的开发带来很大的麻烦,springboot推荐使用模板引擎。
模板引擎有很多种,jsp,freemarker,thymeleaf,模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,我们来组装一些数据,我们把这些数据找到。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过不同的模板引擎语法不同而已,下面重点学习下springboot推荐使用的thymeleaf模板引擎,语法简单且功能强大
1、thymeleaf的介绍
官网地址:https://www.thymeleaf.org/
thymeleaf在github的地址:https://github.com/thymeleaf/thymeleaf
中文网站:https://raledong.gitbooks.io/using-thymeleaf/content/
导入依赖:
<!--thymeleaf模板-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
在springboot中有专门的thymeleaf配置类:ThymeleafProperties
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
/**
* Whether to check that the template exists before rendering it.
*/
private boolean checkTemplate = true;
/**
* Whether to check that the templates location exists.
*/
private boolean checkTemplateLocation = true;
/**
* Prefix that gets prepended to view names when building a URL.
*/
private String prefix = DEFAULT_PREFIX;
/**
* Suffix that gets appended to view names when building a URL.
*/
private String suffix = DEFAULT_SUFFIX;
/**
* Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
*/
private String mode = "HTML";
/**
* Template files encoding.
*/
private Charset encoding = DEFAULT_ENCODING;
/**
* Whether to enable template caching.
*/
private boolean cache = true;
2、thymeleaf使用模板
在java代码中写入如下代码:
@RequestMapping("/hello")
public String hello(Model model){
model.addAttribute("msg","Hello");
//classpath:/templates/hello.html
return "hello";
}
html页面中写入如下代码:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<h1>Hello</h1>
<div th:text="${msg}"></div>
</body>
</html>
3、thymeleaf的表达式语法
Simple expressions:
Variable Expressions: ${...}
Selection Variable Expressions: *{...}
Message Expressions: #{...}
Link URL Expressions: @{...}
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、thymeleaf实例演示
1、th的常用属性值
- 一、th:text :设置当前元素的文本内容,相同功能的还有th:utext,两者的区别在于前者不会转义html标签,后者会。优先级不高:order=7
- 二、th:value:设置当前元素的value值,类似修改指定属性的还有th:src,th:href。优先级不高:order=6
- 三、th:each:遍历循环元素,和th:text或th:value一起使用。注意该属性修饰的标签位置,详细往后看。优先级很高:order=2
- 四、th:if:条件判断,类似的还有th:unless,th:switch,th:case。优先级较高:order=3
- 五、th:insert:代码块引入,类似的还有th:replace,th:include,三者的区别较大,若使用不恰当会破坏html结构,常用于公共代码块提取的场景。优先级最高:order=1
- 六、th:fragment:定义代码块,方便被th:insert引用。优先级最低:order=8
- 七、th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4
- 八、th:attr:修改任意属性,实际开发中用的较少,因为有丰富的其他th属性帮忙,类似的还有th:attrappend,th:attrprepend。优先级一般:order=5
thymeleaf.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${thText}"></p>
<p th:utext="${thUText}"></p>
<input type="text" th:value="${thValue}">
<div th:each="message:${thEach}">
<p th:text="${message}"></p>
</div>
<div>
<p th:text="${message}" th:each="message:${thEach}"></p>
</div>
<p th:text="${thIf}" th:if="${not #strings.isEmpty(thIf)}"></p>
<div th:object="${thObject}">
<p>name:<span th:text="*{name}"/></p>
<p>age:<span th:text="*{age}"/></p>
<p>gender:<span th:text="*{gender}"/></p>
</div>
</body>
</html>
ThymeleafController.java
package com.mashibing.controller;
import com.mashibing.entity.Person;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ThymeleafController {
@RequestMapping("thymeleaf")
public String thymeleaf(ModelMap map){
map.put("thText","th:text设置文本内容 <b>加粗</b>");
map.put("thUText","th:utext 设置文本内容 <b>加粗</b>");
map.put("thValue","thValue 设置当前元素的value值");
map.put("thEach","Arrays.asList(\"th:each\", \"遍历列表\")");
map.put("thIf","msg is not null");
map.put("thObject",new Person("zhangsan",12,"男"));
return "thymeleaf";
}
}
2、标准表达式语法
${...} 变量表达式,Variable Expressions
*{...} 选择变量表达式,Selection Variable Expressions
一、可以获取对象的属性和方法
二、可以使用ctx,vars,locale,request,response,session,servletContext内置对象
session.setAttribute("user","zhangsan");
th:text="${session.user}"
三、可以使用dates,numbers,strings,objects,arrays,lists,sets,maps等内置方法
standardExpression.html
<!--
一、strings:字符串格式化方法,常用的Java方法它都有。比如:equals,equalsIgnoreCase,length,trim,toUpperCase,toLowerCase,indexOf,substring,replace,startsWith,endsWith,contains,containsIgnoreCase等
二、numbers:数值格式化方法,常用的方法有:formatDecimal等
三、bools:布尔方法,常用的方法有:isTrue,isFalse等
四、arrays:数组方法,常用的方法有:toArray,length,isEmpty,contains,containsAll等
五、lists,sets:集合方法,常用的方法有:toList,size,isEmpty,contains,containsAll,sort等
六、maps:对象方法,常用的方法有:size,isEmpty,containsKey,containsValue等
七、dates:日期方法,常用的方法有:format,year,month,hour,createNow等
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaf内置方法</title>
</head>
<body>
<h3>#strings </h3>
<div th:if="${not #strings.isEmpty(Str)}" >
<p>Old Str : <span th:text="${Str}"/></p>
<p>toUpperCase : <span th:text="${#strings.toUpperCase(Str)}"/></p>
<p>toLowerCase : <span th:text="${#strings.toLowerCase(Str)}"/></p>
<p>equals : <span th:text="${#strings.equals(Str, 'blog')}"/></p>
<p>equalsIgnoreCase : <span th:text="${#strings.equalsIgnoreCase(Str, 'blog')}"/></p>
<p>indexOf : <span th:text="${#strings.indexOf(Str, 'r')}"/></p>
<p>substring : <span th:text="${#strings.substring(Str, 2, 4)}"/></p>
<p>replace : <span th:text="${#strings.replace(Str, 'it', 'IT')}"/></p>
<p>startsWith : <span th:text="${#strings.startsWith(Str, 'it')}"/></p>
<p>contains : <span th:text="${#strings.contains(Str, 'IT')}"/></p>
</div>
<h3>#numbers </h3>
<div>
<p>formatDecimal 整数部分随意,小数点后保留两位,四舍五入: <span th:text="${#numbers.formatDecimal(Num, 0, 2)}"/></p>
<p>formatDecimal 整数部分保留五位数,小数点后保留两位,四舍五入: <span th:text="${#numbers.formatDecimal(Num, 5, 2)}"/></p>
</div>
<h3>#bools </h3>
<div th:if="${#bools.isTrue(Bool)}">
<p th:text="${Bool}"></p>
</div>
<h3>#arrays </h3>
<div th:if="${not #arrays.isEmpty(Array)}">
<p>length : <span th:text="${#arrays.length(Array)}"/></p>
<p>contains : <span th:text="${#arrays.contains(Array,2)}"/></p>
<p>containsAll : <span th:text="${#arrays.containsAll(Array, Array)}"/></p>
</div>
<h3>#lists </h3>
<div th:if="${not #lists.isEmpty(List)}">
<p>size : <span th:text="${#lists.size(List)}"/></p>
<p>contains : <span th:text="${#lists.contains(List, 0)}"/></p>
<p>sort : <span th:text="${#lists.sort(List)}"/></p>
</div>
<h3>#maps </h3>
<div th:if="${not #maps.isEmpty(hashMap)}">
<p>size : <span th:text="${#maps.size(hashMap)}"/></p>
<p>containsKey : <span th:text="${#maps.containsKey(hashMap, 'thName')}"/></p>
<p>containsValue : <span th:text="${#maps.containsValue(hashMap, '#maps')}"/></p>
</div>
<h3>#dates </h3>
<div>
<p>format : <span th:text="${#dates.format(Date)}"/></p>
<p>custom format : <span th:text="${#dates.format(Date, 'yyyy-MM-dd HH:mm:ss')}"/></p>
<p>day : <span th:text="${#dates.day(Date)}"/></p>
<p>month : <span th:text="${#dates.month(Date)}"/></p>
<p>monthName : <span th:text="${#dates.monthName(Date)}"/></p>
<p>year : <span th:text="${#dates.year(Date)}"/></p>
<p>dayOfWeekName : <span th:text="${#dates.dayOfWeekName(Date)}"/></p>
<p>hour : <span th:text="${#dates.hour(Date)}"/></p>
<p>minute : <span th:text="${#dates.minute(Date)}"/></p>
<p>second : <span th:text="${#dates.second(Date)}"/></p>
<p>createNow : <span th:text="${#dates.createNow()}"/></p>
</div>
</body>
</html>
ThymeleafController.java
@RequestMapping("standardExpression")
public String standardExpression(ModelMap map){
map.put("Str", "Blog");
map.put("Bool", true);
map.put("Array", new Integer[]{1,2,3,4});
map.put("List", Arrays.asList(1,3,2,4,0));
Map hashMap = new HashMap();
hashMap.put("thName", "${#...}");
hashMap.put("desc", "变量表达式内置方法");
map.put("Map", hashMap);
map.put("Date", new Date());
map.put("Num", 888.888D);
return "standardExpression";
}
@{...} 链接表达式,Link URL Expressions
<!--
不管是静态资源的引用,form表单的请求,凡是链接都可以用@{...} 。这样可以动态获取项目路径,即便项目名变了,依然可以正常访问
链接表达式结构
无参:@{/xxx}
有参:@{/xxx(k1=v1,k2=v2)} 对应url结构:xxx?k1=v1&k2=v2
引入本地资源:@{/项目本地的资源路径}
引入外部资源:@{/webjars/资源在jar包中的路径}
-->
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/main/css/123.css}" rel="stylesheet">
<form class="form-login" th:action="@{/user/login}" th:method="post" >
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
#{...} 消息表达式,Message Expressions
<!--
消息表达式一般用于国际化的场景。结构:th:text="#{msg}"
-->
~{...} 代码块表达式,Fragment Expressions
fragment.html
<!--
支持两种语法结构
推荐:~{templatename::fragmentname}
支持:~{templatename::#id}
templatename:模版名,Thymeleaf会根据模版名解析完整路:/resources/templates/templatename.html,要注意文件的路径。
fragmentname:片段名,Thymeleaf通过th:fragment声明定义代码块,即:th:fragment="fragmentname"
id:HTML的id选择器,使用时要在前面加上#号,不支持class选择器。
代码块表达式的使用
代码块表达式需要配合th属性(th:insert,th:replace,th:include)一起使用。
th:insert:将代码块片段整个插入到使用了th:insert的HTML标签中,
th:replace:将代码块片段整个替换使用了th:replace的HTML标签中,
th:include:将代码块片段包含的内容插入到使用了th:include的HTML标签中,
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--th:fragment定义代码块标识-->
<footer th:fragment="copy">
2019 The Good Thymes Virtual Grocery
</footer>
<!--三种不同的引入方式-->
<div th:insert="fragment::copy"></div>
<div th:replace="fragment::copy"></div>
<div th:include="fragment::copy"></div>
<!--th:insert是在div中插入代码块,即多了一层div-->
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<!--th:replace是将代码块代替当前div,其html结构和之前一致-->
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<!--th:include是将代码块footer的内容插入到div中,即少了一层footer-->
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>
5、国际化的配置
在很多应用场景下,我们需要实现页面的国际化,springboot对国际化有很好的支持, 下面来演示对应的效果。
1、在idea中设置统一的编码格式,file->settings->Editors->File Encoding,选择编码格式为utf-8
2、在resources资源文件下创建一个i8n的目录,创建一个login.properties的文件,还有login_zh_CN.properties,idea会自动识别国际化操作
3、创建三个不同的文件,名称分别是:login.properties,login_en_US.properties,login_zh_CN.properties
内容如下:
#login.properties
login.password=密码1
login.remmber=记住我1
login.sign=登录1
login.username=用户名1
#login_en_US.properties
login.password=Password
login.remmber=Remember Me
login.sign=Sign In
login.username=Username
#login_zh_CN.properties
login.password=密码~
login.remmber=记住我~
login.sign=登录~
login.username=用户名~
4、配置国际化的资源路径
spring:
messages:
basename: i18n/login
5、编写html页面
初始html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<form action="" method="post">
<label >Username</label>
<input type="text" name="username" placeholder="Username" >
<label >Password</label>
<input type="password" name="password" placeholder="Password" >
<br> <br>
<div>
<label>
<input type="checkbox" value="remember-me"/> Remember Me
</label>
</div>
<br>
<button type="submit">Sign in</button>
<br> <br>
<a>中文</a>
<a>English</a>
</form>
</body>
</html>
修改后的页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<form action="" method="post">
<label th:text="#{login.username}">Username</label>
<input type="text" name="username" placeholder="Username" th:placeholder="#{login.username}">
<label th:text="#{login.password}">Password</label>
<input type="password" name="password" placeholder="Password" th:placeholder="#{login.password}">
<br> <br>
<div>
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remmber}]]
</label>
</div>
<br>
<button type="submit" th:text="#{login.sign}">Sign in</button>
<br> <br>
<a>中文</a>
<a>English</a>
</form>
</body>
</html>
可以看到通过浏览器的切换语言已经能够实现,想要通过超链接实现的话,如下所示:
添加WebMVCConfig.java代码
package com.mashibing.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
}
@Bean
public LocaleResolver localeResolver(){
return new NativeLocaleResolver();
}
protected static class NativeLocaleResolver 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) {
}
}
}
login.html页面修改为:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<form action="" method="post">
<label th:text="#{login.username}">Username</label>
<input type="text" name="username" placeholder="Username" th:placeholder="#{login.username}">
<label th:text="#{login.password}">Password</label>
<input type="password" name="password" placeholder="Password" th:placeholder="#{login.password}">
<br> <br>
<div>
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remmber}]]
</label>
</div>
<br>
<button type="submit" th:text="#{login.sign}">Sign in</button>
<br> <br>
<a th:href="@{/login.html(language='zh_CN')}">中文</a>
<a th:href="@{/login.html(language='en_US')}">English</a>
</form>
</body>
</html>
国际化的源码解释:
//MessageSourceAutoConfiguration
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
public MessageSourceAutoConfiguration() {
}
@Bean
@ConfigurationProperties(prefix = "spring.messages") //我们的配置文件可以直接放在类路径下叫: messages.properties, 就可以进行国际化操作了
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//设置国际化文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
}
//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
@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);
}