SpringBoot

SpringBoot入门 command+n-类内get/set方法 shift*2-全文搜索

https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/documentation-overview.html#boot-documentation

1. Maven配置

下载maven3.6.2 http://archive.apache.org/dist/maven/maven-3/3.6.2/binaries/

解压进入setting.xml添加插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--国内镜像 -->
<mirrors>
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
</mirrors>

<!--jdk -->
<profiles>
<profile>
<id>jdk-1.8</id>

<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>

<repositories>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</repositories>
</profiles>

IntelliJ IDEA中配置

2. 第一个SpringBoot项目

新建Maven project

在pom.xml下配置springboot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
</properties>

<!-- 用于导出jar包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

新建Application.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages="com.runaccpeted")
public class TestApplication {

public static void main(String[] args) {
SpringApplication.run(TestApplication.class,args);
}

//得到所有bean name
@Bean
public CommandLineRunner getBeans(ApplicationContext ac){
return args -> {
String[] str = ac.getBeanDefinitionNames();
for (String s : str) {
System.out.println(s);

}
};
}
}

新建controller类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.runaccpeted.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {

@RequestMapping("/test")
@ResponseBody
public String test(){
return "Hello!!!";
}
}

运行main方法

1
2
3
4
5
6
7
8
9
.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)

Tomcat started on port(s): 8080 (http) with context path ''

运行

2.1. java jar xx.jar

将整个项目打包

打包好的项目会放在target文件夹下

直接终端运行jar项目,可以直接访问

1
$ java -jar SpringBoot-Start-1.0-SNAPSHOT.jar

在springboot项目中自动包含了tomcat服务器

3. @SpringBootApplication自动配置

1
2
3
4
5
6
7
8
9
@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 {

注解标注为自动配置

3.1. @SpringBootConfiguration 注明为配置类

1
2
3
@Configuration
public @interface SpringBootConfiguration {
}

配置类也是容器中的一个组件@Component

1
2
3
4
5
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {}

3.2. @EnableAutoConfiguration开启自动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}

3.2.1. @AutoConfigurationPackage自动配置包

3.2.1.1. @Import({Registrar.class}) 导入配置包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {}

3.2.1.2. Register.java描主配置类包

1
2
3
4
5
6
7
8
9
10
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}

扫描主配置类所在包及子包

3.2.2. @Import({AutoConfigurationImportSelector.class})

导入组件

AutoConfigurationImportSelector 组件选择器

将需要的组件以全类名的形式添加到容器中

给容器导入大量自动配置类xxxAutoConfiguration,给容器中导入场景所需组件并配置

如何知道配置类?

3.2.2.1. AutoConfigurationImportSelector.java

1
2
3
4
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
return configurations;
}
3.2.2.1.1. getSpringFactoriesLoaderFactoryClass
1
2
3
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
3.2.2.1.2. SpringFactoriesLoader内部
1
2
3
LoadSpringFactories(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
}

在spring-boot-autoconfigure-2.1.6.RELEASE.jar下/META-INF/spring.factories

1
2
3
4
5
6
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
...

写明了配置类全路径名

Spring Boot在启动时从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定值,作为自动配置类路径,进行自动配置

所有的配置声明在 org/springframework/boot/autoconfigure

3.3. 自动配置原理

xxxxAutoConfiguration 对应 xxxxProperties

3.3.1. SpringDataWebAutoConfiguration.java

