JavaEE

JavaWeb了解之SpringBoot篇1

2022-11-17  本文已影响0人  平安喜乐698
目录
  1. 第一个SpringBoot项目(HelloWorld)
  2. starter机制
  3. YAML标记语言
  4. 配置文件
  5. 日志(记录运行情况、定位问题)
  6. 静态资源映射
  7. Thymeleaf模板引擎
  8. 国际化

用于简化Spring项目搭建和开发的开源框架(使开发者更专注于业务逻辑)。

SpringBoot
  1. 以Spring为基础,使用更简单、功能更丰富、性能更稳定健壮。
  2. 提供了大量开箱即用的依赖包(starter机制):自动管理依赖包中的依赖(简化了复杂的依赖包管理);提供了大量默认/自动配置(省去了大量的XML配置内容),不需要任何形式的配置即可实现Spring的所有配置(可以通过配置文件修改默认配置)。
  3. 内嵌了Servlet容器(如:Tomcat、Jetty、Undertow等),应用无需打成WAR包 。
  4. 可在终端执行java–jar xxx.jar命令来独立运行SpringBoot项目。
  5. 可对正在运行的项目提供监控。

随着微服务技术的流行,SpringBoot也成了时下炙手可热的技术。

1. 第一个SpringBoot项目(HelloWorld)

===》1. 创建项目
 方式1(创建Maven项目)
  1. 修改pom.xml文件(添加SpringBoot依赖包)
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.5</version>
    <relativePath/>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  2. 创建HelloWorldApplication.java文件(在com.sst.cx包下)
  package com.sst.cx;
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;
  @SpringBootApplication
  public class HelloWorldApplication {
    public static void main( String[] args ) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }
  }
 方式2(创建Spring项目)推荐
  会自动在pom.xml添加SpringBoot依赖,并自动在com.sst.cx包下创建项目名+Application.java文件(内容同上)。

===》2. 创建HelloController.java文件(在com.sst.cx.controller包下)
package com.sst.cx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
    @ResponseBody  // 将方法的返回内容作为页面内容。不使用该注解时,会使用该方法的返回值对应的同名html文件作为显示页面。
    @RequestMapping("/hello")  // 映射路径为/hello,在浏览器中使用http://localhost/hello可访问到。
    public String hello() {
        return "Hello World!";
    }
}

===》3. 运行项目,在浏览器中输入http://127.0.0.1:8080/hello。
  内置了Tomcat(不再需要部署到Tomcat),可以直接运行。
创建Maven项目
创建Spring项目-步骤1
创建Spring项目-步骤2
SpringBoot项目结构
运行结果

2. starter机制

Spring项目在创建后想要运行,需要:导入各种依赖jar包、添加许多xml配置、部署到Tomcat服务器中。
SpringBoot项目在创建后可以直接运行(不用编写任何代码、不用进行任何配置),这都要归功于starter机制。

SpringBoot将企业应用研发中的各种场景都抽取出来 做成一个个的starter依赖包(整合了该场景下所有可能用到的依赖,并提供了大量的默认/自动配置),开发者只需要在Maven的pom.xml中添加相应的starter即可(SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置)。

1. SpringBoot官方提供的starter依赖包以spring-boot-starter-xxx方式命名。
    spring-boot-starter-parent
    spring-boot-starter-web
    spring-boot-starter-test
    spring-boot-starter-redis
    spring-boot-starter-data-mongodb
    spring-boot-starter-data-elasticsearch
2. 自定义的starter依赖包(第三方技术厂商提供 或 开发员自己创建)以xxx-spring-boot-starter方式命名。
    druid-spring-boot-starter
    mybatis-spring-boot-starter
如果pom.xml中提示找不到该依赖时,点击Maven刷新按钮
  1. spring-boot-starter-parent

所有SpringBoot项目的父级依赖(即所有SpringBoot项目都需要添加该父依赖):统一管理项目内的部分常用依赖;统一管理其他starter的版本(该依赖又被称为SpringBoot的版本仲裁中心)。

该依赖包主要提供了以下特性:
  1. 默认JDK版本(Java 8)
  2. 默认字符集(UTF-8)
  3. 依赖管理功能
  4. 资源过滤
  5. 默认插件配置
  6. 识别 application.properties 和 application.yml 类型的配置文件
===》查看源码可知
其有一个父级依赖:spring-boot-dependencies。
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.4.5</version>
    </parent>