@EnableConfigurationProperties({SpringDataWebProperties.class})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//表示这是一个配置类,可以给容器中添加组件
@Configuration(
proxyBeanMethods = false
)
//启动自动配置
@EnableSpringDataWebSupport
//底层为@Conditional,判断是否满足指定条件,整个配置类里面的配置根据判断生效@Conditional({OnWebApplicationCondition.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目中是否有这个类
@ConditionalOnClass({PageableHandlerMethodArgumentResolver.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({PageableHandlerMethodArgumentResolver.class})
//启动指定类的ConfigurationProperties功能,将配置文件中的值和SpringDataWebProperties绑定起来,将SpringDataWebProperties加载到类中
@EnableConfigurationProperties({SpringDataWebProperties.class})
@AutoConfigureAfter({RepositoryRestMvcAutoConfiguration.class})
public class SpringDataWebAutoConfiguration {
//properties已经和springboot的配置文件映射了
private final SpringDataWebProperties properties;
public SpringDataWebAutoConfiguration(SpringDataWebProperties properties) {
this.properties = properties;
}

@Bean //容器中添加组件,某些值从properties中获取
@ConditionalOnMissingBean
public PageableHandlerMethodArgumentResolverCustomizer pageableCustomizer() {
return (resolver) -> {
Pageable pageable = this.properties.getPageable();
resolver.setPageParameterName(pageable.getPageParameter());
resolver.setSizeParameterName(pageable.getSizeParameter());
resolver.setOneIndexedParameters(pageable.isOneIndexedParameters());
resolver.setPrefix(pageable.getPrefix());
resolver.setQualifierDelimiter(pageable.getQualifierDelimiter());
resolver.setFallbackPageable(PageRequest.of(0, pageable.getDefaultPageSize()));
resolver.setMaxPageSize(pageable.getMaxPageSize());
};
}

3.3.2. SpringDataWebProperties.java

这里实际表明可以配置的值 spring.data.web.x=?

所有配置文件中能配置的属性都在xxxxProperties类中封装

配置文件能配置什么就可以参照所需功能对应的属性类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ConfigurationProperties("spring.data.web")
public class SpringDataWebProperties {
private final SpringDataWebProperties.Pageable pageable = new SpringDataWebProperties.Pageable();

public static class Pageable {
private String pageParameter = "page";
private String sizeParameter = "size";
private boolean oneIndexedParameters = false;
private String prefix = "";
private String qualifierDelimiter = "_";
private int defaultPageSize = 20;
private int maxPageSize = 2000;
}

}

application.properties

1
spring.data.web.pageable.prefix=

3.4. 精髓

  1. SpringBoot启动加载大量自动配置类
  2. 需要的功能有没有springboot默认写好的自动配置类
  3. 看自动配置类中到底配置了那些组件
  4. 容器中自动配置类添加组件时,从properties类中获取属性,指定属性值

xxxxAutoConfiguration –> 添加组件 –> Properties封装配置文件相关属性

3.5. 自动配置报告

书写在application.properties中

1
debug=true

控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

DispatcherServletAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)


Negative matches:
-----------------

AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)

4. Spring Initializer创建SpringBoot

new – > project –> Spring Initializr

直接联网生成一个初步的完备的springboot项目

4.1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.runaccpeted</groupId>
<artifactId>helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>helloworld</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

resources文件夹

—- static 保存静态资源 js css images

—- templates 保存所有模板页面,springboot默认jar包使用嵌入式tomcat,不支持jsp页面,支持模板引擎freemarker,thymeleaf

—- application.properties 配置文件

​ eg. server.port=8081

5. SpringBoot配置文件

有两种方式,文件名固定

  • applicaton.properities

    1
    server.port=8081
  • application.yml

    YAML Ain’t Markup Language

    1
    2
    server:
    port: 8081

5.1. application.yml

键值对

空格缩进控制层级,左对齐的是一级

大小写敏感

  • 字面量:

    k: v: 字面量直接写,不加引号

    双引号:不会转义字符串中的特殊字符,特殊字符作为本身想表示的意思

    name: “zhang \n” == “zhang 换行”

    单引号:会转义字符串中的特殊字符,特殊字符最终只是一个普通的字符串数据

    name: ‘zhang \n’ == ‘zhang \n’

  • 对象:

    k: v: 值

    1
    2
    3
    person:
    age: 18
    name: zhangsan

    行内写法:

    1
    person:{age:18,name:zhangsan}
  • 数组

    1
    2
    3
    4
    pets:
    - cat
    - dog
    - pig

    行内写法

    1
    pets:{cat,dog,pig}

5.1.1. 配置springboot

application.yml

1
2
3
4
user:
id: 1
uname: wt
pwd: 123

在pom.xml中添加依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

User.java

配置@ConfigurationProperties 让springboot将application.yml中的配置项对应给User的bean

User 注册为一个bean @Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.runaccpeted.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "user")
public class User {
private int id;
private String uname;
private String pwd;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUname() {
return uname;
}

public void setUname(String uname) {
this.uname = uname;
}

public String getPwd() {
return pwd;
}

public void setPwd(String pwd) {
this.pwd = pwd;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", uname='" + uname + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}

添加单元测试依赖

1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.runaccpeted.helloworld;

import com.runaccpeted.pojo.User;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class HelloworldApplicationTests {

@Autowired
User user;

@Test
public void contextLoads() {
System.out.println(user);

}
}

Out: User{id=1, uname=’wt’, pwd=’123’}

5.2. application.properties

5.2.1. 配置SpringBoot

1
2
3
user.id=1
user.uname=wt
user.pwd=123

5.2.2. @Value VS @ConfigurationProperities

Value直接添加到变量上

1
2
3
4
5
6
@Value("${user.id}")
private int id;
@Value("${user.uname}")
private String uname;
@Value("${user.pwd}")
private String pwd;

ConfigurationProperities添加到类上

1
2
@ConfigurationProperties(prefix = "user")
public class User {}

比较

@ConfigurationProperities @Value
功能 批量注入配置文件中的值 一一对应
松散语法 支持,user.uname name 可以对应 必须一一对应
SpEL 不支持 支持 ${1+2}
JSR303数据检验 @Size(min = 0) 支持 不支持
复杂类型注入 支持map,list,set 不支持

5.2.3. 配置文件占位符${}

1
2
3
4
user:
id: 2
username: 张三_${user.id}_${random.uuid}
password: 123456

5.3. @PropertySource指定properties文件

User.properties

1
2
3
user.id=1
user.uname=wt
user.pwd=123456

指定特定配置文件

1
2
3
4
@Component
@PropertySource(value = {"classpath:User.properties"})
@ConfigurationProperties(prefix = "user")
public class User {}

5.4. @ImportSource指定xml文件

导入Spring 配置文件,让内容生效

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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="u" class="com.runaccpeted.pojo.User">
<property name="id" value="3">
</property>
<property name="uname" value="abc"></property>
<property name="pwd" value="123"></property>
</bean>
</beans>

注解

@ImportResource(locations= {“classpath:beans.xml”})

1
2
3
4
5
6
7
8
@ImportResource(locations= {"classpath:beans.xml"})
@SpringBootApplication
public class HelloworldApplication {

public static void main(String[] args) {
SpringApplication.run(HelloworldApplication.class, args);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class HelloworldApplicationTests {
@Autowired
ApplicationContext ac;
@Test
void contextLoads() {
User user = ac.getBean(User.class);
System.out.println(user);
}
}

5.5. 全注解方式向容器中添加bean

5.5.1. @Configuration配置类

1
2
3
4
5
6
7
8
9
10
11
12
import com.runaccpeted.firstboot.service.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyAppConfig {

@Bean
public HelloService helloService(){
return new HelloService();
}
}

5.5.2. 测试bean是否注册到容器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.runaccpeted.firstboot;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class FirstbootApplicationTests {

@Autowired
ApplicationContext ac;

@Test
void TestBean(){
//组件名为方法名
boolean flag = ac.containsBean("helloService");
System.out.println(flag);
}
}

6. 多个profile开发环境

6.1. application.properties

新建多个配置文件

application-{环境}.properties

application-dev.properties

1
server.port=8082

application.properties

1
2
3
server.port=8081
#激活
spring.profiles.active=dev

默认生效的是application.properties

Tomcat started on port(s): 8082 (http) with context path ‘’

6.2. application.yml

多个代码块

— 分割文档

spring.profiles.active 指定哪个profiles被激活,默认为8081

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: prod

Tomcat started on port(s): 8082 (http) with context path ‘’

当不设定active时,

6.3. 命令行

–spring.profiles.active=dev

7. 配置文件加载位置

file:./config/

file:./

classpath:/config/ 为resources:/config/

classpath:/

优先级从高到低,高优先级会覆盖低优先级内容

springboot会从4个位置全部加载配置文件,可以实现互补配置

config/application.properties 下配置 server.port=8081

src/main/resources/application.properties 下配置 server.servlet.context-path=/boot 项目根目录

实现 访问 http://localhost:8081/boot/test

7.1. 命令行指定配置文件位置

打包项目

maven projects –> package –> target下生成一个jar文件

命令行

java -jar helloworld-0.0.1-SNAPSHOT.jar –spring.config.location=/Users/Learning/application.properties

8. 日志

日志门面-抽象层 日志实现
JCL(Jakarta Commons Logging) SLF4j jboos-logging Log4j JUL(java util logging) log4j2 Logback

日志门面(抽象)SLF4j和日志实现(实现类)logback

Simple Logging Facade for Java

1
2
3
4
5
6
7
8
9
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld{
public static void main(String[] args){
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}

slf4j-api.jar – slf4j-xx.jar – xx.jar

抽象类 – 适配层 – 实现类

统一所有日志:

排除其他日志框架

用中间包替换原有的日志框架,功能不变

导入sjf4j的其他实现包

SpringBoot中的log引入

中间包底层直接用slf4j

引用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- <artifactId>spring-boot-starter</artifactId> -->   
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.2.0.RELEASE</version>
<scope>compile</scope>
</dependency>

<!-- <artifactId>spring-boot-starter-logging</artifactId> -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.12.1</version>
<scope>compile</scope>
</dependency>

<!-- <artifactId>log4j-to-slf4j</artifactId> -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>

8.1. 实践

日志级别 trace < debug < info < warn < error

1
2
3
4
5
6
7
8
9
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger logger= LoggerFactory.getLogger(this.getClass());
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");

8.1.1. 调整日志级别

application.properties调整 默认日志级别info

1
logging.level.com.runaccpeted=trace

设定日志级别

指定日志文件名和路径

不指定默认为控制台

生成在项目根目录下

1
2
3
4
5
6
7
8
9
logging.file.name=spring.log
#当前磁盘根路径下创建spring文件和log文件,使用spring.log作为默认文件
logging.file.path=/log #好像没什么用
#完整路径已废除
#logging.file=/Users/Learning/springboot.log
#最大日志容量
loggin.file.max-size=10MB
#最大日志保留天数
logging.file.max-history=7

8.1.2. 日志格式

%d 日期时间

%thread 线程

%-5level 级别从左显示5个字符宽度

%logger{50} logger名字最长50个字符,否则按照句点分割

%msg 日志消息

%n 换行符

1
2
3
4
5
#控制台日志格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} {%thread} %-5level %logger{50} - %msg%n

#文件日志格式
logging.pattern.file=%d{yyyy-MM-dd} {%thread} %-5level %logger{50} - %msg%n

e.g.

2019-10-07 16:46:47.726 {main} INFO c.r.helloworld.HelloworldApplicationTests - Started HelloworldApplicationTests in 5.292 seconds (JVM running for 8.207)

默认配置

spring-boot-2.2.0.RELEASE

org.springframework.boot.logging.logback.base.xml

指定默认为INFO

1
2
3
4
5
6
7
8
9
10
<included>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</included>

org.springframework.boot.logging.logback.defaults.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<included>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
<logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>
<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
<logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>
<logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>
</included>

8.2. 指定配置

给类路径下放每个日志框架自己的配置文件

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JUL (Java Util Logging) logging.properties

logback.xml:直接被日志框架识别

8.2.1. logback-spring.xml

日志框架不直接加载日志配置项,SpringBoot解析日志配置,使用SpringBoot高级profile功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
<!-- 定义日志的根目录 -->
<property name="LOG_HOME" value="log" />
<!-- 定义日志文件名称 -->
<property name="appName" value="springboot"></property>
<!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
<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">
<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>

<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<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.SSS} [ %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.atguigu" 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

确定开发环境:

1
2
3
4
5
6
<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>

在application.properties中配置dev

1
spring.profiles.active=dev

8.3. 切换日志框架

8.3.1. log4j❌不推荐❌

剔除原有

log4j-to-slf4j

ch.qos.logback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j-to-slf4j</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
</exclusions>
</dependency>

添加log4j

1
2
3
4
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>

添加log4j.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# set log levels
log4j.rootLogger=debug,stdout,D

# 输出到控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} ---> %5p %c{1}:%L - %m%n

# 输出到日志文件
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
# 项目根目录开始
log4j.appender.D.File=logs/log.log
log4j.appender.D.Append=true
log4j.appender.D.Threshold=DEBUG
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n

切换记录类

1
2
3
4
5
6
7
import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(this.getClass());
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");

记录日志

1
2
3
4
[main:7073] - [DEBUG] debug
[main:7073] - [INFO] info
[main:7074] - [WARN] warn
[main:7074] - [ERROR] error

8.3.2. log4j2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

配置文件

8.3.2.1. log4j2-spring.xml

9. SpringBoot与Web开发

9.1. 静态资源映射

9.1.1. WebMvcAutoConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 //静态资源配置 
@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/**")) {
//配置webjar
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));
}
}

/webjars/** 映射到/META-INF/resources/webjars/

webjar

将js,css以jar的形式加载到项目中

导入jquery

1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>

导入的maven依赖

访问路径:localhost:8080/webjars/jquery/3.3.1/jquery.js

9.2. 文件自定义静态资源

9.2.1. WebMvcAutoConfigurationAdapter.java

9.2.2. ResourceProperties

1
2
3
4
5
6
7
8
9
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}

@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/" };
}

默认在下列路径找静态资源

/ == 项目名

1
2
3
4
5
#resources:/
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/

resources/resources/a.txt == Hello

resources/static/a.txt == hello world

显示的还是Hello

9.3. 欢迎页

9.3.1. WebMvcAutoConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";
Resource getResource(String var1);
}

private Resource getIndexHtml(String location) {
//private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
return this.resourceLoader.getResource(location + "index.html");
}

private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}

@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;
}

9.3.2. WebMvcProperties.java

getStaticPathPattern() 静态资源文件夹下所有index.html页面

1
2
3
4
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
private String staticPathPattern = "/**";
}

resources/public/index.html == index

9.4. 图标

静态资源文件夹下所有favicon.ico

9.4.1. 自定义静态资源路径

1
spring.resources.static-locations=classpath:/hello/,classpath:/test/

配置后,原配置路径失效

10. Thymeleaf模板引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 更换版本 -->
<properties>
<java.version>1.8</java.version>
<!-- themeleaf3 和 layout2 适配-->
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.4.1</thymeleaf-layout-dialect.version>
</properties>

<!-- layout-dialect -->
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>${thymeleaf-layout-dialect.version}</version>
</dependency>

10.1. ThymeleafAutoConfiguration

1
2
3
4
5
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {}

10.2. ThymeleafProperties

1
2
3
4
5
6
7
8
@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";

10.3. TestController

对应 classpath:/templates/ 下 .html

1
2
3
4
5
6
7
8
@Controller
public class TestController {

@RequestMapping("/test")
public String test(){
return "hello";
}
}

hello.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
hello """"
</body>
</html>

结果

10.4. 语法规则

1
<html lang="en" xmlns:th="http://www.thymeleaf.org">

导入语法提示

10.4.1. th:

10.4.1.1. th:text 改变元素文本内容

resource/templates/hello.html

1
2
3
4
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<div th:text="${hello}">显示文字</div>
</html>

传入的值

1
2
3
4
5
6
7
8
9
@Controller
public class HelloController {

@RequestMapping("/hello")
public String hello(Map<String,String> m){
m.put("hello","您好");
return "hello";
}
}

10.4.1.2. unbescape

SpringBoot集成Thymeleaf 出现java.lang.ClassNotFoundException: org.unbescape.html.HtmlEscape异常问题

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
<dependency>
<groupId>org.unbescape</groupId>
<artifactId>unbescape</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>

unbescape 版本过低

10.4.1.3. th: 改变组件任意属性

1
<div th:id="${}" th:class="${}"></div>

10.4.2. 类比

10.4.3. 表达式

10.4.3.1. Simple expressions表达式语法

10.4.3.1.1. VariableExpressions获取变量值 ${…}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
VariableExpressions获取变量值: ${...} 获取对象属性,调用方法
- 使用内置基本对象:
#ctx: the context object.
#vars: the context variables.
#locale: the context locale.
#request: (only in Web Contexts) the HttpServletRequest object.
${foo}
${param.foo}
${param.size()}
${param.isEmpty()}
${param.containKey('foo')}
#response: (only in Web Contexts) the HttpServletResponse object.
#session: (only in Web Contexts) the HttpSession object.
${session.foo}
#servletContext: (only in Web Contexts) the ServletContext object.
${application.foo}
- 内置工具对象
#execInfo: information about the template being processed.
#messages: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{...} syntax.
#uris: methods for escaping parts of URLs/URIs`
#conversions: methods for executing the configured conversion service (if any).
#dates: methods for java.util.Date objects: formatting, component extraction, etc.
#calendars: analogous to #dates , but for java.util.Calendar objects.
#numbers: methods for formatting numeric objects.`
#strings: methods for String objects: contains, startsWith, prepending/appending, etc.
${#strings.toString(obj)}
#objects: methods for objects in general.
#bools: methods for boolean evaluation.
#arrays: methods for arrays.
#lists: methods for lists.
#sets: methods for sets.
#maps: methods for maps.
#aggregates: methods for creating aggregates on arrays or collections.
#ids: methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
10.4.3.1.2. SelectionVariableExpressions选择表达式 *{…}
1
2
3
4
5
6
7
SelectionVariableExpressions选择表达式 功能同${}: *{...} 
配合th:object使用,对象成员变量
<div th:object="${session.user}">
<p>Age:
<span th:text="*{age}?: '(no age specified)'">27</span>.
</p>
</div>
10.4.3.1.3. MessageExpressions获取国际化内容: #{…}
10.4.3.1.4. LinkURLExpressions定义URL: @{…}
1
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})} 	
10.4.3.1.5. Fragment Expressions 片段引用表达式: ~{…}
1
<div th:insert="~{commons :: main}">...</div>

10.4.3.2. Literals 字面量

1
2
3
4
5
Text literals: 'one text' , 'Another one!' ,... 
Number literals: 0 , 34 , 3.0 , 12.3 ,...
Boolean literals: true , false
Nullliteral: null
Literal tokens: one , sometext , main ,...

10.4.3.3. Text operations: 文本

1
2
Stringconcatenation: +
Literal substitutions: |The name is ${name}|

10.4.3.4. Arithmetic operations: 数学运算

1
2
Binaryoperators: +, -, *, /, %
Minussign(unaryoperator): -

10.4.3.5. Boolean operations: 布尔运算

1
2
Binary operators: and , or
Boolean negation (unary operator): ! , not

10.4.3.6. Comparisons and equality: 比较运算

1
2
Comparators: >, <, >=, <= (gt, lt, ge, le)
Equality operators: == , != ( eq , ne )

10.4.3.7. Conditional operators: 条件运算符 三元

1
2
3
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)

10.4.3.8. Special tokens: 特殊操作

10.4.3.8.1. No-Operation: _

10.4.4. 实例

值来自map

1
2
3
<p th:text="${hello}">
文本
</p>

TestController.java

1
2
3
4
5
@RequestMapping("/test")
public String test(HashMap<String,String> map){
map.put("hello","您好");
return "hello";
}

实现前后端分离

10.4.5. 实例2 th:each

1
2
3
4
5
6
7
@RequestMapping("/test")
public String test(HashMap<String,Object> map){

map.put("hello","<h1>您好</h1>");
map.put("user", Arrays.asList("a","b","c"));
return "hello";
}

前端

1
2
3
4
5
6
7
8
<!-- 转义 -->
<div th:text="${hello}"></div>
<!-- 不转义 -->
<div th:utext="${hello}"></div>
<!-- [[]] ==th:text转义 [()]==th:utext不转义-->
<div th:each="u:${user}">[[${u}]]</div>

<div th:each="u:${user}" th:text="${u}"></div>

实现

11. web自动配置

https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-auto-configuration

11.1. 7.1.1. Spring MVC Auto-configuration

org.springframework.boot.autoconfigure.web

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

SpringBoot提供了SpringMVC的自动配置

The auto-configuration adds the following features on top of Spring’s defaults:

自动配置添加了以下Spring配置:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    在WebMvcAutoConfiguration中实现ContentNegotiatingViewResolver,组合所有的视图解析器

    自动配置ViewResolver视图解析器,根据方法的返回值视图对象决定对视图对象的转发?重定向?

    WebMvcAutoConfiguration.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Bean
    @ConditionalOnBean(ViewResolver.class)
    @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
    public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver uses all the other view resolvers to locate
    // a view so it should have a high precedence
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
    }

    ContentNegotiatingViewResolver.java

    从bean工厂加载所有视图解析器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
    implements ViewResolver, Ordered, InitializingBean {
    @Nullable
    private List<ViewResolver> viewResolvers;

    @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);
    }
    }

    编写自定义视图解析器

    实现ViewResolver接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Bean
    public ViewResolver myView(){
    ViewResolver r=new myViewResolver();
    return r;
    }
    private class myViewResolver implements ViewResolver{
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
    return null;
    }
    }

    断点470行 DispatcherServlet的doDispatch方法 debug运行项目,自定义视图添加到容器

  • Support for serving static resources, including support for WebJars (covered later in this document)).

  • Automatic registration of Converter, GenericConverter, and Formatter beans.

    Converter 转换器,将public String test(){ return “hello”;}–> hello.html

    Formatter 格式化器,将2017-10-9 –> Date

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //WebMvcAutoConfiguration
    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
    WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
    }

    @ConfigurationProperties(prefix = "spring.mvc")
    public class WebMvcProperties {
    /**
    * Date format to use. For instance, `dd/MM/yyyy`.
    */
    private String dateFormat;
    }
  • Support for HttpMessageConverters (covered later in this document).

    SpringMVC用来转换Http请求和响应的: User对象转为Json发送到前台

    1
    2
    3
    public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>>{
    private final List<HttpMessageConverter<?>> converters;
    }

    HttpMessageConverters从容器中确定,获取所有的HttpMessageConverter

    给容器中加HttpMessageConverter,只需要将组件注册到容器中

  • Automatic registration of MessageCodesResolver (covered later in this document).

    定义错误代码格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    //WebMvcAutoConfiguration
    @Override
    public MessageCodesResolver getMessageCodesResolver() {
    if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
    DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
    resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
    return resolver;
    }
    return null;
    }

    //DefaultMessageCodesResolver
    public enum Format implements MessageCodeFormatter {

    /**
    * Prefix the error code at the beginning of the generated message code. e.g.:
    * {@code errorCode + "." + object name + "." + field}
    */
    PREFIX_ERROR_CODE {
    @Override
    public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
    return toDelimitedString(errorCode, objectName, field);
    }
    },

    /**
    * Postfix the error code at the end of the generated message code. e.g.:
    * {@code object name + "." + field + "." + errorCode}
    */
    POSTFIX_ERROR_CODE {
    @Override
    public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
    return toDelimitedString(objectName, field, errorCode);
    }
    };
    }
  • Static index.html support.

  • Custom Favicon support (covered later in this document).

  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    从容器中取Bean this.beanFactory.getBean()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
    FormattingConversionService mvcConversionService, Validator mvcValidator) {
    try {
    //从容器中取得
    return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
    return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
    }
    }
1
2
3
4
5
6
7

```java
/**
* Return the {@link ConfigurableWebBindingInitializer} to use for
* initializing all {@link WebDataBinder} instances.
*/
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(}

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

编写一个配置类 标注为@Configuration,实现WebMvcConfigurer

1
2
3
4
5
6
7
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/Hello").setViewName("hello");
}
}

11.2. 原理

WebMvcAutoConfiguration在做其他自动配置时导入

11.2.1. @Import(EnableWebMvcConfiguration.class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Import(EnableWebMvcConfiguration.class)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}

@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

//继承自父类DelegatingWebMvcConfiguration
//从容器中获取所有配置类WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}

//DelegatingWebMvcConfiguration.java 父类
//如需视图解析器
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
}
//WebMvcConfigurerComposite configurers
//将所有WebMvcConfigurer相关配置都调用
@Override
public void addViewControllers(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}

//ViewControllerRegistry.java
public ViewControllerRegistration addViewController(String urlPath) {
ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
registration.setApplicationContext(this.applicationContext);
this.registrations.add(registration);
return registration;
}

故容器中所有的WebMvcConfigurer相关配置都会被调用

自定义配置类也会被调用

11.3. 修改SpringBoot默认配置

SpringBoot在自动配置组件的时候,先看容器中有没有用户自己配置的bean,没有才进行配置,还可以将用户配置的和自动配置的组合起来

xxConfigurer 扩展配置

xxCustomizer 自定义配置

11.4. 全面接管SpringMVC

@EnableWebMvc 使自动配置失效

WebMvcAutoConfiguration只有当容器中没有WebMvcConfigurationSupport时才生效

1
2
3
4
5
6
7
8
9
10
11
12
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {}

12. web开发

12.1. index.html

12.1.1. 到templates下找 .html

12.1.1.1. @RequestMapping({“/“,”index.html”})

1
2
3
4
@RequestMapping({"/","index.html"})
public String index(){
return "index";
}

12.1.1.2. 使用自定义配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index.html").setViewName("index");
}

@Bean
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer configurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");
}
};
return configurer;
}
}

12.2. 加载css-th:href, jquery-th:src

pom.xml 添加依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.4.1-1</version>
</dependency>

th:src=”@{/webjars/jquery/3.3.1/jquery.js}

th:href=”@{/webjars/bootstrap/4.0.0/css/bootstrap.css}

而且是自适应的 项目server.servlet.context-path=/web 目录改为web,js,css仍然能访问到

1
2
<link th:href="@{/webjars/bootstrap/4.4.1-1/css/bootstrap.css}" rel="stylesheet">
<script th:src="@{/webjars/jquery/3.4.1/src/jquery.js}" type="text/javascript"></script>

如传入一个json数据,用ajax异步读取

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/user")
@ResponseBody
public User list(){

User u = new User();
u.setId(0);
u.setUname("a");
u.setPwd("123");
return u;
}

前端

$.post(“请求映射”,”请求参数”,”返回数据”,”响应方式”);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<script type="text/javascript">
$(function(){
$.post("./user","",function(data){
alert(data);
},"html");
});
</script>
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1>我的第一个 Bootstrap 页面</h1>
<p>重置窗口大小,查看响应式效果!</p>
</div>
</div>
</body>
</html>

效果

12.3. 国际化

新建i18n文件,新建login.properties,login_en_US.properties,login_zh_CN.properties

按Resource Bundle调出面板

Spring中配置国际化:使用ResourceBundleMessageSource管理,fmt:message取出内容

SpringBoot:

12.3.1. MessageSourceAutoConfiguration

MessageSourceAutoConfiguration内部配置了ResourceBundleMessageSource的bean

12.3.1.1. ResourceBundleMessageSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MessageSourceProperties {
//配置文件可以直接放在messages.properties下
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath location), each following the ResourceBundle convention with relaxed support for slash based locations. If it doesn't contain a package qualifier (such as "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
}

public class MessageSourceAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}

@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//设置国际化资源文件基础名(去掉国家代码) -- 只有文件名login
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;
}
}

设置语言位置

1
spring.messages.basename=i18n.login

测试页面

#{}就表示取message的值

1
2
3
4
5
6
7
<html lang="en" xmls:th="http://www.themeleaf.org">
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
<span th:text="#{login.username}"></span><input type="text" name="uname" id="uname">
<span th:text="#{login.password}"></span><input type="password" name="pwd">
<input type="submit" th:value="#{login.tip}">
</form>
</html>

chrome中测试

设置– >高级 –> 语言 –> 英语(美国) –> 移到顶部

测试

设置– >高级 –> 语言 –> 中文(简体) –> 移到顶部

乱码问题

12.3.2. 原理

国际化Locale区域信息对象;LocaleResolver获取区域信息对象

在WebMvcAutoConfiguration中定义区域视图解析器 LocaleResolver

区域信息来自于默认或请求头

@ConditionalOnMissingBean 当容器中没有区域视图解析器,才自动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
//request.getHeader("Accept-Language")
//Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
//区域信息来自于默认或请求头
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}

12.3.2.1. 自定义LocaleSesolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.runaccpeted.viewresolver;

import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class myLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//index.html?local=zh_CN
String local = request.getParameter("local");
//取默认
Locale locale = Locale.getDefault();
if (local!=null&&local!=""){
String[] split=local.split("_");
//public Locale(String language, String country) {}
locale= new Locale(split[0],split[1]);

}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

}
}

注册到容器中

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
@Bean
public LocaleResolver localeResolver(){
return new myLocaleResolver();
}
}

Thymeleaf设定链接

(local=’zh_CN’)表示请求参数 ==> ?local=zh_CN

1
2
<a th:href="@{/index.html(local='zh_CN')}">中文</a>
<a th:href="@{/index.html(local='en_US')}">英文</a>

实现

12.4. 修改后立即编译html

禁用缓存

1
2
#模板缓存禁用
spring.thymeleaf.cache=false

重新编译html

12.5. 登录实例

1
2
3
4
5
6
7
8
9
10
//post请求
@PostMapping("login")
public String login(@RequestParam(value = "uname") String uname,String pwd,HashMap<String,String> map){
if (uname.equals("a")&&pwd.equals("123")) {
map.put("success","欢迎");
return "hello";
}
map.put("error","用户名或密码错误");
return "index";
}

@PostMapping

1
2
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {}

12.5.1. 错误提示

th:if 优先级高于th:text

error不为空 div才会显示

1
<div th:text="${error}" th:if="${not #strings.isEmpty(error)}"></div>

登录成功应该采用重定向,防止重复提交

1
2
3
4
5
6
7
8
@PostMapping("login")
public String login(@RequestParam("uname") String uname,String pwd,HashMap<String,String> map){
if (uname.equals("a")&&pwd.equals("123")) {
return "redirect:/main.html";
}
map.put("error","用户名或密码错误");
return "index";
}

main.html映射到dashboard.html 这样dashboard.html内样式不会改变

1
registry.addViewController("/main.html").setViewName("dashboard");

但这样登录就没有意义了,其他浏览器可以直接访问main.html

12.5.2. 拦截器

请求处理

1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping("login")
public String login(@RequestParam("uname") String uname, String pwd, HashMap<String,String> map, HttpSession session){
if (uname.equals("a")&&pwd.equals("123")) {
User u = new User();
u.setUname(uname);
u.setPwd(pwd);
session.setAttribute("user",u);
return "redirect:/main.html";
}
map.put("error","用户名或密码错误");
return "index";
}

12.5.2.1. 自定义拦截器–实现HandlerInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.runaccpeted.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//有登录数据
Object user = request.getSession().getAttribute("user");
if (user!=null){
return true;
}
else {
//请求转发,让其登录
request.setAttribute("error","没有权限");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}
}

注册拦截器

/** 拦截所有映射

excludePathPatterns 剔除映射 /webjars/** 静态资源也会被拦截

1
2
3
4
5
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/","/index.html","/login","/webjars/**");
}

12.6. Thymeleaf公共页面抽取th:fragment

1
2
3
4
5
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>

<div th:insert="~{footer :: copy}"></div>

~{templatename::selector} 模板名:选择器

~{templatename::fragmentname} 模板名:片段名

例如

dashboard.html

1
2
3
4
5
6
<div class="container" th:fragment="uname">
<div class="jumbotron">
<h1 th:text="${session.user.uname}"></h1>

</div>
</div>

detail.html

1
<div th:insert="~{dashboard::uname}"></div>

12.6.1. th:insert,th:replace,th:include

th:insert 将公共片段整个插入到声明引入的元素中

th:replace 将声明引入的元素替换为公共片段

th:include 将被引入的片段内容插入到元素中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>

<div>
<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>
</div>

<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

<div>
&copy; 2011 The Good Thymes Virtual Grocery
</div>

12.6.2. th:fragment传参

~{模板名:片段名(参数)}

dashboard.html

1
2
3
4
5
6
<div class="container" th:fragment="uname">
<div class="jumbotron">
<h1 th:text="${session.user.uname}"></h1>
<h1 th:text="${value}=='12'?'active':''"></h1>
</div>
</div>

detail.html

1
<div th:insert="~{dashboard::uname(value='12')}"></div>

12.7. #dates

1
2
3
4
5
# Format date with the specified pattern
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}

# Create a date (java.util.Date) object for the current date and time`
${#dates.createNow()}

13. 错误机制

13.1. 错误页面

13.1.1. 浏览器

请求头accpet的是text/html

响应的content-type是text/html

13.1.2. Postman

以json数据形式返回

请求头accpet的是 */*

响应的content-type是application/json

13.2. 原理ErrorMvcAutoConfiguration

ErrorMvcAutoConfiguration 错误处理自动配置

一旦系统出现4xx或5xx错误,ErrorPageCustomizer就会生效,定制错误的响应规则,默认转到/error,BasicErrorController处理/error请求,

13.2.1. DefaultErrorAttributes

在页面共享信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}

//DefaultErrorAttributes.java
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}

13.2.2. BasicErrorController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//ErrorMvcAutoConfiguration.java
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

//BasicErrorController.java
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

//text/html, 浏览器请求处理,因为浏览器的请求头 accept:text/html
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);

//错误数据来自DefaultErrorAttributes的getErrorAttributes方法
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//错误页面指定
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

//application/json,其他设备请求处理, 请求头accept:*/*
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<Map<String, Object>>(status);
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
}