查看spring-boot-dependencies的pom.xml内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.5</version>
    <packaging>pom</packaging>
    ....
    <!-- 负责定义依赖、插件的版本号 -->
    <properties>
        <activemq.version>5.16.1</activemq.version>
        <antlr2.version>2.7.7</antlr2.version>
        <appengine-sdk.version>1.9.88</appengine-sdk.version>
        <artemis.version>2.15.0</artemis.version>
        <aspectj.version>1.9.6</aspectj.version>
        <assertj.version>3.18.1</assertj.version>
        <atomikos.version>4.0.6</atomikos.version>
        ....
    </properties>
    <!-- 负责管理依赖 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.activemq</groupId>
                <artifactId>activemq-amqp</artifactId>
                <version>${activemq.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.activemq</groupId>
                <artifactId>activemq-blueprint</artifactId>
                <version>${activemq.version}</version>
            </dependency>
            ...
        </dependencies>
    </dependencyManagement>
    <build>
        <!-- 负责管理插件 -->
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <version>${build-helper-maven-plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.flywaydb</groupId>
                    <artifactId>flyway-maven-plugin</artifactId>
                    <version>${flyway.version}</version>
                </plugin>
                ...
            </plugins>
        </pluginManagement>
    </build>
</project>
  1. spring-boot-starter-web
为Web开发提供了所有依赖:
  1. spring-boot-starter(核心启动器)
    spring-boot
    spring-boot-autoconfigure
    spring-boot-starter-logging(log日志)
      logback-classic
      log4j-to-slf4j
      jul-to-slf4j
    jakarta.annotation-api
    snakeyaml
  2. spring-boot-starter-tomcat(Tomcat服务器)
  3. spring-boot-starter-json(jackson)
  4. spring-web(SpringFramework)
    spring-beans
  5. spring-webmvc
    spring-aop
    spring-context
    spring-expression

  2. SpringMVC,为SpringMVC提供了大量默认配置,在SpringMVC基础上添加了以下特性:
    1. 引入了ContentNegotiatingViewResolver和BeanNameViewResolver(视图解析器)
    2. 对包括WebJars在内的静态资源的支持
    3. 自动注册Converter、GenericConverter、Formatter(转换器和格式化器)
    4. 对HttpMessageConverters的支持(Spring MVC中用于转换HTTP请求和响应的消息转换器)
    5. 自动注册 MessageCodesResolver(用于定义错误代码生成规则)
    6. 支持对静态首页(index.html)的访问
    7. 自动使用 ConfigurableWebBindingInitializer


使用(只需在pom.xml中添加依赖:spring-boot-starter-web ):
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/>
    </parent>
    <dependencies>
        <!--导入 spring-boot-starter-web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        ...
    </dependencies>

在终端执行如下命令,可查看项目的依赖树
  mvn dependency:tree

自定义starter(命名规则:xxx-spring-boot-starter)

将独立于业务代码之外的功能模块封装成一个starter,便于复用。

步骤:
===》1. 创建一个SpringBoot项目,修改pom文件
  添加spring-boot-autoconfigure依赖,可根据功能添加其他依赖。
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
===》2. 创建HelloProperties.java配置类
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
===》3. 创建HelloService.java
public class HelloService {
    private String name;
    private int age;
    public HelloService(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void hello() {
        System.out.println(name + "  age:" + age);
    }
}
===》4. 创建MyAutoConfiguration.java自动配置类
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class MyAutoConfiguration {
    @Autowired
    private HelloProperties helloProperties;
    @Bean
    public HelloService helloService() {
        return new HelloService(helloProperties.getName(), helloProperties.getAge());
    }
}
===》5. 创建spring.factories文件(在resources/META-INF/目录下)
  在该文件中配置上面创建的自动配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sst.cx.config.MyAutoConfiguration
===》6. 在application.properties配置文件中可进行默认配置。 
===》7. 打包 
  放置到测试项目的resources/lib目录下。

使用自定义的starter

===》1. 创建一个SpringBoot项目(勾选Web依赖),修改pom.xml文件
  添加自定义的starter依赖
<dependency>
   <groupId>com.sst.cx</groupId>
   <artifactId>hello-spring-boot-starter</artifactId>
   <version>1.0-SNAPSHOT</version>
   <scope>system</scope>
   <systemPath>${project.basedir}/src/main/resources/lib/hello-spring-boot-starter-0.0.1-SNAPSHOT.jar</systemPath>
</dependency>
===》2. 在appllication.properties/yml配置文件中,添加
hello.name=zhangsan
hello.age=12
===》3. 测试(TestController.java)
@Controller
public class TestController {
    @Autowired
    private HelloService helloService;
    @ResponseBody
    @RequestMapping("/hello")
    public String hello() {
        helloService.hello();
        return "success";
    }
}
在浏览器中访问http://127.0.0.1:8080/test,控制台会输出:zhangsan  age:12

3. YAML标记语言(以数据为中心,比xml、json更适合作为配置文件)

只需在SpringBoot项目中引入spring-boot-starter-web或spring-boot-starter(二者都集成了SnakeYAML库)就可以使用YAML(以 .yml 或 .yaml 结尾)作为配置文件。

语法规则
  1. 使用缩进表示层级关系(不允许使用Tab键,只允许使用空格;空格数不重要,但同级元素必须左侧对齐)。
  2. 大小写敏感。
  3. 使用"key:[空格]value"形式(空格不能省略)表示一个键值对。
    例: url: www.baidu.com
  4. 支持3种数据结构(可任意组合嵌套):
    1. 对象(键值对的集合:对象的每个属性对应一个键值对)
      写法1. 普通写法(使用缩进表示:对象与属性的层级关系) 
        website: 
          name: 张三
          url: www.baidu.com
      写法2. 行内写法
        website: {name: 张三,url: www.baidu.com}
    2. 数组(一组按次序排列的值)
      写法1. 普通写法(使用-表示:数组中的元素)
        pets:
          -dog
          -cat
          -pig
      写法2. 行内写法
        pets: [dog,cat,pig]
    3. 字面量(单个的不可拆分的值,如:数字、字符串、布尔值、日期)
      直接写在键值对的value中即可,默认情况下字符串是不需要使用单引号或双引号的。 
        若字符串使用了单引号,则会对字符串的特殊字符进行转义(如:"hello\nworld"则会输出hello\nworld)。
        若字符串使用了双引号,则不会转义(如:"hello\nworld"则会进行换行)。
  5. 文件组织结构
    一个YAML文件由一个或多个相互独立的文档组成,文档之间使用“---”作为分隔符(只包含一个文档时可省略)。

例(hello.yaml)

spring:
  profiles: dev
  datasource:
    url: jdbc:mysql://localhost:3306/Test
    username: root
    password: 12345678
    driver-class-name: com.mysql.cj.jdbc.Driver
---
website:
  name: bianchengbang
  url: www.biancheng.net
---
name: "张三 \n 李四"

4. 配置文件

  1. 默认配置文件
SpringBoot项目启动时会将以下5个位置的application.properties或apllication.yml文件(文件名固定)作为默认配置文件,并读取配置内容。
  1. file:./config/*/
  2. file:./config/
  3. file:./
  4. classpath:/config/
  5. classpath:/
说明:
  1. 优先级依次降低,相同位置的application.properties的优先级高于application.yml。
  2. file: 指项目的根目录;classpath: 指项目的类路径(即resources目录)。
  3. 通常只使用第5种(在resources目录下创建配置文件,并添加内容来覆盖默认配置)。
  4. Maven项目打包时,位于项目根目录下的配置文件无法被打包进项目的JAR包(即在jar包中失效)。  
    解决(3方式):
      1. 在IDEA的运行配置(Run/Debug Configuration)中,添加虚拟机参数 -Dspring.config.additional-location=/my-application.yml,指定外部配置文件。
      2. 在IDEA的运行配置(Run/Debug Configuration)中,添加程序运行参数 --spring.config.additional-location=/my-application.yml,指定外部配置文件。
      3. 在主启动类中调用System.setProperty()方法添加系统属性spring.config.additional-location,指定外部配置文件。

===》创建项目,在项目根目录下、类路径的config目录下、类路径下分别创建一个配置文件(application.yml文件)。

1. 项目根路径下
#上下文路径为 /abc
server:
  servlet:
    context-path: /abc

2. 类路径的config目录下
#端口号为8084
#上下文路径为 /helloWorld
server:
  port: 8084
  servlet:
    context-path: /helloworld

3. 类路径下
#默认配置
server:
  port: 8080

===》MyController.java
    package com.sst.cx.controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    public class MyController {
        @ResponseBody
        @RequestMapping("/test")
        public String hello() {
            return "hello Spring Boot!";
        }
    }

===》根据优先级可知:服务器端口为8084;上下文路径为/abc
使用浏览器访问http://localhost:8084/abc/test
  1. 外部配置文件

除了默认配置文件,SpringBoot还可以加载项目外部的配置文件。

指定外部配置文件的路径(2种方式)
  1. spring.config.location(将只加载外部配置文件,默认配置文件会失效)
    使用命令:java -jar {JAR}  --spring.config.location={外部配置文件全路径}
  2. spring.config.additional-location(外部配置文件的优先级最高) 
    使用命令:java -jar {JAR}  --spring.config.additional-location={外部配置文件全路径}
  1. 配置加载顺序

SpringBoot项目不仅可以通过配置文件进行配置,还可以通过环境变量、命令行参数等形式进行配置。

SpringBoot会加载以下所有形式的配置(优先级由高到低):
  1. 命令行参数
    SpringBoot中的所有配置都可以通过命令行参数进行指定。
    命令格式:java -jar {Jar文件名} --{参数1}={参数值1} --{参数2}={参数值2}
  2. 来自java:comp/env的JNDI 属性
  3. Java系统属性(System.getProperties())
  4. 操作系统的环境变量
  5. RandomValuePropertySource配置的random.*属性值
  6. 配置文件(.yml/.properties文件)
    SpringBoot启动时,会自动加载JAR包内部及JAR包所在目录指定位置的配置文件(.yml/.properties文件)。
    下图(配置文件的加载顺序)的说明:
      1. /myBoot:表示JAR包所在目录
      2. /childDir:表示JAR包所在目录下config目录的子目录
      3. JAR:表示SpringBoot项目打包生成的JAR包;
      4. 数字:表示该配置文件的优先级,数字越小 优先级越高 越先被加载(后加载的相同属性会被忽略)。
      5. 同一位置下,Properties文件优先级高于YAML文件。
  7. @Configuration注解修饰的配置类上的@PropertySource指定的配置文件
  8. 通过SpringApplication.setDefaultProperties指定的默认属性
配置文件的加载顺序

例(命令行参数)

java -jar hello-0.0.1-SNAPSHOT.jar --server.port=8081 --server.servlet.context-path=/hello
说明:
  1. server.port:指定服务器端口
  2. server.servlet.context:上下文路径(项目的访问路径)
  1. 配置绑定(把配置文件中的值绑定到JavaBean对象的属性中)
步骤:
1. 将配置信息存放在配置文件中。
  如果将所有的配置都集中在application.properties/yml配置文件中,会十分臃肿、难以维护。通常会将与SpringBoot无关的自定义配置提取到一个单独的配置文件(如:resources/person.properties)中,然后给类添加@PropertySource注解指向该配置文件。
/*
  例:
    @Component  
    @ConfigurationProperties(prefix="person")  // 将JavaBean属性和配置文件的值进行绑定
    @PropertySource(value = "classpath:person.properties")  // 指定配置文件
    public class Person{}
*/

2. 在代码中给类添加注解进行绑定。
  1. @ConfigurationProperties注解(修饰类:用于将配置文件的多个配置绑定到类的属性中)
    支持松散绑定/松散语法(如:配置文件中的person.firstName、person.first-name、person.first_name、PERSON_FIRST_NAME都可以绑定到Person类的firstName属性)。
    不支持SpEL表达式。
    支持所有类型数据的封装(如: 基础数据类型、类、Map、List、Set)。
    例:
      @Component  
      @ConfigurationProperties(prefix="person")
      public class Person{}
  2. @Value注解(修饰属性,用于将配置文件中的某一个配置绑定到属性中)
    不支持松散绑定。
    支持SpEL表达式。
    只支持基本数据类型(字符串、布尔值、整数)。
    例:
      @Component
      public class Person {
        @Value("${person.name}")
        private String name;
      }

===》在application.yml配置文件中(若为.properties文件则为:person.age=10形式)
  person:
    name: 张三
    age: 10
    boss: false
    birth: 1949/10/1
    maps: { k1: v1,k2: 12 }
    lists:
      ‐ 张三
      ‐ 李四
    dog:
      name: 初一
      age: 2 
===》Person类
@Component  // 类必须在Ioc容器中。
@ConfigurationProperties(prefix="person")  // 将配置文件中以person前缀开头的配置绑定到类的属性中。
public class Person{
    private String name;
    private Integer age;
    private Boolean boss;
    private Date birth;
    private Map<String, Object> maps;
    private List<Object> lists;
    private Dog dog;
        ...省略set、get方法
}
===》Dog类
public class Dog {
    private String name;
    private String age;
        ...省略set、get方法
}
===》HelloController控制器类
@Controller
public class HelloController {
    @Autowired
    private Person person;
    @ResponseBody
    @RequestMapping("/hello")
    public Person hello() {
        return person;
    }
}
在浏览器中输入:http://localhost:8081/hello
  1. 导入Spring配置(默认情况下,Spring的.xml配置文件不会被SpringBoot识别)
2种方式:
  1. 使用@ImportResource注解加载Spring的xml配置文件。
    首先在类路径(resources目录)下创建beans.xml,然后在主启动类中添加@ImportResource(locations = {"classpath:/beans.xml"})
  2. 使用全注解方式加载Spring配置。
    在使用@Configuration注解修饰的配置类中,使用@Bean注解向容器中注入JavaBean。
    例:
      @Configuration  // 定义一个配置类(替代.xml配置文件)
      public class MyAppConfig {
          // @Bean修饰的方法会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类扫描,构建Bean并注入到容器中。
          // 等价于 <bean id="personService" class="PersonServiceImpl"></bean>
          @Bean  // 等价于xml文件的Bean元素,id为方法名,class为返回值类型
          public PersonService personService() {
              return new PersonServiceImpl();
          }
      }

例(@ImportResource注解 方式加载)

===》beans.xml(resources目录下)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="personService" class="com.sst.cx.service.impl.PersonServiceImpl"></bean>
</beans>

===》PersonService.java
public interface PersonService {
    public Person getPersonInfo();
}
===》PersonServiceImpl.java
public class PersonServiceImpl implements PersonService {
    @Autowired
    private Person person;
    @Override
    public Person getPersonInfo() {
        return person;
    }
}
===》HelloworldApplicationTests.java(测试文件)
@SpringBootTest
class HelloworldApplicationTests {
    @Autowired
    Person person;
    // IOC 容器
    @Autowired
    ApplicationContext ioc;
    @Test
    public void testHelloService() {
        // 校验 IOC 容器中是否包含组件 personService
        boolean b = ioc.containsBean("personService");
        if (b) {
            System.out.println("personService 已经添加到 IOC 容器中");
        } else {
            System.out.println("personService 没添加到 IOC 容器中");
        }
    }
    @Test
    void contextLoads() {
        System.out.println(person);
    }
}
===》HelloworldApplication.java(在主启动类中添加@ImportResource注解)
@ImportResource(locations = {"classpath:/beans.xml"})  // 将beans.xml加载到项目中
@SpringBootApplication
public class HelloworldApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloworldApplication.class, args);
    }
}
  1. 配置Profile(多环境)