13.2.3. ErrorPageCustomizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//ErrorMvcAutoConfiguration.java
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
}

//ServerProperties.java
@NestedConfigurationProperty
private final ErrorProperties error = new ErrorProperties();

//ErrorProperties.java
//系统出现错误来到的error请求进行处理
@Value("${error.path:/error}")
private String path = "/error";

13.2.4. DefaultErrorViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//ErrorMvcAutoConfiguration.java
@Bean
@ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}

//DefaultErrorViewResolver.java
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

static {
Map<Series, String> views = new EnumMap<>(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认去 error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
//模板引擎可用的情况下返回errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用时,去viewName.html
return resolveResource(errorViewName, model);
}

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
}

13.3. 自定义错误页面

13.3.1. 有模板引擎:/error/错误码.html

将错误页面放在模板引擎文件夹下的error文件夹下

可以用4xx,5xx命名.html,用于匹配这种类型的所有错误,优先匹配最精确的404.html

eg.404

404.html

1
<div>404--</div>

页面

页面能获取的信息 来自DefaultErrorAttributes

timestamp:时间戳

status:错误状态

error:错误提示

exception:异常信息

message:异常消息

errors:JSR303数据校验的错误

1
2
3
4
5
6
<h1 th:text="${timestamp}"></h1>
<h1 th:text="${status}"></h1>
<h1 th:text="${error}"></h1>
<h1 th:text="${exception}"></h1>
<h1 th:text="${message}"></h1>
<h1 th:text="${errors}"></h1>

13.3.2. 没有模板引擎 static/error/错误码.html

resources文件下static/error/错误码.html

1
2
3
<h1 th:text="${timestamp}">时间戳</h1>
<h1 th:text="${status}">状态码</h1>
<h1 th:text="${error}">错误信息</h1>

13.3.3. SpringBoot默认错误页面

response.getWriter().append(builder.toString()); 拼接一个响应页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//ErrorMvcAutoConfiguration.java
//Simple {@link View} implementation that writes a default HTML error page.
private static class StaticView implements View {

private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

private static final Log logger = LogFactory.getLog(StaticView.class);

@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
response.setContentType(TEXT_HTML_UTF8.toString());
StringBuilder builder = new StringBuilder();
Date timestamp = (Date) model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}

13.4. 定制错误json数据

13.4.1. 浏览器和postman返回的是一样的数据

自定义异常类myException 继承RuntimeException

1
2
3
4
5
6
7
8
package com.runaccpeted.exception;

public class myException extends RuntimeException {

public myException() {
super("参数不能为空");
}
}

添加异常管理handler类

注解为@ExceptionHandler 处理myException异常数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.runaccpeted.handler;

import com.runaccpeted.exception.myException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class myExceptionHandler {

@ExceptionHandler(myException.class)
@ResponseBody
public Map<String, Object> handleException(Exception e){
Map<String,Object> map=new HashMap<>();
map.put("message",e.getMessage());
return map;
}
}