通常程序会运行在多套环境(开发、测试、生产)下,每套环境的配置不同(如:数据库地址、服务器端口、日志级别 等)。如果每次打包时靠手动修改代码来实现,繁琐且易错。可使用Profile功能来实现动态切换环境(2种方式):
  方式1. yml多文档方式
    在一个yml文件中每个分隔符隔开的文档对应一套环境。
  方式2. 多profile文件方式
    创建多个环境配置文件(每个环境配置文件对应一套环境)。

切换环境的方法:
  1. 配置文件中手动切换
  2. 虚拟机参数: 在VM options指定: -Dspring.profiles.active=dev
  3. 命令行参数: java-jar xxx.jar --spring.profiles.active=dev

例(yml多文档方式)

在application.yml配置文件中,添加:

#默认配置
server:
  port: 8080
#切换配置
spring:
  profiles:
    active: test  #设置当前环境
---
#开发环境
server:
  port: 8081
spring:
  config:
    activate:
      on-profile: dev
---
#测试环境
server:
  port: 8082
spring:
  config:
    activate:
      on-profile: test
---
#生产环境
server:
  port: 8083
spring:
  config:
    activate:
      on-profile: prod

例(多profile文件方式)

===》创建application-dev.properties
server.port = 8080
===》创建application-test.properties
server.port = 8081
===》创建application-pro.properties
server.port = 8082
===》在application.properties配置文件中
spring.profiles.active = dev #设置当前环境,各环境配置文件的后缀。
  1. 自动配置原理

SpringBoot通过注解(而不是xml文件的方式)来实现自动/默认配置。SpringBoot在启动时从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的所有自动配置类,导入到容器中生效。
在SpringBoot项目的启动类中有一个@SpringBootApplication注解(核心注解,一个组合注解)。