编写异常

1
2
3
4
5
6
7
@RequestMapping("/a")
public String exception(String a){
if (a.length()==0){
throw new myException();
}
return "index";
}

测试Postman

测试浏览器

13.4.2. 自适应浏览器和postman

BasicErrorController中getStatus来自request的

request.getAttribute(“javax.servlet.error.status_code”);

1
2
3
4
5
6
7
8
9
10
11
12
protected HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
}
catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}

实现自适应

请求转发到/error,使其调用ErrorMvcAutoConfiguration中的BasicErrorController处理/error

必须指定状态码,否则状态码为200

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.runaccpeted.handler;

import com.runaccpeted.exception.myException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class myExceptionHandler {

@ExceptionHandler(myException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map=new HashMap<>();
//Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
request.setAttribute("javax.servlet.error.status_code",500);
map.put("message",e.getMessage());
return "forward:/error";
}
}

但map.put(“message”,e.getMessage()); 没用

/error是被BasicErrorController接管的,该控制器返回一个ModelAndView,其中携带状态码,请求和响应,model,model来自于getErrorAttributes,getErrorAttributes来自于BasicErrorController的父类AbstractErrorController

1
2
3
4
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}

传入数据策略:

  1. 编写一个ErrorController实现类或AbstractErrorController子类放在容器中

  2. 数据通过errorAttributes.getErrorAttributes得到

    ErrorMvcAutoConfiguration中定义容器中没有ErrorAttributes时自动配置DefaultErrorAttributes

    1
    2
    3
    4
    5
    @Bean
    @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
    public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
    }

    在DefaultErrorAttributes中定义时间戳等信息

13.4.2.1. extends DefaultErrorAttributes

自定义ErrorAttributes继承DefaultErrorAttributes,注册到容器中,重写getErrorAttributes方法,添加信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
   package com.runaccpeted.error;


import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

@Component
public class myErrorAttributes extends DefaultErrorAttributes {

@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String,Object> map=super.getErrorAttributes(webRequest, includeStackTrace);
//添加格外信息
map.put("country","CN");

/**数据来源自myExceptionHandler
map.put("message",e.getMessage());
request.setAttribute("map",map);

public interface WebRequest extends RequestAttributes {
int SCOPE_REQUEST = 0;
int SCOPE_SESSION = 1;
Object getAttribute(String name, int scope);
}
*/
Map<String,Object> ext=(Map<String, Object>) webRequest.getAttribute("map",0);
map.put("ext",ext);
return map;
}
}

获得数据
Postman

浏览器

14. tomcat

SpringBoot采用内置tomcat

14.1. 修改tomcat相关配置

14.1.1. application.properties

也可以配置tomcat

配置来源于ServerProperties.java

1
2
3
4
5
6
7
8
9
10
11
12
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
private final Tomcat tomcat = new Tomcat();
private final Servlet servlet = new Servlet();
private Integer port;
private InetAddress address;
private final ErrorProperties error = new ErrorProperties();

public static class Tomcat {
private Charset uriEncoding = StandardCharsets.UTF_8;
}
}

对应于ServletWebServerFactoryAutoConfiguration类

1
2
3
4
5
6
7
8
9
10
@EnableConfigurationProperties(ServerProperties.class)
public class ServletWebServerFactoryAutoConfiguration {

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
}

14.1.2. implements WebServerFactoryCustomizer< TomcatServletWebServerFactory>

TomcatServletWebServerFactoryCustomizer是实现了WebServerFactoryCustomizer接口的,接口中只有一个方法customize(T factory)TomcatServletWebServerFactory 继承自AbstractServletWebServerFactoryAbstractServletWebServerFactory继承自AbstractConfigurableWebServerFactoryAbstractServletWebServerFactory中定义了tomcat默认接口中配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TomcatServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>{
}

@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
void customize(T factory);
}

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {}


public abstract class AbstractServletWebServerFactory extends AbstractConfigurableWebServerFactory
implements ConfigurableServletWebServerFactory {
//默认为/访问
private String contextPath = "";
}

public abstract class AbstractConfigurableWebServerFactory implements ConfigurableWebServerFactory {
//默认端口
private int port = 8080;

@Override
public void setPort(int port) {
this.port = port;
}
}

像TomcatServletWebServerFactoryCustomizer一样实现

WebServerFactoryCustomizer< TomcatServletWebServerFactory>注册到环境中,修改端口号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.runaccpeted.tomcat;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;

public class myServletWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>{
@Override
public void customize(TomcatServletWebServerFactory factory) {
//端口号设置为8083
factory.setPort(8083);
}
}

//注册到WebMvcConfigurer,WebMvcAutoConfiguration中EnableWebMvcConfiguration会扫描容器中的所有WebMvcConfigurer
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactoryWebServerFactoryCustomizer(){
return new myServletWebServerFactoryCustomizer();
}
}

运行

1
INFO 6396 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8083 (http) with context path ''

14.2. 注册Serlvet, Listener, Filter

  • ServletRegistrationBean

  • ServletListenerRegistrationBean

  • FilterRegistrationBean

14.2.1. ServletRegistrationBean

定义一个Servlet

service方法响应一句话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.runaccpeted.servlet;

import org.springframework.boot.web.servlet.ServletComponentScan;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class myServlet extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello Servlet");
}
}

注册到容器中

1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Bean
public ServletRegistrationBean servletRegistrationBean(){

//public ServletRegistrationBean(T servlet, String... urlMappings){}
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new myServlet(),"/servlet");
return registrationBean;
}
}

访问localhost:8083/serlvet

14.2.2. ServletListenerRegistrationBean

定义一个Listener

当开始请求时输出到控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.runaccpeted.listener;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

public class myServletListener implements ServletRequestListener{

@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("开始访问");
}
}

注册监听器

1
2
3
4
5
6
7
8
9
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){

ServletListenerRegistrationBean listenerRegistrationBean = new ServletListenerRegistrationBean(new myServletListener());
return listenerRegistrationBean;
}
}

​ 访问任意localhost:8083/test

14.2.3. FilterRegistrationBean

定义一个拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.runaccpeted.filter;

import javax.servlet.*;
import java.io.IOException;

public class myFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

response.getWriter().write("filter\n");
chain.doFilter(request,response);
}
@Override
public void destroy() {

}
}

注册

拦截刚定义的myServlet对应的/servlet

1
2
3
4
5
6
7
8
9
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
//public FilterRegistrationBean(T filter, ServletRegistrationBean<?>... servletRegistrationBeans)
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new myFilter(),servletRegistrationBean());
return registrationBean;
}
}

或者定义拦截哪些uri

1
2
3
4
5
6
7
8
@Bean
public FilterRegistrationBean filterRegistrationBean(){

FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new myFilter());
registrationBean.setUrlPatterns(Arrays.asList("/servlet","/test"));
return registrationBean;
}

访问localhost:8083/servlet

14.2.4. 原理 DispatcherServletAutoConfiguration

注册了DispatcherServlet

1
2
3
4
5
6
7
8
9
10
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
return dispatcherServlet;
}

注册了dispatcherServletRegistration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
public static class Servlet {
//Path of the dispatcher servlet.
//默认拦截/,不拦截jsp
private String path = "/";
//Load on startup priority of the dispatcher servlet.
private int loadOnStartup = -1;
public String getPath() {
return this.path;
}
}
}
*/
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
//The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
//public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}

14.3. 容器工厂

14.4. jetty容器–长连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 剔除tomcat -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 加载jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>

默认为0.0.0:8080

1
2
3
INFO 7475 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@15bcf458{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO 7475 --- [ main] o.s.b.web.embedded.jetty.JettyWebServer : Jetty started on port(s) 8080 (http/1.1) with context path '/'
INFO 7475 --- [ main] c.runaccpeted.SpringbootwebApplication : Started SpringbootwebApplication in 6.32 seconds (JVM running for 8.188)

14.5. undertow容器–不支持JSP

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

localhost:8080

1
2
3
4
INFO 7670 --- [           main] io.undertow                              : starting server: Undertow - 2.0.27.Final
INFO 7670 --- [ main] org.xnio : XNIO version 3.3.8.Final
INFO 7670 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.3.8.Final
INFO 7670 --- [ main] o.s.b.w.e.u.UndertowServletWebServer : Undertow started on port(s) 8080 (http) with context path ''

14.6. 嵌入式Servlet容器自动配置原理

14.7. ServletWebServerFactoryAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
//在web应用下才生效
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
}

14.7.1. WebServerFactoryCustomizer

1
2
3
4
@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
void customize(T factory);
}

14.7.1.1. TomcatServletWebServerFactoryCustomizer

14.7.1.1.1. ServerProperties
14.7.1.1.2. TomcatServletWebServerFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 将ServerProperties中的配置信息注册到TomcatServletWebServerFactoryCustomizer定制器中
public class TomcatServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {

private final ServerProperties serverProperties;

public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}

@Override
public void customize(TomcatServletWebServerFactory factory) {
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
}
if (tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
}
if (tomcatProperties.getUseRelativeRedirects() != null) {
customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects());
}
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
}
}

14.7.2. ServletWebServerFactoryConfiguration

14.7.2.1. EmbeddedTomcat

14.7.2.1.1. TomcatServletWebServerFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

@Configuration(proxyBeanMethods = false)
//必须用tomcat依赖
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
//用户没有自定义Servlet容器工厂
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}

14.7.2.2. ServletWebServerFactory

1
2
3
4
@FunctionalInterface
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
14.7.2.2.1. TomcatServletWebServerFactory

类中实例化一个tomcat,这个tomcat位于org.apache.catalina.startup包下,最终实例化生成对象使得TomcatServletWebServerFactoryCustomizer能被注册到容器中

getWebServer方法返回的TomcatWebServer构造器中真正定义tomcat启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors{
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}

//当端口号>=0就注册tomcat
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
}
14.7.2.2.1.1. Tomcat
1
2
3
4
5
6
7
8
package org.apache.catalina.startup;
public class Tomcat {
private static final StringManager sm = StringManager.getManager(Tomcat.class);
protected Server server;
protected int port = 8080;
protected String hostname = "localhost";
protected String basedir;
}
14.7.2.2.1.2. TomcatWebServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}

private void initialize() throws WebServerException {
synchronized (this.monitor) {
try {
// Start the server to trigger initialization listeners
this.tomcat.start();
}
catch (Exception ex) {
}
}
}

14.7.3. BeanPostProcessorsRegistrar

给容器中导入一些组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ServletWebServerFactoryAutoConfiguration {	
/**
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration.
*/
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
}
}

14.7.3.1. WebServerFactoryCustomizerBeanPostProcessor后置处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private ListableBeanFactory beanFactory;
private List<WebServerFactoryCustomizer<?>> customizers;

public WebServerFactoryCustomizerBeanPostProcessor() {
}

public void setBeanFactory(BeanFactory beanFactory) {
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, "WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory");
this.beanFactory = (ListableBeanFactory)beanFactory;
}
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//如果当前初始化的是一个WebServerFactory类型的组件
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
//获取所有定制器,调用一个定制器的customize方法给serlvet容器赋值
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}