===》@SpringBootApplication注解,定义如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration 
@EnableAutoConfiguration  //  
@ComponentScan(  // 
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

===》@EnableAutoConfiguration注解,定义如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
...
}
SpringBoot通过@EnableAutoConfiguration注解开启自动配置(扫描jar包下的spring.factories文件,文件中包含了自动配置类)。
该注解导入了EnableAutoConfigurationImportSelector类的selectimports方法
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    }
    通过getCandidateConfiguration方法,获取配置文件列表。
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    loadFactoryNames方法会加载所有META-INF下有spring.factories文件的jar包,并根据spring.factories文件中的配置,去加载相应的类。
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            Map<String, List<String>> result = new HashMap();
            try {
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }
                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

===》@import
该注解导入了AutoConfigurationImportSelector类,这个类中有一个很重要的方法:selectImports(),它几乎涵盖了组件自动装配的所有处理逻辑,包括获得候选配置类、配置类去重、排除不需要的配置类、过滤等,最终返回符合条件的自动配置类的全限定名数组。

例(spring.factories文件)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sst.cx.config.MyAutoConfiguration,\
com.sst.cx.config.MyAutoConfiguration2

例:
当项目中需要使用依赖jar包中类的实例,只需创建一个该类类型的属性,并使用@Autowired或@Resource注解标注(会注入到容器中)。

5. 日志(记录运行情况、定位问题)

日志框架分为两类:
  1. 日志抽象层(为日志功能提供了一套标准规范的JavaAPI)
    1. JCL(Jakarta Commons Logging)
    2. SLF4j(Simple Logging Facade for Java)目前最流行
      可灵活使用占位符进行参数占位:简化代码、可读性更好。
    3. jboss-logging
  2. 日志实现
    1. Log4j
    2. JUL(java.util.logging)
    3. Log4j2
    4. Logback(SLF4j的原生实现框架)
      和Log4j同一个作者,用来代替Log4j,拥有比Log4j更多的优点特性、更强的性能。

通常情况下,日志功能由一个日志抽象层和一个日志实现组合而成(SpringBoot选用的是:SLF4J+Logback)。
  1. 使用SLF4J
使用步骤:
  1. 在项目中导入SLF4J框架和一个日志实现框架(如:Logback)。
  2. 调用日志时,应调用日志抽象层的方法,而不是直接调用日志实现层的方法。

例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(HelloWorld.class);  
        // 调用SLF4J的info()方法,而非直接调用logback的方法。
        logger.info("Hello World");
    }
}

从下图的SLF4J官方方案可以看出:
  1. Logback作为Slf4j的原生实现框架。当项目使用SLF4J+Logback组合记录日志时,只需要引入SLF4J、Logback的Jar包即可;
  2. Log4j虽然和Logback属于同一个作者,但Log4j的出现要早于SLF4J,因而Log4j没有直接实现SLF4J。当项目使用SLF4J+Log4j组合记录日志时,不但需要引入SLF4J、Log4j 的Jar包,还必须引入它们之间的适配层(承上启下:既要实现SLF4J的方法,还要有调用Log4j的方法):slf4j-log4j12.jar。
  3. 当项目使用SLF4J+JUL组合记录日志时,与SLF4J+Log4j一样,不但需要引入SLF4J、JUL 的对应的Jar包,还要引入适配层:slf4j-jdk14.jar。

每一个日志实现框架都有自己的配置文件。使用SLF4J记录日志时,应该使用日志实现框架的配置文件。
SLF4J可以和各种日志实现框架组合使用
  1. 统一各依赖包中的日志框架
通常,一个项目会依赖于各种框架,每个框架记录日志所使用的日志框架各不相同。如:Spring Boot(slf4j+logback)、Spring(commons-logging)、Hibernate(jboss-logging)。
因此,需要统一日志框架的使用:
  1. 使用 各替换包 替换 项目中原来的全部日志实现框架(分为2步:去除原框架;引入相应的替换包)。
    替换包包含了 被替换的日志框架中的所有类(保证应用不会报错),但使用的是SLF4J的API(达到统一日主框架的目的)。
    如:log4j-over-slf4j替换Log4j、jul-to-slf4j.jar替换JUL
  2. 导入SLF4J实现。

SpringBoot项目
  spring-boot-starter(核心启动器)引入了spring-boot-starter-logging。spring-boot-starter-logging引入了 logback-classic(SLF4J的实现)、 log4j-to-slf4j(log4j的替换包)、jul-to-slf4j(JUL的替换包)。
  所以引入spring-boot-starter就完成了:引入替换包和导入SLF4J实现 这2步。引入其他三方框架依赖时,只需删除其所依赖的日志框架,即可实现日志框架的统一。例:
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-console</artifactId>
    <version>${activemq.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
SLF4J 官方给出的统一日志框架使用的方案
  1. 日志的配置(日志的级别、日志的输出格式)
  1. 默认配置
SpringBoot项目中引入spring-boot-starter(引入了SLF4J+Logback,提供了大量默认配置)就可以直接使用日志功能。

日志级别
  日志的输出都是分级别的,当一条日志信息的级别大于或等于配置文件的级别时,才会对这条日志进行记录。

输出格式
  可以通过日志参数对日志的输出格式进行修改。
序号 常见的日志级别(优先级依次升高) 描述
1 trace 追踪,指明程序运行轨迹。(很少使用)
2 debug 调试。(实际项目中一般将其作为最低级别)
3 info 输出重要的信息。(使用较多)
4 warn 警告。(使用较多)
5 error 错误信息。(使用较多)
序号 常用的输出格式 描述
1 %d{yyyy-MM-dd HH:mm:ss, SSS} 日志创建时间(年月日 时分秒 毫秒)
2 %-5level 日志级别(-5表示:左对齐且固定输出5个字符,不足则右边补0)
3 %logger 或 %c logger的名称
4 %thread 或 %t 当前线程的名称
5 %p 日志输出格式
6 %message 或 %msg 或 %m 日志内容。logger.info("message")中的message
7 %n 换行符
8 %class 或 %C Java类名
9 %file 或 %F 文件名
10 %L 出错的行号
11 %method 或 %M 方法名
12 %l 语句所在的行数(包括类名、方法名、文件名、行数)
13 hostName 本地机器名
14 hostAddress 本地ip地址
例(测试SpringBoot项目的日志默认级别)
package com.sst.cx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
        Logger logger = LoggerFactory.getLogger(SpringBootMaven2Application.class);
        logger.trace("trace 级别日志");
        logger.debug("debug 级别日志");
        logger.info("info 级别日志");
        logger.warn("warn 级别日志");
        logger.error("error 级别日志");
    }
}

通过控制台输出结果可知:
  1. SpringBoot项目的日志默认级别为:info。
  2. 日志输出内容默认包含(占用一行,依次包含):
    创建时间(yyyy-MM-dd HH:mm:ss.SSS)
    日志级别(如:WARN)
    进程 ID(如:945)
    分隔符:---
    线程名:方括号括起来(如:[     main])
    logger的名称(如:com.sst.cx.HelloApplication)
    日志内容(如:warn 级别日志)
例:
2015-10-29 11:37:21.940  INFO 945 --- [           main] com.sst.cx.HelloApplication   : info 级别日志
  1. 修改默认配置
在resources目录下创建application.properties/yml,按需求添加如下内容来覆盖默认配置:

#日志级别
logging.level.net.biancheng.www=trace
#设置日志输出的位置-相对路径(会在项目根目录/my-log/myLog下自动生成spring.log文件)
#logging.file.path=my-log/myLog
#设置日志输出的位置-绝对路径(会在项目所在磁盘根目录/Users/cx/spring-boot/logging下自动生成spring.log文件)
logging.file.path=/Users/cx/spring-boot/logging
#控制台的日志输出格式
logging.pattern.console=%d{yyyy-MM-dd hh:mm:ss} [%thread] %-5level %logger{50} - %msg%n
#spring.log日志文件输出格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} === - %msg%n
  1. 自定义日志配置
修改application.porperties/yml文件只能修改个别日志配置,若修改更多的配置或使用更高级的功能,则需要通过日志实现框架的配置文件(Spring官方提供,开发者只需将指定的配置文件放置到项目的类路径即resourecs目录下即可)来进行配置(按需修改)。
日志框架 配置文件
Logback logback-spring.xml、logback-spring.groovy、logback.xml、logback.groovy
Log4j2 log4j2-spring.xml、log4j2.xml
JUL (Java Util Logging) logging.properties
日志框架的配置文件分为2类(在使用时大不相同):
  1. 普通日志配置文件(即不带srping标识),如:logback.xml。
    放置在项目的类路径下后,配置文件会跳过SpringBoot直接被日志框架加载。
  2. 带有spring标识的日志配置文件(SpringBoot推荐该方式),如:logback-spring.xml、log4j2-spring.xml。
    放置在项目的类路径下后,配置文件不会直接被日志框架加载,而是由SpringBoot对它们进行解析,这样就能使用SpringBoot的高级功能Profile(实现在不同的环境中使用不同的日志配置)。 
例(logback.xml)普通日志配置文件
  将logback.xml复制到SpringBoot项目的类路径下(resources目录下),该配置文件配置内容如下

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:设置为true(默认),则当配置文件发生改变后会被重新加载。
scanPeriod:scan为true时此属性生效。监测配置文件是否发生修改的时间间隔(默认1min)。如果没有给出时间单位,默认单位是毫秒。
debug:设置为true(默认值为false)时会打印出logback内部日志信息(实时查看logback运行状态)。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 -->
    <property name="LOG_HOME" value="/Users/cx/spring-app/log"/>
    <!-- 定义日志文件名称 -->
    <property name="appName" value="cx-spring-boot-logging"></property>
    <!-- 定义控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志输出格式:
           %d:日期时间。
           %thread:线程名。
           %-5level:级别,从左显示且5个字符宽度。
           %logger{50}:logger名字最长50个字符,否则按照句点分割。
           %msg:日志消息。
           %n:换行符。
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread]**************** %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责发出滚动。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!--
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件时,那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!--
            当日志文件超过maxFileSize指定的大小时,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %thread ] ------------------ [ %-5level ] [ %logger{50} : %line ] -
                %msg%n
            </pattern>
        </layout>
    </appender>

    <!--
    logger主要用于存放日志对象,也可以定义日志类型、级别。
      name:表示匹配的logger类型前缀(包路径的前半部分)
      level:要记录的日志级别,包括:TRACE < DEBUG < INFO < WARN < ERROR
      additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,false:表示只用当前logger的appender-ref,true:表示当前logger的appender-ref和rootLogger的appender-ref都有效。
    -->
    <!-- hibernate logger -->
    <logger name="com.sst.cx" level="debug"/>
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>

    <!--
    root与logger是父子关系。没有特别定义则默认为root,任何一个类只会和一个logger对应,要么是定义的logger,要么是root。判断的关键在于找到这个logger,然后判断这个logger的appender和level。
    -->
    <root level="info">
        <appender-ref ref="stdout"/>
        <appender-ref ref="appLogAppender"/>
    </root>
</configuration> 
例(logback-spring.xml)带有spring标识的日志配置文件

1. 将上例中的logback.xml文件名修改为:logback-spring.xml。修改控制台日志输出,通过Profile实现不同的环境使用不同的日志输出格式:
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
       <layout class="ch.qos.logback.classic.PatternLayout">
            <!--开发环境 日志输出格式-->
            <springProfile name="dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
            </springProfile>
            <!--非开发环境 日志输出格式-->
            <springProfile name="!dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
            </springProfile>
        </layout>
    </appender>
2. 在SpringBoot项目的application.yml中,激活开发环境(dev)的 Profile,配置内容如下:
#默认配置
server:
  port: 8080
#在这里切换环境,dev、test、prod
spring:
  profiles:
    active: dev
---
#开发环境
server:
  port: 8081
spring:
  config:
    activate:
      on-profile: dev
---
#测试环境
server:
  port: 8082
spring:
  config:
    activate:
      on-profile: test
---
#生产环境
server:
  port: 8083
spring:
  config:
    activate:
      on-profile: prod

6. 静态资源映射

在Web项目中,为了让页面更美观、更好的用户体验,会使用到大量的静态资源(如: JS、CSS、HTML、jQuery、Bootstrap等)。
SpringMVC项目导入静态资源(将静态资源文件复制到webapp目录下)后需要配置静态资源的映射。
而SpringBoot项目则不需要,SpringBoot默认提供了3种静态资源映射规则:
  1. WebJars映射(以Jar形式为Web项目提供资源文件)
    SpringBoot项目是以Jar包的形式进行部署的,因此不存在webapp目录。
    WebJars可以将Web前端资源(JS,CSS 等)打成一个个的Jar包,然后将这些Jar包部署到Maven中央仓库中进行统一管理。
    只需在SpringBoot项目的pom.xml中引入依赖(访问WebJars官网,找到所需Web前端资源的pom依赖)。
    例(在SpringBoot项目中引入jquery,只需在pom.xml中引入jquery依赖)
        <dependency>
          <groupId>org.webjars</groupId>
          <artifactId>jquery</artifactId>
          <version>3.6.0</version>
        </dependency>
        启动SpringBoot,在浏览器中访问“http://localhost:8080/webjars/jquery/3.6.0/jquery.js”访问 jquery.js
    所有通过WebJars引入的前端资源都存放在当前项目类路径下的/META-INF/resources/webjars/目录中。
    SpringBoot通过MVC的自动配置类WebMvcAutoConfiguration为这些WebJars前端资源提供了默认映射规则,部分源码如下:
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
        } else {
            // WebJars 映射规则
            this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
            this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                if (this.servletContext != null) {
                    ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                    registration.addResourceLocations(new Resource[]{resource});
                }
            });
        }
      }
    通过源码可知,WebJars的映射路径为"/webjars/**"(即所有访问"/webjars/**"的请求,都会去“classpath:/META-INF/resources/webjars/”下查找WebJars前端资源)。
  2. 默认资源映射
    当访问项目中的任意资源(即"/**")时,SpringBoot会默认从以下路径(静态资源目录)中查找资源文件(优先级依次降低)classpath表示resources目录:
      1. classpath:/META-INF/resources/
      2. classpath:/resources/
      3. classpath:/static/
      4. classpath:/public/
  3. 静态首页(欢迎页)映射
    静态资源目录下的所有index.html被称为静态首页或欢迎页,会被"/**"映射(访问“/”或“/index.html”时会按优先级查找 并跳转到静态首页)。
WebJars官网查找JQuery的pom依赖

7. Thymeleaf模板引擎(用于渲染xml/xhtml/html5内容的模板引擎,取代JSP)