private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
//从容器中获取所有WebServerFactoryCustomizer类型组件
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}

private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
}

14.7.3.2. LambdaSafe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static <C, A> Callbacks<C, A> callbacks(Class<C> callbackType, Collection<? extends C> callbackInstances,A argument, Object... additionalArguments) {
return new Callbacks<>(callbackType, callbackInstances, argument, additionalArguments);
}

private Callbacks(Class<C> callbackType, Collection<? extends C> callbackInstances, A argument,Object[] additionalArguments) {
super(callbackType, argument, additionalArguments);
this.callbackInstances = callbackInstances;
}


public void invoke(Consumer<C> invoker) {
this.callbackInstances.forEach((callbackInstance) -> {
invoke(callbackInstance, () -> {
invoker.accept(callbackInstance);
return null;
});
});
}

14.7.4. 配置步骤

  1. SpringBoot根据导入的依赖,给容器中添加相应的TomcatServletWebServerFactory,factory中配置了tomcat
  2. ServletWebServerFactoryAutoConfiguration中导入ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,根据org.apache.catalina.startup.Tomcat配置TomcatServletWebServerFactoryCustomizer
  3. 容器中某个组件要创建对象时惊动WebServerFactoryCustomizerBeanPostProcessor,这个类只要是嵌入式的serlvet容器工厂,后置处理器就工作
  4. 后置处理器从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法

14.7.5. 启动原理 - IoC容器启动创建嵌入式的Servlet容器

14.7.5.1. SpringApplication启动run方法

14.7.5.2. refreshContext(context)

SpringBoot刷新Ioc容器,创建IoC容器对象并初始化容器,创建容器中的每一个组件,web应用创建web环境org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext,不是web应用创建org.springframework.context.annotation.AnnotationConfigApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class SpringApplication {

//The class name of application context that will be used by default for non-web environments.
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";

//The class name of application context that will be used by default for web environments.
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
context = this.createApplicationContext();
refreshContext(context);
}

//判断是否为web环境
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}

return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

}

14.7.5.3. refresh(context);

刷新刚创建的IoC容器

1
2
3
4
5
6
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
}
protected void refresh(ApplicationContext applicationContext) {
((AbstractApplicationContext) applicationContext).refresh();
}

14.7.5.4. AbstractApplicationContext的refresh()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {

// Prepare this context for refreshing.
this.prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
this.prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
this.postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
this.invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
this.registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
this.initMessageSource();

// Initialize event multicaster for this context.
this.initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
this.onRefresh();

// Check for listener beans and register them.
this.registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
this.finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
this.finishRefresh();
}catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}

// Destroy already created singletons to avoid dangling resources.
this.destroyBeans();

// Reset 'active' flag.
this.cancelRefresh(var9);

// Propagate exception to caller.
throw var9;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

14.7.5.5. ServletWebServerApplicationContext的onRefresh()

web的IoC容器重写了onRefresh()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {

protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}

private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//获取容器中的servlet工厂
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
}

14.7.5.6. 获取嵌入式的Servlet容器工厂

ServletWebServerFactory factory = getWebServerFactory();

从IoC容器中获取ServletWebServerFactory,为TomcatServletWebServerFactory创建对象,后置处理器就获取所有定制器定制tomcat配置

14.7.5.7. 使用容器工厂获取嵌入式的servlet容器

this.webServer = factory.getWebServer(getSelfInitializer());

14.7.5.8. 嵌入式的servlet容器创建对象并启动Servlet容器

1
getSelfInitializer().onStartup(servletContext);

14.7.5.9. 先启动嵌入式的servlet容器,再将IoC容器中剩下没有创建出来的对象获取出来

1
2
3
4
5
6
7
public abstract class AbstractApplicationContext{

public void refresh() throws BeansException, IllegalStateException{
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
}
}

15. SpringBoot运行在外部tomcat

15.1. springboot以war项目运行

生成webapp文件夹和web.xml文件

15.2. 运行本地tomcat

报错:org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘defaultValidator’ defined in class path resource

解决:使用tomcat 8.5版本

测试类

TestController

1
2
3
4
5
6
7
8
9
@Controller
public class TestController {

@RequestMapping("/test")
@ResponseBody
public String test(){
return "Hello";
}
}

测试

15.3. 运行jsp

jsp文件放于webapp/WEB-INF内

使用视图解析器规定前后缀

1
2
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

15.4. 运行原理

15.4.1. SpringServletContainerInitializer

/org/springframework/spring-web/5.2.3.RELEASE/spring-web-5.2.3.RELEASE.jar!/META-INF/services/javax.servlet.ServletContainerInitializer文件内容

org.springframework.web.SpringServletContainerInitializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//所有HandlesTypes标注的类转入onStartup方法
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {

public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();

//创建类实例
initializers.add((WebApplicationInitializer)ReflectionUtils
.accessibleConstructor(waiClass, new Class[0]).newInstance());

Iterator var4;
var4 = initializers.iterator();

//类又调用自己的onStartup
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}

15.4.2. WebApplicationInitializer

15.4.3. SpringBootServletInitializer

这个类是WebApplicationInitializer的实现类,故也会被反射创建实例并调用onStartup方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
//创建容器
WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
public void contextInitialized(ServletContextEvent event) {
}
});
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}

}

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//创建SpringApplicationBuilder
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}

builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);

//调用configure方法,子类重写该方法并传入springboot主程序类
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});

//创建springboot应用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}

Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}

//运用springboot应用
return this.run(application);
}

}

16. SpringBoot部署到外部tomcat

16.1. 项目打包到war

1
<packaging>war</packaging>

16.2. 内置tomcat环境为provided

将内置的tomcat的jar设置成provided级别的, 对于编译和测试classpath有效,但在运行时候无效,避免与外部tomcat包中jar相冲

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

16.3. 配置jsp解析jar包

1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>

16.4. 修改启动类

添加ServletInitializer类,继承SpringBootServletInitializer,重写configure方法,SpringApplicationBuilder链接到拥有主方法的Tomcat8Application类

1
2
3
4
5
6
7
8
9
10
11
12
package com.runaccpeted;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Tomcat8Application.class);
}
}

16.5. 添加jsp页面

webapp/WEB-INF/login.jsp

1
2
3
<body>
Login
</body>

16.6. 配置前后缀

1
2
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

16.7. 配置视图

1
2
3
4
@RequestMapping("/login")
public String login(){
return "login";
}

访问 http://localhost:8080/login

16.8. 打包war

16.9. 将war复制到tomcat8的webapps下

运行tomcat8 /tomcat8/bin/catalina.sh run

访问 localhost:端口号/项目名/URl –> localhost:8080/tomcat8-0.0.1-SNAPSHOT/login

org.apache.jasper.JasperException: java.lang.IllegalStateException: No output folder 权限不够

sudo /tomcat8/bin/catalina.sh run

超级用户访问

/tomcat8/bin/catalina.sh stop

17. 外部运行原理

jar:执行springboot的main方法,启动IoC容器,创建嵌入式serlvet容器

war:启动服务器,服务器启动SpringBoot应用,启动IoC容器

本文结束  感谢您的阅读
  • 本文作者: Wang Ting
  • 本文链接: /zh-CN/2019/10/07/SpringBoot/
  • 发布时间: 2019-10-07 22:22
  • 更新时间: 2021-11-09 16:26
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!