Thymeleaf作为新一代Java模板引擎,相比传统Java模板引擎(JSP、Velocity、FreeMaker):
  1. 支持HTML原型,其文件后缀为“.html”(因此可以直接被浏览器打开)。
  2. 通过在html标签中增加额外的属性来达到“模板+数据”的展示方式。

特点:
  1. 动静结合(最大的特点):未启动Web应用时,也能在浏览器中显示模板页面(此时展示的是静态内容;通过Web应用访问时会动态替换掉静态内容)。
  2. 开箱即用:Thymeleaf 提供了 Spring 标准方言以及一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
  3. 多方言支持:它提供了 Thymeleaf 标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL 表达式;必要时,开发人员也可以扩展和创建自定义的方言。
  4. 与SpringBoot完美整合:SpringBoot为Thymeleaf提供了默认配置,并且还为Thymeleaf设置了视图解析器。

<!DOCTYPE html>
<!-- 声明thymeleaf命名空间 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--th:text为Thymeleaf属性,用于展示文本内容-->
<h1 th:text="迎您来到Thymeleaf">欢迎您访问静态页面 HTML</h1>
</body>
</html>
未启动Web应用时,直接使用浏览器打开显示:欢迎您访问静态页面HTML。
使用Web应用访问时,显示:迎您来到Thymeleaf。

使用(语法规则)

首先在html标签中声明thymeleaf空间(避免编辑器出现html验证错误,非必须):
  <html xmlns:th="http://www.thymeleaf.org">

语法规则:
  1. 标准表达式语法
    1. 变量表达式(使用${}包裹的表达式)
      1. 获取对象的属性、方法
        ${person.lastName}
      2. 获取内置的基本对象及其属性、方法
        ${#session.getAttribute('map')}  或  ${session.map}
        内置的基本对象:
          1. #ctx :上下文对象;
          2. #vars :上下文变量;
          3. #locale:上下文的语言环境;
          4. #request:HttpServletRequest 对象(仅在Web应用中可用)。
          5. #response:HttpServletResponse 对象(仅在Web应用中可用)。
          6. #session:HttpSession对象(仅在Web应用中可用)。
          7. #servletContext:ServletContext 对象(仅在Web应用中可用)。
      3. 获取内置的工具对象
        ${#strings.equals('张三',name)}
        内置的工具对象:
          1. strings(字符串工具对象)
            常用方法:equals、equalsIgnoreCase、length、trim、toUpperCase、toLowerCase、indexOf、substring、replace、startsWith、endsWith、contains、containsIgnoreCase 等;
          2. numbers(数字工具对象)
            常用方法:formatDecimal 等;
          3. bools(布尔工具对象)
            常用方法:isTrue、isFalse 等;
          4. arrays(数组工具对象)
            常用方法:toArray、length、isEmpty、contains、containsAll 等;
          5. lists/sets(List/Set集合工具对象)
            常用方法:toList、size、isEmpty、contains、containsAll、sort 等;
          6. maps(Map 集合工具对象)
            常用方法:size、isEmpty、containsKey、containsValue 等;
          7. dates(日期工具对象)
            常用方法:format、year、month、hour、createNow 等。
    2. 选择变量表达式(使用*{}包裹的表达式)
      在变量表达式的基础上增加了与th:object(用于存储一个临时变量,该变量只在该元素及其子元素中有效)的配合使用。
      使用th:object存储一个对象后,可以在其子元素中使用选择变量表达式*{name}获取该对象(*代表该对象)中的属性。子元素中仍可使用${}变量表达式。
      例:
        <div th:object="${session.user}" >
          <p th:text="*{fisrtName}">firstname</p>
        </div>
    3. 链接表达式(使用@{}包裹的表达式)
      用于:静态资源引用、form表单请求(凡是链接都可以用链接表达式)。
      1. 无参请求:@{/xxx}
      2. 有参请求:@{/xxx(k1=v1,k2=v2)}
      例:
        <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
    4. 国际化表达式(使用#{}包裹的表达式)
      用于国际化的场景。
      th:text="#{msg}"
    5. 片段引用表达式(使用~{}包裹的表达式)
      用于在模板页面中引用其他的模板片段。
      方式1.  ~{templatename::fragmentname}  推荐
      方式2.  ~{templatename::#id}
      说明:
        1. templatename:模版名,Thymeleaf会根据模版名解析完整路径:/resources/templates/templatename.html,要注意文件的路径。
        2. fragmentname:片段名,Thymeleaf通过th:fragment声明定义代码块(即:th:fragment="fragmentname")
        3. id:html的id选择器,使用时要在前面加上#号,不支持class选择器。
  2. th属性
    可以直接在HTML标签中使用。
常用的th属性 描述 示例
th:id 替换id属性 <input id="html-id" th:id="thymeleaf-id" />
th:text 文本替换,会转义特殊字符 <h1 th:text="hello world" >hello</h1>
th:utext 文本替换,不会转义特殊字符 <div th:utext="'<h1>hello world</h1>'" >世界你好</div>
th:object 在父标签中存储一个对象,子标签使用选择变量表达式获取该对象的属性。 <div th:object="${session.user}" ><p th:text="*{fisrtName}">firstname</p></div>
th:value 替换value属性 <input th:value = "${user.name}" />
th:with 局部变量赋值运算 <div th:with="isEvens=${prodStat.count}%2==0" th:text="${isEvens}"></div>
th:style 设置样式 <div th:style="'color:#F00; font-weight:bold'">hello</div>
th:onclick 点击事件 <td th:onclick = "'getInfo()'"></td>
th:each 遍历(支持Iterable、Map、数组等)。 <table><tr th:each="m:${session.map}"><td th:text="${m.getKey()}"></td><td th:text="${m.getValue()}"></td></tr></table>
th:if 根据条件判断是否需要展示此标签 <a th:if ="${userId == collect.userId}">
th:unless 和 th:if 判断相反,满足条件时不显示 <div th:unless="${m.getKey()=='name'}" ></div>
th:switch 类似switch-case语句,与th:case配合使用,根据不同的条件展示不同的内容。 <div th:switch="${name}"><span th:case="a">hello</span><span th:case="b">world</span></div>
th:fragment 类似jsp的tag,用来定义一段被引用或包含的模板片段。 <footer th:fragment="footer">插入的内容</footer>
th:insert 将使用th:fragment属性指定的模板片段(包含标签)插入到当前标签中。 <div th:insert="commons/bar::footer"></div>
th:replace 将使用th:fragment属性指定的模板片段(包含标签)替换当前整个标签。 <div th:replace="commons/bar::footer"></div>
th:selected select选择框选中 <select><option th:selected="${name=='a'}">hello</option><option th:selected="${name=='b'}">world</option></select>
th:src 替换html中的src属性 <img th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" />
th:inline 内联属性,有text、none、javascript三种取值,在<script>标签中使用时,js代码中可以获取到后台传递页面的对象。 <script type="text/javascript" th:inline="javascript">var name = 'hello world';alert(name)</script>
th:action 替换表单提交的地址 <form th:action="@{/user/login}" th:method="post"></form>

公共页面的抽取和引用

Web项目的页面中通常会存在一些重复代码(如:头部导航栏、侧边菜单栏、公共的js/css等)。可以把这些公共页面片段抽取出来存放在一个独立的页面中,然后再由其他页面根据需要进行引用(消除代码重复、使页面更简洁)。

1. 抽取公共页面
  将公共页面片段抽取出来存放到一个独立的页面中,并使用th:fragment属性命名。
    <div th:fragment="fragment-name" id="fragment-id">
        <span>公共页面片段</span>
    </div>

2. 引用公共页面
  可以通过以下3个属性,将公共页面片段引入到当前页面中。
    1. th:insert:将代码块片段整个插入到使用了th:insert属性的html标签中。
    2. th:replace:将代码块片段整个替换使用了th:replace属性的html标签中。
    3. th:include:将代码块片段包含的内容插入到使用了th:include属性的html标签中。
  属性值使用片段引用表达式引入引入页面片段(通常,~{}可以省略)
    方式1. ~{templatename::#id}:模板名::选择器
    方式2. ~{templatename::fragmentname}:模板名::片段名
  行内写法为: [[~{...}]] 会转义特殊字符、[(~{...})] 不会转义特殊字符。
  例:
    <div th:insert="commons::fragment-name"></div>
  
  3. 传递参数
    引用公共页面片段时,通过以下2种方式将参数传入到被引用的页面片段中:
      方式1(参数较多时使用该方式)明确指定参数名和参数值
        模板名::选择器名或片段名(参数1=参数值1,参数2=参数值2)
      方式2(参数较少时使用该方式)
        模板名::选择器名或片段名(参数值1,参数值2)

例(查看th:insert、th:replace、th:include3者的区别)

1. 在页面 fragment.html 中引入 commons.html 中声明的页面片段
<!--th:insert 片段名引入-->
<div th:insert="commons::fragment-name"></div>
<!--th:insert id 选择器引入-->
<div th:insert="commons::#fragment-id"></div>
------------------------------------------------
<!--th:replace 片段名引入-->
<div th:replace="commons::fragment-name"></div>
<!--th:replace id 选择器引入-->
<div th:replace="commons::#fragment-id"></div>
------------------------------------------------
<!--th:include 片段名引入-->
<div th:include="commons::fragment-name"></div>
<!--th:include id 选择器引入-->
<div th:include="commons::#fragment-id"></div>

2. 启动 Spring Boot,使用浏览器访问fragment.html,右键查看页面源码:
<!--th:insert 片段名引入-->
<div>
    <div id="fragment-id">
        <span>公共页面片段</span>
    </div>
</div>
<!--th:insert id 选择器引入-->
<div>
    <div id="fragment-id">
        <span>公共页面片段</span>
    </div>
</div>
------------------------------------------------
<!--th:replace 片段名引入-->
<div id="fragment-id">
    <span>公共页面片段</span>
</div>
<!--th:replace id 选择器引入-->
<div id="fragment-id">
    <span>公共页面片段</span>
</div>
------------------------------------------------
<!--th:include 片段名引入-->
<div>
    <span>公共页面片段</span>
</div>
<!--th:include id 选择器引入-->
<div>
    <span>公共页面片段</span>
</div>

例(传递参数)

<!--th:insert 片段名引入-->
<div th:insert="commons::fragment-name(var1='insert-name',var2='insert-name2')"></div>
<!--th:insert id 选择器引入-->
<div th:insert="commons::#fragment-id(var1='insert-id',var2='insert-id2')"></div>
------------------------------------------------
<!--th:replace 片段名引入-->
<div th:replace="commons::fragment-name(var1='replace-name',var2='replace-name2')"></div>
<!--th:replace id 选择器引入-->
<div th:replace="commons::#fragment-id(var1='replace-id',var2='replace-id2')"></div>
------------------------------------------------
<!--th:include 片段名引入-->
<div th:include="commons::fragment-name(var1='include-name',var2='include-name2')"></div>
<!--th:include id 选择器引入-->
<div th:include="commons::#fragment-id(var1='include-id',var2='include-id2')"></div>

在公共页面片段中使用:
<div th:fragment="fragment-name(var1,var2)" id="fragment-id">
    <p th:text="'参数1:'+${var1} + '-------------------参数2:' + ${var2}">...</p>
</div>

SpringBoot项目中使用Thymeleaf

SpringBoot推荐使用Thymeleaf作为模板引擎(为其提供了大量默认配置)。

使用步骤
  1. 引入Thymeleaf依赖(在项目的pom.xml中添加spring-boot-starter-thymeleaf依赖)
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  2. 创建.html模板文件,并放置在项目类路径(resources目录)的templates目录下。
    例(hello.html):
      <!DOCTYPE html>
      <!--导入thymeleaf命名空间-->
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
            <h1 th:text="'欢迎来到'+${name}"></h1>
      </body>
      </html>
  3. 使用
    创建HelloController.java,通过参数map传递数据到页面中:
      package com.sst.controller;
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.RequestMapping;
      import java.util.Map;
      @Controller
      public class HelloController {
          @RequestMapping("/hello")
          public String hello(Map<String, Object> map) {
              map.put("name", "hello world");
              return "hello";
          }
      }
    启动SpringBoot,在浏览器中访问:http://localhost:8080/hello
SpringBoot通过ThymeleafAutoConfiguration自动配置类为Thymeleaf提供了一整套的自动化配置方案。部分源码如下:
    @Configuration(
        proxyBeanMethods = false
    )
    @EnableConfigurationProperties({ThymeleafProperties.class})
    @ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
    @AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
    public class ThymeleafAutoConfiguration {
    }
使用@EnableConfigurationProperties注解导入了ThymeleafProperties类(包含了和Thymeleaf相关的自动配置属性)。其部分源码如下:
    @ConfigurationProperties(
        prefix = "spring.thymeleaf"
    )
    public class ThymeleafProperties {
        private static final Charset DEFAULT_ENCODING;
        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 = "classpath:/templates/";
        private String suffix = ".html";
        private String mode = "HTML";
        private Charset encoding;
        private boolean cache;
        ...
    }
ThymeleafProperties通过@ConfigurationProperties注解将配置文件(application.properties/yml) 中前缀为spring.thymeleaf的配置和这个类中的属性绑定。
ThymeleafProperties还提供了以下静态变量:
  1. DEFAULT_ENCODING:默认编码格式
  2. DEFAULT_PREFIX:视图解析器的前缀
  3. DEFAULT_SUFFIX:视图解析器的后缀
根据以上配置属性可知:
  1. Thymeleaf模板的默认位置在resources/templates目录下,默认的后缀是html。
  2. 只要将html页面放在该目录下,Thymeleaf就能自动进行渲染。

8. 国际化(为不同的国家/语言提供相应的页面和数据)

步骤:
  1. 创建国际化资源文件(在resources目录的i18n目录下)
    文件名格式:基本名_语言_国家.properties
    例(IDEA会自动识别国际化资源文件并自动添加Resouce Bundle目录):
      hello.properties:默认
      hello_zh_CN.properties:中文时生效
      hello_en_US.properties:英语时生效
    打开任意一个国际化资源文件,切换为ResourceBundle模式(需要安装ResourceBundle插件),然后点击“+”号创建所需的国际化属性(需要进行国际化的字段)。
  2. 使用ResourceBundleMessageSource管理国际化资源文件。
    在application.porperties/yml配置文件中添加spring.messages.basename来覆盖默认值(当指定多个资源文件时,用逗号分隔)。
    例:
      spring.messages.basename=i18n.hello
  3. 在页面中使用国际化表达式#{}来获取国际化内容。
切换为ResourceBundle模式
添加国际化资源
SpringBoot通过MessageSourceAutoConfiguration类对ResourceBundleMessageSource提供了默认配置。

===》MessageSourceAutoConfiguration类的部分源码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = {};
    // 将MessageSourceProperties以组件的形式添加到容器中,MessageSourceProperties下的每个属性都与以spring.messages开头的属性对应。
    @Bean
    @ConfigurationProperties(prefix = "spring.messages")
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }
    // Spring Boot会从容器中获取MessageSourceProperties,读取国际化资源文件的basename(基本名)、encoding(编码)等信息并封装到 ResourceBundleMessageSource中。
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        // 读取国际化资源文件的basename (基本名),并封装到ResourceBundleMessageSource中
        if (StringUtils.hasText(properties.getBasename())) {
            messageSource.setBasenames(StringUtils
                    .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }
        // 读取国际化资源文件的encoding (编码),并封装到ResourceBundleMessageSource中
        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;
    }
    ...
}
从源码可知:
  1. SpringBoot将MessageSourceProperties以组件的形式添加到容器中。
  2. MessageSourceProperties的属性与配置文件中以“spring.messages”开头的配置进行了绑定。
  3. SpringBoot从容器中获取MessageSourceProperties组件,并从中读取国际化资源文件的basename(文件基本名)、encoding(编码)等信息,将它们封装到 ResourceBundleMessageSource中。
  4. SpringBoot将ResourceBundleMessageSource以组件的形式添加到容器中,进而实现对国际化资源文件的管理。

===》MessageSourceProperties类的源码如下:
public class MessageSourceProperties {
    private String basename = "messages";
    private Charset encoding;
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration cacheDuration;
    private boolean fallbackToSystemLocale;
    private boolean alwaysUseMessageFormat;
    private boolean useCodeAsDefaultMessage;
    public MessageSourceProperties() {
        this.encoding = StandardCharsets.UTF_8;
        this.fallbackToSystemLocale = true;
        this.alwaysUseMessageFormat = false;
        this.useCodeAsDefaultMessage = false;
    }
    ...
}
从源码可知:
  1. MessageSourceProperties为basename、encoding等属性提供了默认值。
  2. basename表示国际化资源文件的基本名,其默认值为“message”(即SpringBoot默认会获取类路径下的message.properties以及message_XXX.properties作为国际化资源文件)。
  3. 在application.porperties/yml配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。

1. 在resources目录的i18n目录下创建
  login.properties
  login_en_US.properties
  login_zh_CN.properties

2. 在application.porperties/yml配置文件中添加
  application.porperties
    spring.messages.basename=i18n.login
  application.yml
    spring:
      messages:
        basename: i18n/login
        encoding: utf-8

3. 获取国际化内容
  创建login.html,代码如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="ThemeBucket">
    <link rel="shortcut icon" href="#" type="image/png">
    <title>Login</title>
    <!--将js css 等静态资源的引用修改为 绝对路径-->
    <link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
    <link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
    <script src="js/html5shiv.js" th:src="@{/js/html5shiv.js}"></script>
    <script src="js/respond.min.js" th:src="@{/js/respond.min.js}"></script>
    <![endif]-->
</head>
<body class="login-body">
<div class="container">
    <form class="form-signin" th:action="@{/user/login}" method="post">
        <div class="form-signin-heading text-center">
            <h1 class="sign-title" th:text="#{login.btn}">Sign In</h1>
            <img src="/images/login-logo.png" th:src="@{/images/login-logo.png}" alt=""/>
        </div>
        <div class="login-wrap">
            <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
            <input type="text" class="form-control" name="username" placeholder="User ID" autofocus
                   th:placeholder="#{login.username}"/>
            <input type="password" class="form-control" name="password" placeholder="Password"
                   th:placeholder="#{login.password}"/>
            <label class="checkbox">
                <input type="checkbox" value="remember-me" th:text="#{login.remember}">
                <span class="pull-right">
                    <a data-toggle="modal" href="#myModal" th:text="#{login.forgot}"> </a>
                </span>
            </label>
            <button class="btn btn-lg btn-login btn-block" type="submit">
                <i class="fa fa-check"></i>
            </button>

            <div class="registration">
                <!--Thymeleaf 行内写法-->
                [[#{login.not-a-member}]]
                <a class="" href="/registration.html" th:href="@{/registration.html}">
                    [[#{login.signup}]]
                </a>
                <!--thymeleaf 模板引擎的参数用()代替 ?-->
                <br/>
                <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')}">English</a>
            </div>
        </div>
        <!-- Modal -->
        <div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal"
             class="modal fade">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                        <h4 class="modal-title">Forgot Password ?</h4>
                    </div>
                    <div class="modal-body">
                        <p>Enter your e-mail address below to reset your password.</p>
                        <input type="text" name="email" placeholder="Email" autocomplete="off"
                               class="form-control placeholder-no-fix">
                    </div>
                    <div class="modal-footer">
                        <button data-dismiss="modal" class="btn btn-default" type="button">Cancel</button>
                        <button class="btn btn-primary" type="button">Submit</button>
                    </div>
                </div>
            </div>
        </div>
        <!-- modal -->
    </form>
</div>
<!-- Placed js at the end of the document so the pages load faster -->
<!-- Placed js at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js" th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script src="js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
<script src="js/modernizr.min.js" th:src="@{/js/modernizr.min.js}"></script>
</body>
</html>

手动切换语言

1. 区域信息解析器自动配置
  可以通过以下两个对象对区域信息进行切换,继而切换语言。
    1. Locale(区域信息对象)
    2. LocaleResolver(区域信息解析器)容器中的组件,负责获取区域信息对象
2. 手动切换语言
  1. 修改login.html中的切换语言链接,在请求中携带国际化区域信息,代码如下
    <!--thymeleaf 模板引擎的参数用()代替 ?-->
    <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')}">English</a>
  2. 在com.sst.cx.component包下创建一个区域信息解析器MyLocalResolver,代码如下
    package com.sst.cx.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");
            // 获取默认的区域信息解析器
            Locale locale = Locale.getDefault();
            // 根据请求中的参数重新构造区域信息对象
            if (StringUtils.hasText(l)) {
                String[] s = l.split("_");
                locale = new Locale(s[0], s[1]);
            }
            return locale;
        }
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        }
    }
  3. 在com.sst.cx.config包下的MyMvcConfig中添加以下方法,将自定义的区域信息解析器以组件的形式添加到容器中,代码如下:
    // 将自定义的区域信息解析器以组件的形式添加到容器中
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocalResolver();
    }
===》SpringBoot在WebMvcAutoConfiguration类中为区域信息解析器进行了自动配置,源码如下:
    @Bean
    @ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
    @SuppressWarnings("deprecation")
    public LocaleResolver localeResolver() {
        if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.webProperties.getLocale());
        }
        if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.mvcProperties.getLocale());
        }
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
                : this.mvcProperties.getLocale();
        localeResolver.setDefaultLocale(locale);
        return localeResolver;
    }
从源码可知:
  1. 该方法默认向容器中添加了一个LocaleResolver区域信息解析器组件,它会根据请求头中携带的“Accept-Language”参数,获取相应Locale区域信息对象。
  2. 该方法上使用了@ConditionalOnMissingBean注解,其参数name的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为localResolver组件时,该方法才会生效。即手动向容器中添加一个名为“localeResolver”的组件时,SpringBoot自动配置的区域信息解析器会失效,自定义的区域信息解析器则会生效。
上一篇下一篇

猜你喜欢

热点阅读