SpringBoot

0-23一.SpringBoot 入门

1.简介

    SpringBoot 是 Spring 家族中的一个全新的框架,它用来简化 Spring 应用程序的创建和开发过程,也可以说是 Spring Boot 能简化我们之前采用的 SpringMVC+Spring+Mybatis 框架进行开发的过程。
	在以往我们采用 SpringMVC+Spring+Mybatis 框架进行开发的时候,搭建和整合三大框架,我们需要做很多工作,比如配置 web.xml,配置 Spring, 配置 Mybatis,并将它们整合在一起等,而 SpringBoot 框架对此开发过程进行了革命性的颠覆,抛弃了繁琐的 xml 配置过程,采用大量的默认配置简化我们的开发过程。
    采用 Spring Boot 可以非常容易和快速的创建基于 Spring 框架的应用程序,它让代码变得简单了,配置变简单了,部署变简单了,监控变简单了
    让开发变得极其简单和快速。

2.SpringBoot 特性

	能够快速创建基于 Spring 的应用程序
	能够直接使用 java main 方法启动内嵌的 tomcat,jetty 服务器运行 Spring boot 程序。
    根据约定的 start pom 来简化 maven 的配置,让 maven 的配置变得简单。
    根据项目的 maven 依赖配置, Spring boot 自动配置 Spring, Springmvc 等
    提供了程序的健康检查等功能
	基本可以完全不使用 xml 配置文件,采用注解配置

3.SpringBoot 四大核心

	自动配置:针对很多 Spring 应用程序和常见的应用功能,Spring Boot 能自动提供相关配置
	起步依赖:告诉 SpringBoot 需要什么功能,他就能引入需要的依赖库
	Actuator:让你能够深入运行中的 SpringBoot 应用程序,一探SpringBoot 程序的内部信息
	命令行界面:这是 SpringBoot 的可选特性,主要针对 Groovy 语言使用。

4.SpringBoot 优点

	快速创建独立运行的 Spring 项目以及与主流框架集成
	使用嵌入式的 Servlet 容器,应用无需打成 war 包
	Starters 自动 依赖与版本控制
	大量的自动配置,简化开发,也可修改默认值
	无需配置 xml ,无代码生成,开箱即用
	准生产环境的运行时应用监控
	与云计算的天然集成

5.微服务

	架构服务
	一个应用应该是一个小型服务;可以通过 HTTP 的方式进行互通。
	每一个功能元素最终都是一个可独立替换,可独立升级的软件单元。

6.环境准备

	环境约束
	Jdk1.8 : SpringBoot 1.7及以上
	Maven3.x : maven 3.3
	IntelligIDEA2017
	SpringBoot 1.5.9.RELEASE

6.1. Maven 设置

	给 maven 的 settings.xml 配置文件的 profiles 标签内添加代码如下
   在 settings.xml 182行添加以下代码:
   <profiles>
    <profile>
    <id>jdk-1.8</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>1.8</jdk>
    </activation>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>
    </profile>
   </profiles>

6.2.Idea 设置

![enter description here](./images/idea_配置.png)

[外链图片转存失败(img-tyGedOr9-1566610817792)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564815501985.png)]

7.Spring Boot HelloWorld

	一个功能:
	浏览器发送 hello 请求,服务器接受请求并处理,响应 hello world 字符串。
	步骤:

7.1.创建一个 maven 工程;(jar)

7.2.导入 springboot 相关的依赖

       <parent>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-parent</artifactId>
			<version>2.1.6.RELEASE</version>
        </parent>
        <dependencies>
        <dependency>
        <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
        </dependencies>

7.3. 编写一个主程序:启动 SpringBoot 项目

     import org.springframework.boot.SpringApplication;

        /**
         * @SpringBootApplication 来标注一个主程序类,说明这是一个 SpringBoot 应用
         */
        @SpringBootApplication
        public class HelloWorldMainApplication {
        
            public static void main(String[] args) {
                //Spring 应用启动起来
                SpringApplication.run(HelloWorldMainApplication.class,args);
            }
        }

7.4.编写 controller,service



package lan.springboot;

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

@Controller
public class HelloController {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello() {
        return "hello world,欢迎来到 springboot 的世界";
    }
}

7.5. 运行主程序测试

7.6. 简化部署

 <!--这个插件,可以将应用打包成一个可执行的 Jar 包-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

[外链图片转存失败(img-vF3b2S57-1566610817793)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757598191.png)]

[外链图片转存失败(img-maQR7L9b-1566610817794)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757669992.png)]

Lifecycle ——> package

[外链图片转存失败(img-zKiwEA43-1566610817794)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757769324.png)]

[外链图片转存失败(img-WgqmRqZW-1566610817795)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757802145.png)]

[外链图片转存失败(img-1lArrfns-1566610817795)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564757841968.png)]

启动项目 java -jar jar包名

[外链图片转存失败(img-k6dzaARe-1566610817796)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564758111941.png)]

将这个应用打成 jar 包,直接使用 java -jar 的命令执行。

8.HelloWorld 探索

8.1. POM 文件

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>

它的父项目是:
<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-dependencies</artifactId>
		<version>1.5.9.RELEASE</version>
		<relativePath>../../spring-boot-dependencies</relativePath>
	</parent>
它来真正管理 SpringBoot 应用里面的所有依赖版本;

SpringBoot 的版本仲裁中心:

以后我们导入依赖默认是不需要写版本;(没有在 dependencies 里面管理的依赖自然需要声明版本号)

8.2.导入的依赖

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

spring-boot-starter-web:

spring-boot-starter : spring-boot场景启动器(starter):帮我们导入了 web 模块正常运行所依赖的组件。

Spring Boot 将所有的功能场景都抽取出来,做成一个个的 starters(启动器),只需要在项目里面引入这些 starter 相关场景的所有依赖都会导入进来。要什么功能就导入什么启动器即可。

8.3.主程序类,主入口类

package lan.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @SpringBootApplication 来标注一个主程序类,说明这是一个 SpringBoot 应用
 */
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {
        //Spring 应用启动起来
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}

@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的配置类,SpringBoot 就应该运行这个类的 main 方法来启动 SpringBoot 应用;

@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 {

@SpringBootConfiguration : SpringBoot 的配置类:

​ 标注在某个类上,表示这是一个 SpringBoot 的配置类。

​ @Configuration : 配置类上来标注这个注解。

​ 配置类 ----- 配置文件;配置类也是容器中的一个组件:@Component

@EnableAutoConfiguration : 开启自动配置功能

​ SpringBoot 开启自动配置功能。

@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage : 自动配置包

​ @Import({Registrar.class}):

​ Spring 的底层注解 @import ,给容器中导入一个组件;导入的组件由 Registrar.class

将主配置类(@SpringBootApplication 标注的类)的所在包及下面所有子包里面的所有组件扫描到的Spring 容器。

​ @Import({EnableAutoConfigurationImportSelector.class});

EnableAutoConfigurationImportSelector : 导入哪些组件的选择器。

​ 将所有需要导入的组件以全类名的方式返回,然后这些组件 就会被添加到容器中。

​ 会给容器中导入非常多的自动配置类(xxxAutoConfiguration),给容器中导入这个场景需要的所有组件,并配置好这些组件。

SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);

SpringBoot 在启动的时候从类路径下的 META-INF/spring.factories 中获取 EnableAutoConfiguration 指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置。

J2EE 的整体整合解决方案和自动配置都在 spring-boot-autoconfigure-1.x.x.RELEASE.jar 中。

9.使用 Spring Initializer 快速创建 SpringBoot 项目

IDE 都支持使用 Spring 的项目创建向导快速创建一个 SpringBoot 项目

[外链图片转存失败(img-wYgfxkfB-1566610817797)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564762566356.png)]

[外链图片转存失败(img-KTwHFYBS-1566610817797)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564762683870.png)]

[外链图片转存失败(img-TF70pmxG-1566610817798)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564762731415.png)]

默认生成的 Spring Boot 项目:

  1. 主程序已经生成好了,我们只需要我们自己的逻辑;
  2. resource 文件夹中目录结构:
    1. static : 保存所有的静态资源;js,css,images;
    2. template : 保存所有的模板页面(SpringBoot 默认 jar 包使用嵌入式的 Tomcat,默认不支持 jsp 页面);可以使用模板引擎(freemarker、thymeleaf)。
    3. application.properties : SpringBoot 应用的配置文件

二.SpringBoot 配置文件

1.配置文件

SpringBoot 使用一个全局的配置文件,配置文件名是固定的;

  • ​ application.properties

  • ​ application.yml

配置文件的作用:修改 SpringBoot 自动配置的默认值;SpringBoot 在底层都给我们自动配置好。

配置文件放在 src/main/resources 目录或者 类路径/config 下。

YAML(YAML Ain’t Markup Lanauage)

​ YAML A Markup Language : 是一个标记语言

​ YAML isn’t Markup Language :

标记语言:

​ -.yml 是 YAML 语言的文件,以数据为中心,比 Json 、xml 等更适合做配置文件。

​ YAML : 配置例子

server:
	port : 8081

2.YAML 语法

2.1.基本语法

​ k : v : 表示一对键值对**(空格必须有)**;

​ 以空格的缩进来控制层级关系;只要左对齐的一列数据都是同一个层级的

server:
	port : 8081
	path : /hello

属性和值都是大小写敏感。

2.2.值的写法

2.2.1.字面量:普通的值(数字,字符串,布尔)

​ k: v : 字面直接来写

​ 字符串默认不用加上单引号或双引号;

​ “” : 双引号 ; 不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思

​ ‘’ : 单引号; 回转仪特殊字符,特殊字符最终只是一个普通的字符串数据

2.2.2.对象、Map(属性和值)(键值对);

​ k: V : 在下一行来写对象的属性和值的关系;注意缩进

​ 对象还是 k: v 的方式

friends :
		lastName : zhangsan	
    	age : 20

​ 行内写法:

friends : {lastName : zhangsan,age : 18}

​ 数组(List、Set)

​ 用 - 值表示数组中的一个元素

pets:
 - cat
 - dog
 - pig

​ 行内写法

pets: [cat,dog,pig]

3.配置文件注入

配置文件application.yml

server:
  port: 8081

person:
    lastName: zhangsan
    age: 18
    boss: false
    birth: 2019/08/03
    maps: {k1: v1,k2: 12}
    lists:
      - lisi
      - zhaoliu
    dog:
      name: 小狗
      age: 2

配置文件 application.properties

#server.port = 8081

#idea,properties 配置文件 utf-8
person.last-name=张三
person.age=18
person.birth=1993/04/28
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=15

会出现中文乱码,需要进行设置。

[外链图片转存失败(img-Rb78q1Y9-1566610817799)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564795235990.png)]

javabean

/**
 * 将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties : 告诉 SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *      prefix = "person" : 配置文件中哪个下面的所有随性进行一一映射
 *
 *    只有这个组件是容器中的组件,才能使用容器提供的 @ConfigurationProperties 功能;
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

可以导入配置文件处理器,以后编写配置就会存在提示。

 <!--导入配置文件处理器,配置文件进行绑定可以有提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

[外链图片转存失败(img-6bikEJJd-1566610817799)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564795477292.png)]

4. @Value获取值和 @ConfigurationProperties 获取值比较

@ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SpEL 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装(Map等) 支持 不支持

配置文件 yml 还是 properties 他们都能获取到值。

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用 @Value();

如果说,我们专门编写了一个 Javabean 来和配置文件进行映射,我们直接使用 @ConfigurationProperties ;

松散绑定

[外链图片转存失败(img-oe8XNw94-1566610817800)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564795813972.png)]

5.配置文件注入值数据校验

6.@ConfigurationProperties

  • 与 @Bean 结合为属性赋值
  • 与 @PropertySource(只能用于 properties 文件)结合读取指定文件

7.@ConfigurationProperties Validation

  • 支持 JSR303 进行配置文件值校验

8.@PropertySource&@ImportResource

8.1.@PropertySource : 加载指定的配置文件

/**
 * 将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties : 告诉 SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *      prefix = "person" : 配置文件中哪个下面的所有随性进行一一映射 : 默认从全局配置文件中获取值;
 *
 *    只有这个组件是容器中的组件,才能使用容器提供的 @ConfigurationProperties 功能;
 */
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    /**
     * <bean class="person">
     *      <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取 /#{SpEL}"></property>
     * </bean>
     */
//    @Value("${person.last-name}")
    private String lastName;
//    @Value("#{11*2}")
    private Integer age;
//    @Value("true")
    private Boolean boss;
    private Date birth;

person.properties

#server.port = 8081

#idea,properties 配置文件 utf-8
person.last-name=李四
person.age=18
person.birth=1993/04/28
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=15

8.2.@ ImportResource导入 Spring 的配置文件,让配置文件里面的内容生效;

SpringBoot 里面没有 Spring 的配置文件,我们自己编写的配置文件,也不能自动识别;

想要 Spring 的配置文件生效,加载进来;@ImportSource 标注在类上。

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

SpringBoot 推荐给容器中添加组件的方式;推荐使用全注解的方式

1.配置类 === Spring 配置文件

2.@Configuration :指明当前类是一个配置类,就是来替代之前的 Spring 配置文件。

3.@Bean 将方法的返回值添加到容器中

/**
 * @Configuration : 指明当前类是一个配置类,就是来替代之前的 Spring 配置文件
 */
@Configuration
public class MyAppConfig {

    /**
     * @Bean 将方法的返回值添加到容器中;容器中这个组件默认的 Id 就是方法名
     * @return
     */
    @Bean
    public HelloService helloService() {
        System.out.println("配置类@Bean给容器中的组件添加了....");
        return new HelloService();
    }
}

9.配置文件的占位符

9.1. RandomValuePropertySource : 配置文件中可以使用随机数

${random.value}、${random.int}、${random.long}

${random.int(10)}、${random.int[1024,65536]}

9.2.属性配置占位符

app.name=MyApp
app.description=${app.name} is a Spring Boot application
  • 可以在配置文件中引用前面配置过的属性(优先级前面配置过的这里都能用)
  • ${app.name:默认值}里指定找不到属性时的默认值
#server.port = 8081

#idea,properties 配置文件 utf-8
person.last-name=张三${random.uuid}
person.age=${random.int}
person.birth=1993/04/28
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.last-name}_dog
person.dog.age=15

10.Profile

Profile 是 Spring 对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境。
1.多 profile 文件形式
	-格式 : applicatioin-{profile}.properties
		application-dev.properties、application-prod.properties
2.yml 多 profile 文档块模式
3.激活方式:
	- 命令行  --spring.profiles.active = dev
	- 配置文件 spring.profiles.active = dev
	- jvm 参数 -Dspring.profiles.active = dev

10.1.多 profile 文件

-格式 : applicatioin-{profile}.properties
	application-dev.properties、application-prod.properties

10.2.yml 支持多文档块方式

server:
  port: 8081
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev

---
server:
  port: 8084
spring:
  profiles: prod



10.3. 激活方式

- 命令行  --spring.profiles.active = dev
- 配置文件 spring.profiles.active = dev
- jvm 参数 -Dspring.profiles.active = dev

命令行:

​ java -jar spring-boot-xxx.jar --spring-profiles.active=dev;

​ [外链图片转存失败(img-Aguy9yNd-1566610817801)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564803561771.png)]

虚拟机参数:

​ -Dspring.profiles.active = dev

11.配置文件的加载位置

SpringBoot 启动会扫描以下位置的 application.properties 或者 application.yml 文件作为 spring boot 的默认配置文件

—file: ./config/

—file: ./

—classpath: /config/

—classpath:/

—以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级内容会覆盖低优先级配置内容。

SpringBoot 会从这四个位置全部加载主配置文件:互补配置。

也可以通过配置 spring.config.location 来改变默认配置。

[外链图片转存失败(img-BXQsUOlz-1566610817801)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564804292884.png)]

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候里指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;

12.外部配置加载顺序

SpringBoot支持多种外部配置方式以下按照优先级从高到低排列;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置。

  1. 命令行参数

    ​ java -jar spring-boot.xxx.jar --server.port=8087 --server.context = /abc

    多个配置用空格分开; --配置项=值

  2. 来自 java:comp/env 的 JNDI 属性

  3. java 系统属性(System.getProperties)

  4. 操作系统环境变量

  5. RandomValuePropertySource 配置的 random.* 属性值

    优先加载带 profile

  6. jar 包外部的 application-{profile}.properties 或 application.yml(带 spring.profiles) 配置文件

  7. jar 包内部的 application-{profile}.properties 或 application.yml(带 spring.profile) 配置文件

  8. jar 包外部的 application-properties 或 application.yml(不带 spring.profile) 配置文件

  9. jar 包内部的 application-properties 或 application.yml(不带 spring.profile) 配置文件

    [外链图片转存失败(img-oD5ephT6-1566610817802)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564805888679.png)]

  10. @Configuration 注解类上的 @PropertySource

  11. 通过 SpringApplication.setDefaultProperties 指定的默认属性。

13.自动配置原理

13.1.自动配置原理:

1.SpringBoot 启动的时候加载朱配置类,开启了自动配置功能@EnableAutoConfiguration;

2.@EnableAutoConfiguration作用:

  • ​ 利用 EnableAutoConfigurationImportSelector 给容器中导入一些组件?

  • ​ 可以插入 selectImports() 方法的内容;

  • List<String> configurations = getCandidateConnfigurations(annotationMetadata, attributes);        //获取候选配置
    
    • SpringFactoriesLoader.loadFacotoryNames()
      //扫描所有 jar 包类路径下 META-INF/spring.factories 
      把扫描到的这些文件的内容包装成 properties 对象
      从 properties 中获取到 EnableAutoConfiguration.class 类(类名)对应的值,然后把他们添加到容器中
      

将类路径下 META-INF/spring.factories 里面配置的所有 EnableAutoConfiguration 的值添加到了容器中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

每一个这样的 xxxAutoConfiguration 类都是容器中的一个组件,都加入到容器中;用他们来做自动配置

  • 每一个自动配置类进行自动配置功能

  • 以 HttpEncodingAutoConfiguration 为例解释自动配置原理;

    @Configuration                 //表示这是一个配置类,
    @EnableConfigurationProperties({HttpProperties.class})  //启用指定类的ConfigurationProperties功能;将配置文件中对应的值和 HttpProperties 绑定起来;
    @ConditionalOnWebApplication(  //Spring底层 @Conditional 注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;判断当前应用是否是 web 应用,如果是,当前配置类生效
        type = Type.SERVLET
    )
    @ConditionalOnClass({CharacterEncodingFilter.class})  //判断当前项目有没有这个类
    @ConditionalOnProperty(     //判断配置文件中是否存在某个配置;
        prefix = "spring.http.encoding",
        value = {"enabled"},
        matchIfMissing = true
    )
    public class HttpEncodingAutoConfiguration {
      
      //已经和 SpringBoot 的配置文件映射了
      private final Encoding properties;
    
      //只有一个有参构造器的情况下,参数的值就会从容器中拿
        public HttpEncodingAutoConfiguration(HttpProperties properties) {
            this.properties = properties.getEncoding();
        }
          
        @Bean         //给容器中添加一个组件, 这些组件的某些值需要从 properties 中获取
        @ConditionalOnMissingBean
        public CharacterEncodingFilter characterEncodingFilter() {
            CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
            filter.setEncoding(this.properties.getCharset().name());
            filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
            filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
            return filter;
        }
    

    根据当前不同条件进行判断,决定这个配置类是否生效?

    一旦这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的 properties 类中获取的,这个类里面的每一个属性又是和配置文件绑定的。

  • 所有在配置文件中能配置的属性都是在 xxxProperties 类中封装;配置文件能配置什么就可以参照某个功能对应的这个属性类。

    @ConfigurationProperties(   // 从配置文件中获取指定的值和 bean 属性进行绑定
        prefix = "spring.http"
    )
    public class HttpProperties {
    

13.2.细节

1.@Conditional 派生注解(Spring 注解原生的 @Conditional 作用)

​ 作用:必须是 @Conditional 指定的条件成立,才给容器中添加组件,配置中的配置内容才生效。

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

自动配置类在一定条件下才能生效。

可以通过启用 debug=true 属性;来让控制台打印自动配置报告,这样就可以很方便的知道哪些自动配置生效。

# 开启 SpringBoot 的 debug
debug=true
============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:(自动配置类启用的)
-----------------

   CodecsAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)

   CodecsAutoConfiguration.JacksonCodecConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)

   CodecsAutoConfiguration.JacksonCodecConfiguration#jacksonCodecCustomizer matched:
      - @ConditionalOnBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found bean 'jacksonObjectMapper' (OnBeanCondition)

   DispatcherServletAutoConfiguration matched:


Negative matches:(没有启动,没有匹配成功的自动配置类)
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

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

   ArtemisAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

   BatchAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.springframework.batch.core.launch.JobLauncher' (OnClassCondition)

14.SpringBoot 精髓

	1. **==SpringBoot 启动会加载大量的自动配置类==**
	2. **==看我们需要的功能有没有 SpringBoot 默认写好的自动配置类;==**
            		3. **==再来看这个自动配置类到底配置了哪些组件;(如果存在我们要用到的组件,就不需要再来配置了)==**
        		4. **==给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性。我们就可以在配置文件中指定这些属性的值;==**

三.SpringBoot与日志

1.日志框架

日志门面(日志的抽象层) 日志实现
JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-logging Log4j JUL(java.util.logging) Log4j2 Logback

左边选一个门面(抽象层)、右边选择一个实现。

日志门面:SLF4j;

日志实现: Logback;

SpringBoot : 底层是 Spring 框架, Spring 框架默认是用 JCL;

SpringBoot 选用 SLF4j 和 logback;

2.SLF4j 使用

2.1.如何在系统中使用 SLF4j https://www.slf4j.org

以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;

给系统中导入 slf4j 的 jar 和 logback 的实现 jar.

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

图示:

[外链图片转存失败(img-pbEweDef-1566610817802)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564839139682.png)]

每一个日志的实现框架都有自己的配置文件。使用 slf4j 以后,配置文件还是做成日志实现框架自己本身的配置文件;

2.2.遗留问题

slf4j + logback : Spring(commons-logging)、 Hibernate(jboss-logging)、MyBatis、xxx

统一日志记录,即使是别的框架和我一起统一使用 sff4j 进行输出?

[外链图片转存失败(img-p6IBhkaE-1566610817803)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564839593148.png)]

如何让系统中所有的日志都统一到 slf4j;

1.将系统中其他日志框架先排除出去;

2.用中间包来替换原有的日志框架;

3.我们导入 slf4j 其他的实现;

3.SpringBoot 日志关系

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

SpringBoot 使用它来做日志功能

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

总结:

1.SpringBoot 底层也是使用 slf4j + logback 的方式进行日志记录。

2.SpringBoot 也把其他的日志都替换成了 slf4j;

  1. 中间替换包?
@SuppressWarnings("rawtypes")
public abstract class LogFactory {
   static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";
   static LogFactory logFactory = new SLF4JLogFactory();
}

[外链图片转存失败(img-ZmSFOj3F-1566610817803)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564841250015.png)]

  1. 如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉?

    Spring 框架用的是 commons-loggings;

    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-core</artifactId>
    	<exclusions>
    		<exclusion>
    			<groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
      	</exclusion>
      </exclusions>
    </dependency>
    

    SpringBoot 能自动适配所有的日志,而且底层使用 slf4j+logback 的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉;

    4.日志使用

    4.1.默认配置

    ​ SpringBoot 默认帮我们配置好了日志;

     //记录器
        Logger logger = LoggerFactory.getLogger(getClass());
        @Test
        public void contextLoads() {
            //日志的级别;
            //由低到高  trace < debug < info < warn < error.
            //可以调整需要输出的级别;日志就会在这个级别以后的高级别生效。
            logger.trace("这是 trace 日志...");
            logger.debug("这是 debug 日志...");
    
            //SpringBoot 默认给我们使用的是 info 日志级别,没有指定级别的就用 SpringBoot 默认规定的级别;root 级别
            logger.info("这是 info 日志...");
            logger.warn("这是 warn 日志");
            logger.error("这是 error 日志...");
     }
    
    日志输出格式:
    	%d 表示日期时间
    	%thread 表示线程名
    	%-5level : 级别从左现实 5 个字符宽度
    	%logger{50} 表示 logger 名字最长 50 个字符,否则按照句点分割。
    	%msg : 日志消息
     %n 是换行符
    

    SpringBoot 修改日志的默认配置 : 配置文件中配置日志信息:

    logging.level.lan=trace
    #logging.path=
    #当前项目下生成 springboot.log 日志,也可以指定完整的路径
    #logging.file=springboot.log
    #在当前磁盘的根路径下创建 spring 文件夹和里面的 log 文件夹;使用 spring.log 作为默认文件
    logging.path=/spring/log
    #在控制台输出日志的格式
    logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
    #指定文件中日志输出的格式
    logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} === %msg%n
    
    

    4.2.指定配置

    给类路径下放上每个日志框架自己的配置文件即可;SpringBoot 就不使用它默认配置的了

    Logging System Customization
    Logback logback-spring.xml,logback-spring.groovy,logback.xml or logback.groovy

| Log4j2 | log4j2-spring.xml or log4j2.xml |
| JDK(java util Logging) | logging.properties |

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

logback-spring.xml : 日志框架就不直接加载日志的配置项,由 SpringBoot 解析日志配置,可以使用 SpringBoot 的高级 Profile 功能

<springProfile name="staging">
	<!--configuraion to be enabled when the "staging" profile is active-->
  可以指定某段配置只在某个环境下生效
</springProfile>

[外链图片转存失败(img-B9NN4s38-1566610817804)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564844661919.png)]

4.3.切换日志框架

可以按照 slf4j 的日志适配图,进行相关的切换;

slf4j + log4j 的方式;

<dependency>
	<groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
  	<exclusion>
    	<artifactId>logback-classic</artifactId>
      <groupId>ch.qos.logback</groupId>
    </exclusion>
    <exclusion>
    	<artifactId>log4j-over-slf4j</artifactId>
      <groupId>org.slf4j</groupId>
    </exclusion>
  </exclusions>
</dependency>

切换为 log4j2

<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>

四. Spring Boot 与 Web 开发

使用 SpringBoot ;

1. **创建 SpringBoot 应用,选中我们需要的模块;**
2. **SpringBoot 已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来;**
3. **自己编写业务代码;**

自动配置原理?

​ 这个场景 SpringBoot 帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx

xxxxAutoConfiguratiion : 帮我们给容器中自动配置组件
xxxxProperties : 配置类来封装配置文件的内容;

1.SpringBoot 对静态资源的映射规则

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties{
  //可以设置和静态资源有关的参数
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }
//配置欢迎页
 @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
            return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
        }
//配置喜欢的图标 
@Configuration
        @ConditionalOnProperty(
            value = {"spring.mvc.favicon.enabled"},
            matchIfMissing = true
        )
        public static class FaviconConfiguration implements ResourceLoaderAware {
            private final ResourceProperties resourceProperties;
            private ResourceLoader resourceLoader;

            public FaviconConfiguration(ResourceProperties resourceProperties) {
                this.resourceProperties = resourceProperties;
            }

            public void setResourceLoader(ResourceLoader resourceLoader) {
                this.resourceLoader = resourceLoader;
            }

            @Bean
            public SimpleUrlHandlerMapping faviconHandlerMapping() {
                SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
                mapping.setOrder(-2147483647);
                mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler()));
                return mapping;
            }

            @Bean
            public ResourceHttpRequestHandler faviconRequestHandler() {
                ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
                requestHandler.setLocations(this.resolveFaviconLocations());
                return requestHandler;
            }

            private List<Resource> resolveFaviconLocations() {
                String[] staticLocations = WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.getResourceLocations(this.resourceProperties.getStaticLocations());
                List<Resource> locations = new ArrayList(staticLocations.length + 1);
                Stream var10000 = Arrays.stream(staticLocations);
                ResourceLoader var10001 = this.resourceLoader;
                this.resourceLoader.getClass();
                var10000.map(var10001::getResource).forEach(locations::add);
                locations.add(new ClassPathResource("/"));
                return Collections.unmodifiableList(locations);
            }

[外链图片转存失败(img-ZqfvZ2WU-1566610817805)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564851462582.png)]

1.所有 /webjars/**,都去 classpath:/META-INF/resources/webjars/找资源;

​ webjars : 以 jar 包的方式引入 静态资源。 https://www.webjars.org/

<!--引入 jquery-webjar-->   在访问的时候只需要写 webjars 下面资源的名称即可
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.4.1</version>
        </dependency>

http://localhost:8080/webjars/jquery/3.4.1

2.“/**"访问当前项目的任何资源,(静态资源文件夹)

"classpath:/META-INF/resources/", 
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/" : 当前项目的根目录

3.欢迎页 ;静态资源文件夹下的所有 index.html 页面,被 "/**"映射;

localhost:8080/ 找 index 页面

4.所有的 **/favicon.ico 都是在静态资源文件下找;

2.模板引擎

jsp、Velocity、FreeMarker、 Thymeleaf

[外链图片转存失败(img-L7trbe40-1566610817806)(C:\Users\laughig\Desktop\template-engine.png)]

SpringBoot 推荐的 Thymeleaf;

语法简单,功能强大;

1.引入 Thymeleaf;

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

切换 thymeleaf 版本
<thymeleaf.verison>3.0.11.RELEASE</thymeleaf.verison>
<!--布局功能的支持程序 thymeleaf3主程序 layout2 以上版本-->>
<!--thymeleaf2 layout1-->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>

2.Thymeleaf 使用&语法

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
  
  //只要我们把 HTML 页面放在 classpath:/templates/, thymeleaf就能自动渲染
  
    private String mode = "HTML";
    private Charset encoding;
    private boolean cache;
    private Integer templateResolverOrder;
    private String[] viewNames;
    private String[] excludedViewNames;
    private boolean enableSpringElCompiler;
    private boolean renderHiddenMarkersBeforeCheckboxes;
    private boolean enabled;
    private final ThymeleafProperties.Servlet servlet;
    private final ThymeleafProperties.Reactive reactive;

只要我们把 HTML 页面放在 classpath:/templates/, thymeleaf就能自动渲染

使用:

2.1.导入 thymeleaf 的名称空间

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

2.2.使用 thymeleaf 语法;

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>success页面</title>
</head>
<body>
    <h1>success.html————> 成功!</h1>
    <!--th:text 将 div 里面的文本内容设置为-->
    <div th:text="${hello}"></div>
</body>
</html>

2.3.thymeleaf 语法规则

  • th:text ;改变当前元素里面的文本内容。

    th : 任意 html 属性;来替换原生属性的值。

    [外链图片转存失败(img-CgPYT14E-1566610817807)(C:\Users\laughig\Desktop\2018-02-04_123955.png)]

  • 表达式?

    
    
    Simple expressions(表达式语法):
        Variable Expressions : ${...}  : 获取变量值 ; ORGL
    				1.获取对象的属性、调用方法
    				2.使用内置的基本对象
    					#ctx : the context object.
    					#vars : the context variables.
    					#locale : the context locale.
    					#request : (only in Web Contexts) the HttpServletRequest object.
    					#response : (only in Web Contexts) the HttpServletResponse object.
    					#session : (only in Web Contexts) the HttpSession object.
    					#servletContext : (only in Web Contexts) the ServletContext object.
            3.
        Selection Variable Expressions : *{...}     : 
        Message Expressions : #{...}
        Link URL Expressions : @{...}
        Fragment Expressions : ~{...}
    Literals
    Text literals : 'one text', 'Another one!', ...
    Number literals : 0,34,3.0,12.3,...
    Boolean literals : true,,false
    Null literal : null
    Literal tokens : one, sometext, main,...
    Text operations :
    Strinng concatenation : +
    Literal substitutions : |The name is ${name}|
    Arithmetic operations : 
    Binary operators : +,-,*,/,%
    Minus sign(unary operator): -
    Boolean operations :
    
    Simple expressions(表达式语法):
        Variable Expressions : ${...}  : 获取变量值 ; ORGL
    				1.获取对象的属性、调用方法
    				2.使用内置的基本对象
    					#ctx : the context object.
    					#vars : the context variables.
    					#locale : the context locale.
    					#request : (only in Web Contexts) the HttpServletRequest object.
    					#response : (only in Web Contexts) the HttpServletResponse object.
    					#session : (only in Web Contexts) the HttpSession object.
    					#servletContext : (only in Web Contexts) the ServletContext object.
    				3.内置的一些工具对象:
    					#execInfo : information about the template being processed.
    					#messages : methods for obtaining externalized messages inside variables expressions,in the same way as they would be obtained using 
    											#{...} syntax.
    					#uris : methods for escaping parts of URLs/URIs.
    					#conversions : methods for executing the configured conversion service(if any)
    					#dates : methods for java.util.Date objects : formatting, component extraction, etc.
    					#calendars : analogous to #dates, but for java.util.Calendar objects.
    					#numbers : method for formatting numberic objects.
    					#strings : method for String objects : contains, startsWith, prepending/appending, etc.
    					#objects : methods for objects in general.
    					#bools : methods for boolean evaluation.
    					#arrays : methods for arrays.
    					#lists : methods for lists.
    					#sets : methods for sets.
    					#maps : methods for maps.
    					#aggregates : methods for creating aggregates on arrays or collections.
    					#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an interation).
        Selection Variable Expressions : *{...}     : 选择表达式  : 和 ${} 表达式功能一样 
        		补充:配合 th:object = "${session.user}":
        			<div th:object="${session.user}">
        				<p> Name : <span th:text="*{firstName}">Sebastian</span>.</p>
        				<p> Surname : <span th:text="*{lastName}">Papper</span>.</p>
        				<p> Nationality : <span th:text="*{nationality}">Saturn</span>.</p>
        			</div>
        			
        Message Expressions : #{...}         获取国际化内容
        Link URL Expressions : @{...}        定义 URL;
        		@{/order/process(execId=${execId},execType='FAST')}
        Fragment Expressions : ~{...}    : 片段引用表达式
        		<div th:insert="~{commons :: main}">...</div>
    Literals(字面量)
          Text literals : 'one text', 'Another one!', ...
          Number literals : 0,34,3.0,12.3,...
          Boolean literals : true,,false
          Null literal : null
          Literal tokens : one, sometext, main,...
    Text operations (文本操作):
          Strinng concatenation : +
          Literal substitutions : |The name is ${name}|
    Arithmetic operations(数学运算) : 
          Binary operators : +,-,*,/,%
          Minus sign(unary operator): -
    Boolean operations(布尔运算) :
          Binary operators : and, or
          Boolean negation (unary operator) : ! , not
    Commparsons and equality (比较运算):
          Comparators : > , < m >=, <= (gt, lt, ge, le)
          Equality operators : ==, != (eq, ne)
    Conditional operators条件运算(三元运算符) :
          If-then : (if) ? (then)
          If-then-else : (if) ? (then) : (else)
          Default : (value) ?: (defaultvalue)
    Special tokens : 
    		  No-Operation : _3.SpringMVC 自动配置
    

3.SpringMVC 自动配置

https://docs.spring.io/spring-boot/docs/1.5.21.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc

3.1.Spring MVC auto-configuration

Spring Boot 自动配置好了 SpringMVC

以下是 SpringBoot 对 SpringMVC 的默认配置:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 自动配置了 ViewResolver(视图解析器:根据方法的返回值得到视图对象(View) ,视图对象决定如何渲染 (转发?重定向?))
    • ContentNegotiatingViewResolver : 组合所有的视图解析器的;
    • 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来。
  • Support for serving static resources, including support for WebJars (see below). 静态资源文件夹路径,webjars

  • 自动注册了 Converter, GenericConverter, Formatter beans.

    • Converter : 转换器;public String hello(User user) : 类型转换使用 Converter.

    • Formatter : 格式化器: 2017-12-17 === Date;

      @Bean
      @ConditionalOnProperty(prefix="spring.mvc", name="date-format") //在文件中配置日期格式化的规则
      public Formatter<Date> dateFormatter() {
        return new DateFormatter(this.mvcProperties.getDateFormat());   //日期格式化组件
      }
      

      自己添加的格式化器转换器,我们只需要放在容器中即可。

  • Support for HttpMessageConverters (see below).

    • HttpMessageConverters : SpringMVC 用来转换 Http 请求和响应的;User-Json;

    • HttpMessageConverters : 是从容器中确定;获取所有的 HttpMessageConverter;

      自己给容器中添加 HttpMessageConverter ,只需要将自己的组件注册容器中(@Bean)

  • Automatic registration of MessageCodesResolver (see below). 定义错误代码生成规则。

  • Static index.html support. 静态首页访问

  • Custom Favicon support (see below). favicon.ico

  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).

    我们可以配置一个 ConfigurableWebBindingInitializer 来替换默认的;

    初始化 WebDataBinder
    请求数据 ==== JavaBean.
    
  • org.springframework.boot.autoconfigure.web : web 的所有自动场景。

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

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

3.2.扩展SpringMVC

<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
	<mvc:interceptor>
  	<mvc:mapping path="/hello"/>
    <bean></bean>
  </mvc:interceptor>
</mvc:interceptors>

编写一个配置类(@Configuration), 是 WebMvcConfigurerAdapter 类型;不能标注 @EnableWebMvc

既保留了所有的自动配置,也能用我们扩展的配置;

//使用 WebMvcConfigurerAdapter 可以来扩展 SpringMVC 功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers
        //浏览器发送  /lan 请求来到  success
        registry.addViewController("/lan").setViewName("success");
    }

}

原理:

1.WebMvcAutoConfiguration 是 SpringMVC 的自动配置类

  1. 在做其他自动配置时会导入; @Import(EnableWebMvcConfiguration.class)

    @Configuration
        public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
          
        	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
          //从容器中获取所有的 WebMvcConfigurer
          @Autowired(required = false)
          public void setConfigurers(List<WebMvcConfigurers> configurers) {
            if (!CollectionUtils.isEmpty(configurers)) {
              this.configurers.addWebMvcConfigurers(configurers);
              //一个参考实现:将所有的 WebMvcConfigurer 相关配置都来一起调用
              @Override
              public void addViewController(ViewControllerRegistry registry) {
                for (WebMvcConfigurer delegate : this.delegates) {
                  delegate.addViewControllers(registry);
                }
              }
            }
            
          }
    
  2. 容器中所有的 WebMvcConfigurer 都会一起使用;

  3. 我们的配置类也会被调用;

    效果: SpringMVC 的自动配置和我们的扩展配置都会起作用。

3.3.全面接管 SpringMVC

SpringBoot 对 SpringMVC 的自动配置不需要了,所有的配置都是我们自己配;所有的 SpringMVC 的自动配置都失效了;

我们需要在配置类中添加 @EnableWebMvc即可.

//使用 WebMvcConfigurerAdapter 可以来扩展 SpringMVC 功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers
        //浏览器发送  /lan 请求来到  success
        registry.addViewController("/lan").setViewName("success");
    }

}

原理:

​ 为什么 @EnableWebMvc 自动配置就失效了?

1.@EnableWebMvc 的核心

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc{}
@Configuration
public class DelegatingWebMvcConfigutation extends WebMvcConfigurationSupport{}
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration{}

4.@EnableWebMvc 将 WebMvcConfigurationSupport 组件导入进来;

5.导入的 WebMvcConfigurationSupport 只是 SpringMVC 最基本的功能;

4.如何修改 SpringBoot 的默认配置

模式:

​ 1.SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver) 将用户配置的和自己默认的组合起来;

2. 在 SpringBoot 中会有非常多的 xxxConfigurer 帮我们进行扩展配置,

5.RestfulCRUD

5.1.默认访问首页

直接启动项目即可或者在配置类中编写如下代码:

    //所有的 WebMvcConfigurerAdapter 组件都会一起起作用
    @Bean               //将组件注册在容器中
    public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
       WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
            @Override
           public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("index");
                registry.addViewController("/index.html").setViewName("index");
            }
        };
       return adapter;
    }

[外链图片转存失败(img-UgOoprcW-1566610817808)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564917277761.png)]

[外链图片转存失败(img-Kv4pfMRb-1566610817808)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564917345102.png)]

修改引入前端样式地址。

也可以修改配置文件修改访问项目地址。(http://localhost:8080 改为 http://localhost:8080/crud)

[外链图片转存失败(img-JXdBo6h4-1566610817809)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564917408578.png)]

5.2.国际化

1.编写国际化配置文件;(SpringMVC)

2.使用 ResourceBundleMessageSource 管理国际化资源文件;(SpringMVC)

3.在页面使用 fmt:message 取出国际化内容(SpringMVC)

SpringBoot

步骤:

1.编写国际化配置文件,抽取页面需要显示的国际化消息

[外链图片转存失败(img-g9714mtw-1566610817809)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564918060832.png)]

[外链图片转存失败(img-ZiSO1dve-1566610817810)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564918281324.png)]

2.SpringBoot 自动配置好了管理国际化资源文件的组件;

[外链图片转存失败(img-MDRhlpEj-1566610817810)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564918801693.png)]

3.去页面获取国际化的值;

设置全局配置;properties 文件:修改为 utf-8,并且转换为 ascii.

[外链图片转存失败(img-1HxIFhuY-1566610817810)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564919413988.png)]

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
		<!-- Custom styles for this template -->
		<link href="asserts/css/signin.css" th:href="@{asserts/css/signin.css}" rel="stylesheet">
	</head>

	<body class="text-center">
		<form class="form-signin" action="dashboard.html">
			<img class="mb-4" th:href="@{asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
			<label class="sr-only" th:text="#{login.username}">Username</label>
			<input type="text" class="form-control" placeholder="Username" required="" autofocus="" th:placeholder="#{login.username}">
			<label class="sr-only" th:text="#{login.password}">Password</label>
			<input type="password" class="form-control" placeholder="Password" required="" th:placeholder="#{login.password}">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me"/> [[#{login.remember}]]
        </label>
			</div>
			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm">中文</a>
			<a class="btn btn-sm">English</a>
		</form>

	</body>

</html>

效果:根据浏览器语言设置的信息切换了国际化

原理:

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

 public LocaleResolver localeResolver() {
            if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            } else {
                AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
                return localeResolver;
            }
        }

默认的就是根据请求头带来的区域信息获取Localer进行国际化。

4.点击链接(中文/English) 切换国际化

编写解析器:编写组件

package lan.springboot.crud.springbootwebrestfulcrud.component;

import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;

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) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();    //系统默认的
        if (!StringUtils.isEmpty(l)) {
            String[] split = l.split("_");
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

将组件添加到容器中。

  @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }

[外链图片转存失败(img-j7tuHbEJ-1566610817811)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564921476875.png)]

5.3.登录

模板引擎页面修改以后,要实时生效

1.禁用模板引擎的缓存

#禁用缓存
spring.thymeleaf.cache=false

2.页面修改完成以后 ctrl+f9 : 重新编译。

​ 登录错误消息的提示

<p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

防止重复提交;

[外链图片转存失败(img-cl3tBTTC-1566610817812)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564924386154.png)]

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

登录:

 @PostMapping("/user/login")
//    @RequestMapping(value = "/user/login", method = RequestMethod.POST)
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Map<String, Object> map) {
        if(!StringUtils.isEmpty(username) && "123456".equals(password)) {
            //登录成功,防止表单重复提交,可以重定向到主页
            return "redirect:/main.html";
        } else{
            //登录失败
            map.put("msg","用户名密码错误");
            return "index";
        }
    }

5.4.拦截器进行登录拦截检查

session.setAttribute("loginUser",username);

[外链图片转存失败(img-7xSPYG8P-1566610817812)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564928361706.png)]

package lan.springboot.crud.springbootwebrestfulcrud.component;

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

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

/**
 * 登录检查
 */
public class LoginHandlerInterceptor implements HandlerInterceptor {

    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser == null) {
            //未登录,返回登录页面
            request.setAttribute("msg","没有权限请先登录");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        } else {
            //已登录,执行请求
            return true;
        }
    }

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

    }

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

    }
}

 //注册拦截器
           @Override
           public void addInterceptors(InterceptorRegistry registry) {
//                super.addInterceptors(registry);
               //SpringBoot 已经做好了静态资源映射
               registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
                                        .excludePathPatterns("/index.html","/","/user/login");
           }
        };

[外链图片转存失败(img-eEFZmRrS-1566610817813)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1564928325262.png)]

5.5. CRUD—员工列表

实验要求:

  1. RestfuCRUD: CRUD 满足 Rest 风格;

    URI : /资源名称/资源标识 HTTP 请求方式区分对资源 CRUD 操作

    普通 CRUD(uri 来区分操作) RestfulCRUD
    查询 getEmp emp—GET
    添加 addEmp?xxx emp—POST
    修改 updateEmp?id=xxx&xxx=xx emp/{id}—PUT
    删除 deleteEmp?id=1 emp/{id}—DELETE
  2. 实验的请求架构:

    请求 URI 请求方式
    查询所有员工 emps GET
    查询某个员工 emp/{id} GET
    添加页面 emp GET
    添加员工 emp POST
    来到修改页面(查询员工进行信息回显) emp/{id} GET
    修改员工 emp PUT
    删除员工 emp/1 DELETE
  3. 员工列表:

    thymeleaf 公共页面元素抽取:

    1.抽取公共片段
    <div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
    </div> 
    
    2.引入公共片段
    <div th:insert="~{footer :: copy}"></div>
    ~{templatename :: selector} : 模板名 :: 选择器
    ~{templatename :: fragmentname} : 模板名 :: 片段名
    
    3.默认效果:
    insert 的功能片段在 div 标签中
    如果使用 th:insert 等属性进行引入,可以不用写 ~{}
    

    三种引入功能片段的 th 属性:

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

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

    th:include:将被引入的片段的内容包含进这个标签中

    <footer th:fragment="copy">
    	&copy; 2011 The Good Thymes Virtual Grocery
    </footer>
    
    
    引入方式
    <div th:insert="footer :: copy"></div>
    <div th:replace="footer :: copy"></div>
    <div th:include="footer :: copy"></div>
    
    效果
    
  4. 链接高亮

引入片段的时候传入参数:

[外链图片转存失败(img-999ojfps-1566610817813)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565015845897.png)]

首页

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}"  rel="stylesheet">

		<!-- Custom styles for this template -->
		<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>

		<!--引入 topbar-->
		<div th:replace="commons/bar::topbar"></div>

		<div class="container-fluid">
			<div class="row">
				<!--引入 sidebar-->
				<div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>
				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
						<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
						</div>
						<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
						</div>
					</div>
					<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
						<h1 class="h2">Dashboard</h1>
						<div class="btn-toolbar mb-2 mb-md-0">
							<div class="btn-group mr-2">
								<button class="btn btn-sm btn-outline-secondary">Share</button>
								<button class="btn btn-sm btn-outline-secondary">Export</button>
							</div>
							<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
                This week
              </button>
						</div>
					</div>

					<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>

					
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" ></script>
		<script type="text/javascript" src="asserts/js/popper.min.js" ></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" ></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js" ></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js" ></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

员工管理页面

5.6.CRUD—员工添加

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link href="asserts/css/dashboard.css" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<!--引入抽取的 topbar -->
		<!--模板名:会使用 thymeleaf 的前后缀配置规则进行解析-->
		<!--<div th:insert="~{dashboard :: topbar}"></div>-->
		<div th:replace="commons/bar :: topbar"></div>
		<div class="container-fluid">
			<div class="row">
				<!--引入侧边栏-->
				<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2><a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加</a> </h2>
					<div class="table-responsive">
						<table class="table table-striped table-sm">
							<thead>
								<tr>
									<th>#</th>
									<th>lastName</th>
									<th>email</th>
									<th>gender</th>
									<th>department</th>
									<th>操作</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="emp:${emps}">
									<td th:text="${emp.id}"></td>
									<td th:text="${emp.lastName}"></td>
									<td th:text="${emp.email}"></td>
									<td th:text="${emp.gender} == 0 ? '男' : '女'"></td>
									<td th:text="${emp.department.departmentName}"></td>
									<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"></td>
									<td>
										<button class="btn btn-sm btn-primary">编辑</button>
										<button class="btn btn-sm btn-danger">删除</button>
									</td>
								</tr>
							</tbody>
						</table>
					</div>
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

公共页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>公共页面</title>
    <!-- Bootstrap core CSS -->
    <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}"  rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
    <style type="text/css">
        /* Chart.js */

        @-webkit-keyframes chartjs-render-animation {
            from {
                opacity: 0.99
            }
            to {
                opacity: 1
            }
        }

        @keyframes chartjs-render-animation {
            from {
                opacity: 0.99
            }
            to {
                opacity: 1
            }
        }

        .chartjs-render-monitor {
            -webkit-animation: chartjs-render-animation 0.001s;
            animation: chartjs-render-animation 0.001s;
        }
    </style>
</head>
<body>
<!--topbar-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>

<!--sidebar-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link active"
                   th:class="${activeUri == 'main.html'?'nav-link active':'nav-link'}"
                   href="#" th:href="@{main.html}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                    Dashboard <span class="sr-only">(current)</span>
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
                        <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
                        <polyline points="13 2 13 9 20 9"></polyline>
                    </svg>
                    Orders
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
                        <circle cx="9" cy="21" r="1"></circle>
                        <circle cx="20" cy="21" r="1"></circle>
                        <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
                    </svg>
                    Products
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#"

                   th:class="${activeUri == 'emps'?'nav-link active':'nav-link'}"

                   th:href="@{/emps}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                        <circle cx="9" cy="7" r="4"></circle>
                        <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                        <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                    </svg>
                    员工管理
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
                        <line x1="18" y1="20" x2="18" y2="10"></line>
                        <line x1="12" y1="20" x2="12" y2="4"></line>
                        <line x1="6" y1="20" x2="6" y2="14"></line>
                    </svg>
                    Reports
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
                        <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
                        <polyline points="2 17 12 22 22 17"></polyline>
                        <polyline points="2 12 12 17 22 12"></polyline>
                    </svg>
                    Integrations
                </a>
            </li>
        </ul>

        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
            <span>Saved reports</span>
            <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
            </a>
        </h6>
        <ul class="nav flex-column mb-2">
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Current month
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Last quarter
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Social engagement
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Year-end sale
                </a>
            </li>
        </ul>
    </div>
</nav>
</body>
</html>

CRUD—新增

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<!--引入抽取的topbar-->
		<!--模板名:会使用thymeleaf的前后缀配置规则进行解析-->
		<div th:replace="commons/bar::topbar"></div>

		<div class="container-fluid">
			<div class="row">
				<!--引入侧边栏-->
				<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<!--需要区分是员工修改还是添加;-->
					<form th:action="@{/emp}" method="post">
						<!--发送put请求修改员工数据-->
						<!--
						1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
						2、页面创建一个post表单
						3、创建一个input项,name="_method";值就是我们指定的请求方式
						-->
						<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
						<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
						<div class="form-group">
							<label>LastName</label>
							<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
						</div>
						<div class="form-group">
							<label>Email</label>
							<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
						</div>
						<div class="form-group">
							<label>Gender</label><br/>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
								<label class="form-check-label">男</label>
							</div>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
								<label class="form-check-label">女</label>
							</div>
						</div>
						<div class="form-group">
							<label>department</label>
							<!--提交的是部门的id-->
							<select class="form-control" name="department.id">
								<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
							</select>
						</div>
						<div class="form-group">
							<label>Birth</label>
							<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
						</div>
						<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
					</form>
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
		<script>
			feather.replace()
		</script>

	</body>

</html>

Controller

//来到员工添加页面
    @GetMapping("/emp")
    public String toAddPage(Model model) {
        //来到添加页面,查询所有的部门,在页面显示
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);
        return "emp/add";
    }

    //员工添加
    //SpringMVC 自动将请求参数和入参对象的属性进行一一绑定;要求了请求参数的名字和 javaBean 入参的属性名是一致的。
    @PostMapping("/emp")
    public String addEmp(Employee employee) {

        //来到员工列表页面
        System.out.println("保存的员工信息:"+employee);
        //保存员工
        employeeDao.save(employee);
        //redirect : 表示重定向到一个地址   /代表当前项目路径
        //forward : 表示转发到一个地址
        return "redirect:/emps";
    }

提交的数据格式不对:生日:日期;

2017-12-12;2017/12/12;2019.08.05

日期格式化:SpringMVC 将页面提交的值需要转换为指定的类型;

2017-12-12 — Date; 类型转换,格式化。

5.7.CRUD—员工修改

修改页面

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<!--引入抽取的topbar-->
		<!--模板名:会使用thymeleaf的前后缀配置规则进行解析-->
		<div th:replace="commons/bar::topbar"></div>

		<div class="container-fluid">
			<div class="row">
				<!--引入侧边栏-->
				<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<!--需要区分是员工修改还是添加;-->
					<form th:action="@{/emp}" method="post">
						<!--发送put请求修改员工数据-->
						<!--
						1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
						2、页面创建一个post表单
						3、创建一个input项,name="_method";值就是我们指定的请求方式
						-->
						<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
						<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
						<div class="form-group">
							<label>LastName</label>
							<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
						</div>
						<div class="form-group">
							<label>Email</label>
							<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
						</div>
						<div class="form-group">
							<label>Gender</label><br/>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
								<label class="form-check-label">男</label>
							</div>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
								<label class="form-check-label">女</label>
							</div>
						</div>
						<div class="form-group">
							<label>department</label>
							<!--提交的是部门的id-->
							<select class="form-control" name="department.id">
								<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
							</select>
						</div>
						<div class="form-group">
							<label>Birth</label>
							<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
						</div>
						<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
					</form>
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
		<script>
			feather.replace()
		</script>

	</body>

</html>

Controller

 //来到修改页面,查出当前员工,在页面回显
    @GetMapping("/emp/{id}")
    public String toEditPage(@PathVariable("id") Integer id, Model model) {
        Employee employee = employeeDao.get(id);
        model.addAttribute("emp",employee);

        //页面显示所有的部门列表
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);
        //回到修改页面(add 是一个修改添加二合一页面);
        return "emp/add";
    }
 //员工修改
    @PutMapping("/emp")
    public String updateEmployee(Employee employee) {
        System.out.println("修改的员工数据:"+employee);
        employeeDao.save(employee);
        return "redirect:/emps";
    }

5.8. CRUD—员工删除

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link href="asserts/css/dashboard.css" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<!--引入抽取的 topbar -->
		<!--模板名:会使用 thymeleaf 的前后缀配置规则进行解析-->
		<!--<div th:insert="~{dashboard :: topbar}"></div>-->
		<div th:replace="commons/bar :: topbar"></div>
		<div class="container-fluid">
			<div class="row">
				<!--引入侧边栏-->
				<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2><a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加</a> </h2>
					<div class="table-responsive">
						<table class="table table-striped table-sm">
							<thead>
								<tr>
									<th>#</th>
									<th>lastName</th>
									<th>email</th>
									<th>gender</th>
									<th>department</th>
									<th>操作</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="emp:${emps}">
									<td th:text="${emp.id}"></td>
									<td th:text="${emp.lastName}"></td>
									<td th:text="${emp.email}"></td>
									<td th:text="${emp.gender} == 0 ? '男' : '女'"></td>
									<td th:text="${emp.department.departmentName}"></td>
									<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"></td>
									<td>
										<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
										<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
									</td>
								</tr>
							</tbody>
						</table>
					</div>
				</main>
				<form id="deleteForm"  method="post">
					<input type="hidden" name="_method" value="delete"/>

				</form>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>
		<script type="text/javascript">
			$(".deleteBtn").click(function () {
			    //删除当前员工的
			    $("#deleteForm").attr("action", $(this).attr("del_uri")).submit();
				return false;
            });
		</script>
	</body>

</html>

Controller

 //员工删除
    @DeleteMapping("/emp/{id}")
    public String deleteEmployee(@PathVariable("id") Integer id) {
        employeeDao.delete(id);
        return "redirect:/emps";
    }

6.错误处理机制

6.1.SpringBoot 默认的错误处理机制

默认效果:

  1. ​ 返回一个默认的错误页面

    [外链图片转存失败(img-7pbNe5XZ-1566610817814)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565022942409.png)]

    浏览器请求头:

    [外链图片转存失败(img-KBkVim5F-1566610817815)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565024106122.png)]

  2. 其他客户端,默认响应一个 json 数据

    [外链图片转存失败(img-Fxjx3ZIq-1566610817815)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565023108493.png)]

原理:

​ 可以参照 ErrorMvcAutoConfiguration 错误处理的自动配置;

​ 给容器中添加了以下组件:

​ 1.DefaultErrorAttributes:

帮我们在页面共享信息
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = this.getError(request);
        HttpStatus errorStatus = this.determineHttpStatus(error);
        errorAttributes.put("status", errorStatus.value());
        errorAttributes.put("error", errorStatus.getReasonPhrase());
        errorAttributes.put("message", this.determineMessage(error));
        this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
        return errorAttributes;
    }

​ 2.BasicErrorController: 处理默认 /error 请求

@Controller
@RequestMapping("$(server.error.path:${error.path/error})")
public class BasicErrorController extends AbstractErrorController{
  
   @RequestMapping(
        produces = {"text/html"}                 //产生 html 类型的数据
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping                           //产生 json 数据
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
}

​ 3.ErrorPageCustomizer:

@Value("${error.path:/error}")
private String path = "/error";   系统出现错误以后来到 error 请求进行处理;( web.xml 注册的错误页面规则 )

​ 4.DefaultErrorViewResolver:

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

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
      //默认 SpringBoot 可以去找到一个页面? error/404
        String errorViewName = "error/" + viewName;
      //模板引擎可以解析这个页面地址就用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

​ 步骤:

​ 一旦系统出现 4xx 或者 5xx之类的错误 : ErrorPageCustomizer 就会生效(定制错误的响应规则);就会来到 /error 请求;就会被 BasicErrorController 处理;

​ 1.响应页面;去哪个页面是由 DefaultErrorViewResolver

 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        Iterator var5 = this.errorViewResolvers.iterator();

        ModelAndView modelAndView;
        do {
            if (!var5.hasNext()) {
                return null;
            }

            ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
            modelAndView = resolver.resolveErrorView(request, status, model);
        } while(modelAndView == null);

        return modelAndView;
    }

​ 2.

如何定制错误响应:

​ 1.如何定制错误的页面;

​ 1.1.有模板引擎的情况下: error/状态码;【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error 的文件夹下】,发生此状态码的错误就会来到对应的页面。

​ 我们可以使用 4xx 和 5xx 作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)

​ 页面能够获取的信息:

​ timestamp : 时间戳

​ status : 状态码

​ error : 错误提示

​ exception : 异常对象

​ message : 异常消息

​ errors : JSR303数据校验的错误

​ 1.2.没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;

​ 1.3.以上都没有错误页面,就是默认来到 SpringBoot 的默认的错误提示页面。

​ 2.如何定制错误的 Json 数据;

@ControllerAdvice
public class MyExceptionHandler {

    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(HttpServletRequest request, Exception e) {

        Map<String, Object> map = new HashMap<>();
        //传入我们自己的错误状态码  4xx 5xx
        /**
         * Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",400);
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return "forward:/error";
    }
}

携带定制数据:

​ 出现错误以后,回来到 /errror 请求,会被 BasicErrorController 处理,响应出去可以获取的数据是由 getErrorAttributes 得到的(是 AbstractErrorController(ErrorController)规定的方法);

​ 1.完全编写一个 ErrorController 的实现类【或者是编写 AbstractErrorController 的子类】,放在容器中。

​ 2.页面上能用的数据,或者是 json 返回能用的数据都是通过 errorAttributes.getErrorAttributes 得到的。

​ 容器中 DefaultErrorAttributes.getErrorAttributes():默认进行数据处理的。i

自定义:

//给容器中加入自定义的 ErrorAtttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        map.put("lover","lanbao");
        return map;

    }

}

最终的效果:响应是自适应的,可以通过定制 ErrorAttributes 改变需要返回的内容。

//给容器中加入自定义的 ErrorAtttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    //返回值的 map 就是页面和 json 能够获取的 所有字段
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        map.put("lover","lanbao");

        //异常处理器携带的数据
        Map<String, Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;

    }

}

7.配置嵌入式 Servlet 容器

SpringBoot 默认使用Tomcat作为嵌入式的 Servlet 容器;

问题:

7.1.如何定制和修改 Servlet 容器的相关配置;

​ 7.1.1. 修改和 Server 有关的配置(ServerProperties.java)

server.port=8081
server.context-path=/crud

server.tomcat.uri-encoding=UTF-8

//通用的 servlet 容器设置
server.xxx
//Tomcat 的设置
server.tomcat.xxx

​ 7.1.2. 编写一个 EmbeddedServletContainerCustomizer: 嵌入式的 Servlet 容器的定制器;来修改 Servlet 的一些配置。

@Bean  //一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
    return new EmbeddedServletContainerCustomizer() {

        //定制嵌入式的Servlet容器相关的规则
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.setPort(8083);
        }
    };
}

7.2.注册 Servlet 三大组件【Servlet, Filter, Listener】

由于 SpringBoot 默认是以jar 包的方式启动嵌入式的 Servlet 容器来启动 SpringBoot 的 web 应用,没有 web.xml 文件。

所以需要注册三大组件:

7.2.1.ServletRegistrationBean

servlet 类:

package lan.springboot.crud.springbootwebrestfulcrud.servlet;

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

public class MyServlet extends HttpServlet {

    //处理 get 请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello MyServlet");
    }
}

注册 ServletRegistrationBean

@Configuration
public class MyServerConfig {

    //注册三大组件
    @Bean
    public ServletRegistrationBean myServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
        return servletRegistrationBean;
    }

}

7.2.2.FilterRegistrationBean

Filter 类:

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

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter process ....");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

注册FilterRegistrationBean:

 /**
     * 注册 filter
     */
    @Bean
    public FilterRegistrationBean myFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFilter());
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","myServlet"));
        return filterRegistrationBean;
    }

7.2.3.ServletListenerRegistrationBean

Listener 类:

public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized...web应用启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed...当前 web 项目销毁");
    }
}

注册:

 /**
     * listener
     */
    @Bean
    public ServletListenerRegistrationBean myListener() {
        ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<MyListener>(new MyListener());

        return servletListenerRegistrationBean;
    }

SpringBoot 帮我们自动配置 SpringMVC 的时候,自动的注册了 SpringMVC 的前端 DispatcherServlet.

2.SpringBoot 能不能支持其他的 servlet 容器。

7.3.替换为其他嵌入式 Servlet 容器

默认支持:

Tomcat(默认使用)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
 		引入 web 模块默认就是使用嵌入式的 tomcat      
</dependency>

Jetty

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除 tomcat-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入其他的 servlet 容器-->
        <dependency>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

Undertow

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除 tomcat-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入其他的 servlet 容器-->
        <dependency>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

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

EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置?

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
//导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
//后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
public class EmbeddedServletContainerAutoConfiguration {
    
    @Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}
    
    /**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
			return new JettyEmbeddedServletContainerFactory();
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
			return new UndertowEmbeddedServletContainerFactory();
		}

	}

1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)

public interface EmbeddedServletContainerFactory {

   //获取嵌入式的Servlet容器
   EmbeddedServletContainer getEmbeddedServletContainer(
         ServletContextInitializer... initializers);

}

[外链图片转存失败(img-86ohftcf-1566610817816)(E:/%E8%B5%84%E6%96%99/%E8%A7%86%E9%A2%91%E8%B5%84%E6%96%99/Java/%E5%B0%9A%E7%A1%85%E8%B0%B7%20SpringBoot/%E6%BA%90%E7%A0%81%E3%80%81%E8%B5%84%E6%96%99%E3%80%81%E8%AF%BE%E4%BB%B6/%E6%96%87%E6%A1%A3/Spring%20Boot%20%E7%AC%94%E8%AE%B0/images/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20180302144835.png)]

2)、EmbeddedServletContainer:(嵌入式的Servlet容器)

[外链图片转存失败(img-YEYCk7AU-1566610817816)(E:/%E8%B5%84%E6%96%99/%E8%A7%86%E9%A2%91%E8%B5%84%E6%96%99/Java/%E5%B0%9A%E7%A1%85%E8%B0%B7%20SpringBoot/%E6%BA%90%E7%A0%81%E3%80%81%E8%B5%84%E6%96%99%E3%80%81%E8%AF%BE%E4%BB%B6/%E6%96%87%E6%A1%A3/Spring%20Boot%20%E7%AC%94%E8%AE%B0/images/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20180302144910.png)]

3)、以TomcatEmbeddedServletContainerFactory为例

@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
      ServletContextInitializer... initializers) {
    //创建一个Tomcat
   Tomcat tomcat = new Tomcat();
    
    //配置Tomcat的基本环节
   File baseDir = (this.baseDirectory != null ? this.baseDirectory
         : createTempDir("tomcat"));
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   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);
    
    //将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器
   return getTomcatEmbeddedServletContainer(tomcat);
}

4)、我们对嵌入式容器的配置修改是怎么生效?

ServerProperties、EmbeddedServletContainerCustomizer

EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置?

怎么修改的原理?

5)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor

//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    //如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
   if (bean instanceof ConfigurableEmbeddedServletContainer) {
       //
      postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
   }
   return bean;
}

private void postProcessBeforeInitialization(
			ConfigurableEmbeddedServletContainer bean) {
    //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
    for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
        customizer.customize(bean);
    }
}

private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
    if (this.customizers == null) {
        // Look up does not include the parent context
        this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
            this.beanFactory
            //从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer
            //定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
            .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                            false, false)
            .values());
        Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
        this.customizers = Collections.unmodifiableList(this.customizers);
    }
    return this.customizers;
}

ServerProperties也是定制器

步骤:

1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】

2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;

只要是嵌入式的Servlet容器工厂,后置处理器就工作;

3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法

###5)、嵌入式Servlet容器启动原理;

什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;

获取嵌入式的Servlet容器工厂:

1)、SpringBoot应用启动运行run方法

2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext

3)、refresh(context);刷新刚才创建好的ioc容器;

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

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

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

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

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

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

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

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

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

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

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

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

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

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

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

4)、 onRefresh(); web的ioc容器重写了onRefresh方法

5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();

6)、获取嵌入式的Servlet容器工厂:

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

​ 从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());

8)、嵌入式的Servlet容器创建对象并启动Servlet容器;

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

IOC容器启动创建嵌入式的Servlet容器

7.5.嵌入式 Servlet 容器启动原理

什么时候创建嵌入式的 Servlet 容器工厂?什么时候获取嵌入式的 Servlet 容器并启动 Tomcat;

获取嵌入式的 Servlet 容器工厂:

1.SpringBoot 应用启动运行 run 方法;

2.refreshContext(context);SpringBoot 刷新 IOC 容器【创建 IOC 容器对象,并初始化容器,创建容器中的们每一个组件】;如果是 web 应用创建

AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext;

3.refresh(context);刷新刚才创建好的 IOC 容器;

  1. onRefresh(); web的 ioc 容器重写了 onRefresh 方法。

  2. webioc 容器会创建嵌入式的 Servlet:createEmbeddedServletContainer();

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

    EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

    从 Ioc 容器中获取 EmbeddedServletContainerFactory 组件;

    TomcatEmbeddedServletContainerFactory 创建对象,后置处理器一个是这个对象,就获取所有的定制器来先定制 Servlet 容器的相关配置;

  4. 使用容器工厂获取嵌入式的Servlet 容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());

  5. 嵌入式的 Servlet 容器创建对象并启动 Servlet 容器;

    先启动嵌入式的 Servlet 容器,再将 ioc 容器中剩余没有创建出的对象获取出来。

    IOC 容器启动创建嵌入式的 Servlet 容器。

7.6.使用外置的 Servlet 容器

嵌入式 Servlet 容器:应用打成可执行的 jar.

​ 优点:简单、便携;

​ 缺点:默认不支持 JSP、优化定制比较复杂(使用定制器【Serverproperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式 Servlet 容器的创建工厂

​ 【EmbeddedServletContainerFactory】);

外置的 Servlet 容器:外面安装 Tomcat — 应用 war 包的方式打包。

步骤:

​ 1.必须创建一个 war 项目;

​ 2.将嵌入式的 Tomcat 指定为 provided;

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

​ 3.必须编写一个 SpringBootServletInitializer 的子类,并调用 configure 方法

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
      //传入 SpringBoot 应用的主程序
        return application.sources(SpringBootWebJspApplication.class);
    }

}

​ 4.启动服务器就可以使用;

7.7.外部Servlet 容器启动 SpringBoot 应用原理

jar 包: 执行 SpringBoot 主类和 main 方法,启动 IOC 容器,创建嵌入式的 Servlet 容器;

war 包:

​ 启动服务器,服务器启动 SpringBoot 应用【SpringBootServletInitializer】,启动 IOC 容器;

Servlet3.0(Spring 注解版):

规则:

​ 1.服务器启动(web 应用启动)会创建当前 web 应用里面每一个 Jar 包里面 ServletContainerInitializer 的实例。

​ 2.ServletContainerInitializer 的实现放在 jar 包的 META-INF/services 文件夹下,有一个名为 javax.servlet.ServletContainerInitializer 的文件,内容就是

​ ServletContainerInitializer 实现类的全类名。

​ 3.还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:

​ 1.启动 tomcat

  1. org\springframework\spring-web\4.xxx.RELEASE\spring-web-4.xx.RELEASE.jar\META-INF\services\javax.servlet.ServletContainerInitializer :

    Spring 的 web 模块里面有这个文件: org.springframework.web.SpringServletContainerInitializer.

  2. SpringServletContainerInitializer 将 @HandlesTypes(WebApplicationInitializer.class) 标注的所有这个类型的类都传入到 onStartup 方法的 Set<Class<?>>;

    为这些 WebApplicationInitializer 类型的类创建实例。

  3. 每一个 WebAppicationInitializer 都调用自己的 onStartup 方法。

  4. 相当于我们的 SpringBootServletInitializer 的类会被创建对象,并执行 onStartup 方法。

  5. SpringBootServletInitializer 实例执行 onStartup 方法会 createRootApplicationContext;创建容器;

    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)});
            SpringApplication application = builder.build();
            if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
                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));
            }
    
            return this.run(application);
        }
    
  6. Spring 的应用启动并且创建 IOC 容器。

  7. 启动 Servlet 容器,再启动 SpringBoot 应用。

五.Spring Boot 与 Docker

1.Docker

Docker 是一个开源的应用容器引擎。

Docker 支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像。运行中的这个镜像称为容器。

2.Docker 核心概念

docker 镜像:Docker 镜像是用于创建 Docker 容器的模板。软件打包好的镜像;放在 docker 仓库中

docker 容器:容器是独立运行的一个或一组应用。

docker 客户端:客户端通过命令行或者其他工具使用 Docker. 连接 Docker 主机进行操作的。

docker 主机:一个物理或者虚拟的及其用于执行 Docker 守护进程和容器。安装了 Docker 程序的机器(直接安装在操作系统之上的。)

docker 仓库:Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。 Docker Hub提供了庞大的镜像集合供使用。用来保存各种打包好的软件镜像

使用 docker 的步骤:

1.安装 Docker

2.去 docker 仓库找到这个软件对应的镜像。

3.使用 docker 运行这个镜像,这个镜像就会生成一个 docker 容器。

4.对容器的启动停止就是对软件的启动停止。

3.安装 Docker

3.1.安装 linux 虚拟机

​ 可供选择的方式:

​ 1.Vmware

2.VirtualBox(oracle 提供的虚拟机)

3.1.1.安装 VirtualBox :

​ 小巧,免费。

3.1.2.导入虚拟电脑

​ [外链图片转存失败(img-B8n7cALq-1566610817817)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565200208098.png)]

3.1.3.双击启动

​ 用户: root, 密码:123456

3.1.4.使用客户端连接服务器

​ SmarTTy

3.1.5.设置虚拟机网路

​ [外链图片转存失败(img-AVVvpYuA-1566610817818)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565201301743.png)]

3.1.6.重启网络服务

service network restart

3.1.7.查看 Ip

ip addr

3.8.使用客户端连接虚拟机

3.2.在 Linux 虚拟机上安装 docker

3.2.1.查看 Centos 版本

​ docker 要求 Centos系统的内核版本高于 3.10.

uname -r

3.2.2.升级软件包及内核

yum update

3.2.3. 安装 docker

yum install docker

3.2.4.启动 docker

systemctl start docker

3.2.5.将 docker 服务设为开机启动

systemctl enable docker

3.2.6.停止 docker

systemctl stop docker 

3.3.常用操作

3.3.1.镜像操作

操作 命令 说明
检索 docker search 关键字 eg:docker search redis 经常去 docker hub 上检索镜像的详细信息,如镜像的 TAG.
拉取 docker pull 镜像名 : tag :tag 是可选的,tag 表示标签,多为软件的版本,默认是 latest.
列表 docker images 查看所有本地镜像
删除 docker rmi image-id 删除指定的本地镜像

https://hub.docker.com/

例:

检索:

docker search mysql  

拉取(下载):

docker pull mysql

列表

docker images

删除

docker rmi image-id

3.3.2.容器操作

软件镜像(QQ安装程序) —— 运行镜像 —— 产生一个容器(正在运行的软件,运行的QQQ)

操作 命令 说明
运行 docker run --name container-name -d image-name eg:docker run --name myredis --d redis –name:自定义荣容器名 -d:后台运行 image-name :指定镜像模板
列表 docker ps(查看运行中的容器); 加上-a; 可以查看所有容器
停止 docker stop container-name/container-id 停止当前你运行的容器
启动 docker stop container-name/container-id 启动容器
删除 docker rm container-id 删除指定容器
端口映射 -p 6379:6379 eg:docker run -d -p 6379:6379 --name myredis docker.io/redis -p : 主机端口(映射到)容器内部的端口
容器日志 docker logs container-name/coontainer-id
更多命令 https://docs.docker.com/engine/reference/commandline/docker/

步骤:

步骤:
1.搜索镜像
	docker search tomcat
2.拉取镜像
	docker pull tomcat
3.根据镜像启动容器
	docker run --name mytomcat -d tomcat(镜像名tag);
4.查看运行中的容器(tomcat)
	docker ps 
5.停止运行中的容器
	docker stop 容器 id
6.查看所有容器
	docker ps -a
7.启动容器
	docker start 容器 id
8.删除一个容器(容器需要是停止状态)
	docker rm 容器 id
9.启动一个做了端口映射的 tomcat
	docker run -d -p 8888:8080 tomcat	
	-d : 代表后台运行
	-p : 将主机端口映射到容器的一个端口    主机端口:容器内部的端口
10.

4.环境搭建

4.1.安装 mysql

步骤:
1.搜索镜像
	docker search mysql
2.拉取下载镜像
	docker pull mysql
3.查看镜像
	docker images
4.启动(端口映射未做)
	docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
4.启动(端口做了映射)
	docker run -p 3306:3306 --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d msyql

高级操作

1.将主机的 /my/custom 文件夹挂载到 mysqldocker 容器的 /etc/msyql/conf.d 文件上。
   docker run --name msyql03 -v /my/custom:/etc/mysql/conf.d -e MSYQL_ROOT_PASSWORD=my-secret-pw-d msyql:tag

4.2.安装 redis

4.3.安装 rabbitmq

4.4.安装 elasticsearch

六. Spring Boot 与数据访问

1.简介

​ 对于数据访问层,无论是 SQL 还是 NOSQL, SpringBoot 默认采用 Spring Data 的方式进行统一处理,添加大量自动配置,屏蔽了很多设置。引入各种 xxxTemplate,xxxRepository 来简化我们对数据访问层的操作。对我们而言,只需要进行简单的设置即可。

2.整合基本 JDBC 与数据源

1.引入 starter
		- spring-boot-starter-jdbc
2.配置 application.yml
3.测试
4.高级配置:使用 druid 数据源
	- 引入 druid
	- 配置属性
5.配置 druid 数据源监控
<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

application.yml

配置数据源

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/jdbc
    driver-class-name: com.mysql.jdbc.Driver

效果:

​ 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

​ 数据源的相关配置都在 DataSourceProperties 里面。

自动配置原理:

​ org.springframework.boot.autoconfigure.jdbc:

​ 1.参考 DataSourceConfiguration ,根据配置创建数据源,默认使用 Tomcat 连接池;可以使用 spring.datasource.type 指定自定义的数据源类型;

​ 2.SpringBoot 可以默认支持;

org.apache.tomcat.jdbc.pool.DataSource、HikarDataSource、BasicDataSource

​ 3.自定义数据源类型

 @Configuration
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }

        @Bean
        public DataSource dataSource(DataSourceProperties properties) {
          //使用 DataSourceBuilder 创建数据源,利用反射创建响应 type 的数据源,并且绑定相关属性
            return properties.initializeDataSourceBuilder().build();
        }
    }

​ 4.DataSourceInitializer: ApplicationListener;

​ 作用:

  • runSchemaScripts() : 运行建表语句;

  • runDataScripts() : 运行插入数据的 sql 语句;

    默认只需要将文件命名为:

    schema-*.sql、data-*.sql
    默认规则: schema.sql,  schema-all.sql
    
    可以在 application.yml 中配置:
    schema:
    	- classpath:department.sql
    	指定位置
    

**注意点:SpringBoot 生成创建表语句 ,需要在配置文件中配置 initialization-mode: always**,还需要修改数据驱动,url:

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always
    schema:
        - classpath:department.sql

5.操作数据源:自动配置了 JdbcTemplate 来操作数据库。

案例:

配置文件:

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always
    schema:
        - classpath:department.sql

sql : 建表语句

/*
Navicat MySQL Data Transfer

Source Server         : 本地
Source Server Version : 50528
Source Host           : 127.0.0.1:3306
Source Database       : restful_crud

Target Server Type    : MYSQL
Target Server Version : 50528
File Encoding         : 65001

Date: 2018-03-05 10:41:40
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `departmentName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


controller 层:jdbctemplate操作

@Controller
public class HelloController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @ResponseBody
    @GetMapping("/query")
    public Map<String,Object> map() {
        List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from department");
        return list.get(0);
    }
}

6.整合 Druid 数据源(配置指定数据源)

6.1.导包

<!--引入 druid 数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.18</version>
        </dependency>

6.2.在配置文件中配置

type: com.alibaba.druid.pool.DruidDataSource          #指定数据源类型



initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
 #   filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

完整配置文件

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/localmysql?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource          #指定数据源类型
    initialization-mode: always
  #  schema:
   #     - classpath:department.sql

    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
 #   filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

6.3.编写配置数据源类

@Configuration
public class DruidConfig {
    //配置数据源属性 : 从 application.yml 中获取
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid() {
        return new DruidDataSource();
    }
    //配置 Druid 的监控
    //1.配置一个管理后台的 Servlet
    @Bean
    public ServletRegistrationBean statViwServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String,String> initParams = new HashMap<String, String>();
        initParams.put("loginUsername","admin");
        initParams.put("loginPassword","123456");
        initParams.put("allow","localhost");
        initParams.put("deny","192.168.15.21");
        servletRegistrationBean.setInitParameters(initParams);
        return servletRegistrationBean;
    }
    //2.配置一个监控的 filter
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String,String> initParams = new HashMap<String, String>();
        initParams.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

3.整合 MyBatis

1.导包

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

2.配置 application.yml基本配置

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource          #指定数据源类型
    initialization-mode: always
  #  schema:
   #     - classpath:department.sql

    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
 #   filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

3.编写数据源配置类

@Configuration
public class DruidConfig {
    //配置数据源属性 : 从 application.yml 中获取
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid() {
        return new DruidDataSource();
    }
    //配置 Druid 的监控
    //1.配置一个管理后台的 Servlet
    @Bean
    public ServletRegistrationBean statViwServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String,String> initParams = new HashMap<String, String>();
        initParams.put("loginUsername","admin");
        initParams.put("loginPassword","123456");
        initParams.put("allow","localhost");
        initParams.put("deny","192.168.15.21");
        servletRegistrationBean.setInitParameters(initParams);
        return servletRegistrationBean;
    }
    //2.配置一个监控的 filter
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String,String> initParams = new HashMap<String, String>();
        initParams.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

4.给数据库建表

schema:
      - classpath:sql/department.sql
      - classpath:sql/employee.sql
      
创建之后,注释掉 schema;防止重新启动之后重新建表,数据丢失。

5.创建 JavaBean

6.MyBatis注解版

/**
 * 指定这是一个操作数据库的 mapper
 */
@Mapper
public interface DepartmentMapper {

    @Select("select * from department where id=#{id}")
    public Department getDeptById(Integer id);

    @Delete("delete from department where id=#{id}")
    public int delDeptById(Integer id);

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into department(departmentName) values(#{departmentName})")
    public int insertDept(Department department);

    @Update("update department set department={department} where id=#{id}")
    public int updateDept(Department department);
}

可以采用自定义的mybatis 的配置规则:给容器添加一个 ConfigurationCustomizer ;

@Configuration
public class MyBatisConfig {

  @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

批量扫描 Mapper接口

//使用 MapperScan 批量扫描所有的 Mapper 接口
@MapperScan(value="lan.spring.boot.mybatis.springbootmybatis.Mapper")
@SpringBootApplication
public class SpringBootMybatisApplication {

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

}

7.配置版 MyBatis

配置文件:

mybatis:
		config-location : classpath:mybatis/mybatis-config.xml  指定全局配置文件的位置
		mapper-locations: classpath:mybatis/mapper/*.xml     指定 sql 配置文件的位置

4.整合 JPA

4.1. Spring Data

简介:
	Spring Data 项目的目的是为了简化构建基于 Spring 框架引用的数据访问技术,包括非关系数据库、Map-Reduce 框架、云数据服务等等,另外也包含对关系数据库的访问支持。
特点:
	Spring Data 为我们提供使用同一的 API 来对数据访问层进行操作;这主要是 Spring Data Commons 项目来实现的。 Spring Data Commons 让我们在使用关系型或者非关系型数据访问技术时都基于 Spring 提供的统一标准,标准包含了 CRUD(创建、获取、更新、删除)、查询、排序和分页的相关操作。
统一的 Repository 接口
	Repository<T, ID extends Serializable> : 统一接口
	RevisionRepository<T, ID extends Serializable, N extends Number & Comparable<N>>:基于乐观锁机制。
	CrudRepository<T, ID extends Serializable> :  基本 CRUD 操作。
	PagingAndSortingRepository<T, ID extends Serializable> : 基本 CRUD 及分页。
提供数据访问模板类:xxxTemplate:    如 : MongoTemplate、RedisTemplate 等。
JPA 与 Spring Data:
	1.JpaRepository 基本功能:
		编写接口继承 JpaRepository 既有 crud 及分页等基本功能
	2.定义符合规范的方法命名
		在接口中只需要声明符合规范的方法,即拥有对应的功能
	3.@Query 自定义查询,定制查询 SQL
	4.Specifications 查询(Spring Data JPA 支持 JPA2.0 的 Criteria 查询)

[外链图片转存失败(img-N0RUOP4H-1566610817819)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565405527869.png)]

4.2.整合 JPA

步骤:
	1.引入 spring-boot-starter-data-jpa
	2.配置文件打印 SQL 语句
	3.创建 Entity 标注 JPA 注解
	4.创建 Repository 接口继承 JpaRepostory
	5.测试

JAP : ORM (Object Relational Mapping) :

1.编写一个实体类(bean) 和数据库进行映射,并且配置好映射关系;

//使用 JPA 注解配置映射关系
@Entity   //告诉 JPA 这是一个实体类(和数据表映射的类)
@Table(name="tal_user")   //@Table 来指定与那张数据表对应;如果省略默认表名就是 user
public class User {
    @Id        //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)      //自增主键
    private Integer id;

    @Column(name = "last_name", length = 50)        //这是和数据表对应的一个列
    private String lastName;

    @Column(name = "email", length = 20)
    private String email;

2.编写 dao 接口来操作实体类对应的数据表(Repository)

//继承 JpaRepository 来完成对数据库的操作
public interface UserRepository  extends JpaRepository<User, Serializable> {


}

3.基本配置 (JpaProperties)

application.yml

	jpa:
 		 hibernate:
#  	更新或者创建数据表结构
   		 ddl-auto: update
# 显示 sql
  	show-sql: true

完整 application.yml(注意 jpa 放置位置)

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/jpa?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always


  jpa:
    hibernate:
#     更新或者创建数据表结构
      ddl-auto: update
#    控制台显示SQL
    show-sql: true

controller

@RestController
public class UserController {

    @Autowired
    UserRepository userRepository;

    @GetMapping("/user")
    public User insertUser(User user) {
        User user1 = userRepository.save(user);
        return user1;
    }
}

七. Spring Boot 启动配置原理

​ 启动原理、运行流程、自动配置原理

重要的事件回调机制:

配置在 META-INF/spring.factories
	ApplicationContextInitializer
	SpringApplicationRunListener
	
只需要放在 IOC 容器中
	ApplicationRunner
	CommandLineRunner

7.1.启动流程:

步骤:

7.1.1.创建 SpringApplication 对象

1创建 SpringApplication 对象:
this((ResourceLoader)null, primarySources);

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
  			//保存主配置类
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
  			//判断是否是 web 应用
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
  			//从类路径下找到 META-INF/spring.factories 配置的所有 ApplicationContextInitalizer;然后保存起来。
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
  			//从类路径下找到 META-INF/spring.factories 配置的所有 ApplicationListener 
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
  			//从多个配置类中找到有 main 方法的主配置类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }


7.1.2.运行 run 方法

2.运行 run 方法
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
  		
  			//获取SpringApplicationRunListeners;从类路径下 META-INF/spring.factories 中获取的。
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
  			//回调所有的获取 SpringApplicationRunListener.starting() 方法。
        listeners.starting();

        Collection exceptionReporters;
        try {
          	//封装命令行参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
          	//准备环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
          	//创建环境完成后,回调SpringApplicationRunListener.environmentPrepared() 方法;表示环境准备完成
          
          	//打印 banner
            Banner printedBanner = this.printBanner(environment);
          
          	//创建 ApplicationContext ;决定创建 web 的 ioc 还是普通的 ioc
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
          
          	//准备上下文环境;将 environment 保存到 ioc 中;而且 applyInitializers();
          	//applyInitializers() : 回调之前保存的所有的 ApplicationContextInitializer 的方法。
          	//回调所有的 SpringApplicationRunListener 的 contextPrepared() 方法。
          	
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
          	//prepareContext 运行完成以后回调所有的 SpringApplicationRunListener的contextLoaded() 方法。
          	//刷新容器:ioc 容器初始化;
          	//扫描,创建,加载所有组件的地方
            this.refreshContext(context);
          	//从 ioc 容器中获取所有的 ApplicationRunner 和 CommanLineRunner 进行回调
          	//ApplicationRunner 先回调, CommandLineRunner 再回调。
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
						//所有的 SpringApplicationRunnerListener 回调 started() 方法。
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
          	//整个 SpringBoot 应用启动完成以后返回启动的 IOC 容器。
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

7.3.事件监听机制

配置在 META-INF/spring.factories (需要配置)
		ApplicationContextInitializer
		SpringApplicationRunListener
		
只需要放在 IOC 容器中 (容器中的需要添加注解 @Component)
		ApplicationRunner
		CommandLineRunner

八. Spring Boot 自定义 starters

1.starter(场景启动器) 原理

1.场景依赖

2.编写自动配置

@Configuration             //指定这个类是一个配置类
@ConditionalOnXxx                   //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter       //指定自动配置类的顺序
@Bean     //给容器中添加组件

@ConfigurationProperties    //结合相关的xxxProperties 类绑定相关的配置
@EnableConfigurationProperties    //让 xxxProperties 生效并且加入到容器中

自动配置类要能加载
		将标注 @Configuration 的自动配置类,放在 classpath 下 META-INF/spring.factories 中。

3.模式:

2.自定义 starter

2.1.自动装配 Bean

​ 自动装配使用配置类(@Configuration) 结合 Spring4 提供的条件判断注解 @Conditional 及 Spring Boot 的派生注解如 @ConditionOnClass 完成;

2.2.配置自动装配 Bean

​ 将标注 @Configuration 的自动配置类,放在 classpath 下 META-INF/spring.factories 中。

2.3. 启动器

启动器模块是一个空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用户自动装配或者其他类库。

命名规范:

​ 官方命名空间:

前缀: spring-boot-starter-

模式: spring-boot-starter-模块名

举例: spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc

​ 自定义命名空间

后缀:  -spring-boot-starter
模式:  模块-spring-boot-starter
举例:  mybatis-spring-boot-starter

SpringBoot 高级

九.Spring Boot 与缓存

1.JSR-107

Java Caching 定义了5个核心接口,分别是 CachingProvider,CacheManager,Cache,Entry 和 Expiry.
CachingProvider 定义了创建、配置、获取、管理和控制多个 CacheManager.一个应用可以在运行期间访问多个 CachingProvider.
CacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache,这些 Cache 存在于  CacheManager 的上下文中。一个 CacheManager 仅被一个 CachingProvider 所拥有。
Cache 是一个类似 Map 的数据结构并临时存储以 key 为索引的值。一个 Cache 仅被一个 CacheManager 所拥有的。
Entry 是一个存储在 Cache 中的 key-value 对。
Expiry 每一个存储在 Cache 中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过 ExpiryPolicy 设置。

[外链图片转存失败(img-cej0HNRA-1566610817819)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565793079616.png)]

使用 JSR107 需要导入依赖:
<dependency>
	<groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
</dependency>

2.Spring 缓存抽象

Spring 从3.1 开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来同一不同的缓存技术;
并支持使用 JCache(JSR-107) 注解来简化我们开发;
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合。
Cache 接口下 Spring 提供了各种 xxxCache 的实现;如 RedisCache, EhCacheCache , ConcurrentMapCache 等
每次调用需要和缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。
下次调用直接从缓存中获取。
使用 Spring 缓存抽象时我们需要关注以下两点:
1.确定方法需要被缓存以及他们的缓存策略
2.从缓存中读取之前缓存存储的数据。

几个重要概念&缓存注解

Cache 缓存接口,定义缓存操作。实现有:RedisCache,EhCacheCache,ConcurrentMapCache 等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时 key 生成策略
serialize 缓存数据时 value 序列化策略
整合缓存:
	步骤:
		1.开启基于注解的缓存    @EnableCaching
					@MapperScan("com.spring.boot.cache.mapper")
					@SpringBootApplication
					@EnableCaching              //开启基于注解的缓存
					public class SpringbootCacheApplication {
		2.标注缓存注解即可。



CacheManager 管理多个 Cache 组件的,对缓存的真正 CRUD 操作在 Cache 组件中,每一个缓存组件有自己唯一一个名字;  

@Cacheable : 
	属性:
		cacheNames/value : 指定缓存的名字;将方法的犯规结果放在哪个缓存中,是数组的方式,可以指定多个缓存。
		key: 缓存数据使用的 key.可以用它来指定。默认是使用方法的参数。 可以编写 SpEL. #id,参数 id 的值  #a0 #p0 #root.args[0]
		keyGenerator : key 的生成器;可以自己指定  key 的生成器的组件 id  
						key/keyGenerator : 二选一使用、
		cacheManager : 指定缓存管理器;或者指定 cacheResolver 缓存解析器。
		condition : 指定符合条件的情况下才缓存;
		unless: 否定缓存;当 unless 指定的条件为 true,方法的返回值就不会被缓存。可以获取到结果进行判断。 unless=" #result == null "
		sync :  是否使用异步
		 @Cacheable(cacheNames = {"emp"})
    public Employee getEmp(Integer id) {
    

@CachePut : 既调用方法,又更新缓存数据;修改了数据库的某个数据,同时更新缓存;
			运行时机:
					1.先调用目标方法
					2.将目标方法的结果缓存起来
					
			结果可用 result 取出。
			
			
@CacheEvict :缓存清除
		key : 指定要清除的数据
		allEntries = true : 表示清除缓存中的所有数据
		beforeInvocation : 缓存的清除是否在方法之前执行(默认值为 false,表示在方法之后执行);如果出现异常缓存就不会清除
		
		beforeInvocation = true : 代表清除缓存操作是在方法运行之前执,无论方法是否出现异常,缓存都会清除。
		
		
		
@Caching    &   @CacheConfig

@Caching 定义复杂的缓存规则

@Caching(
	cacheable = {
		@Cacheable(value="emp",key="#lastName")
	},
	put = {
		@CachePut(value="emp",key = "#result.id"),
		@CachePut(value="emp",key= "#result.email")
	}
)

@CacheConfig : 抽取缓存的公共配置
		注解在类上。

[外链图片转存失败(img-3aZsJ4k6-1566610817820)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565796202918.png)]

#开启驼峰命名匹配规则
mybatis:
  configuration:
    map-underscore-to-camel-case: true
#打印日志
logging:
  level:
    com.spring.boot.cache.mapper: debug

缓存原理

缓存原理:
	1.自动配置类:  CacheAutoConfiguration
	2.缓存的配置类:
			org.springframework.boot.autoconfigure.cache.GenericCacheConfigutation
			org.springframework.boot.autoconfigure.cache.JCacheCacheConfigutation
			org.springframework.boot.autoconfigure.cache.EhCacheCacheConfigutation
			org.springframework.boot.autoconfigure.cache.HazelcastCacheConfigutation
			org.springframework.boot.autoconfigure.cache.InfinispanCacheConfigutation
			org.springframework.boot.autoconfigure.cache.RedisCacheConfigutation
			org.springframework.boot.autoconfigure.cache.CaffeineCacheConfigutation
			org.springframework.boot.autoconfigure.cache.GuavaCacheConfigutation
			org.springframework.boot.autoconfigure.cache.SimpleCacheConfigutation
			org.springframework.boot.autoconfigure.cache.NoOpCacheConfigutation
	3. 哪个配置类默认生效 : SimpleCacheConfigutation
	
	4. SimpleCacheConfiguration 给容器中注册了一个 CacheManager:ConcurrentMapCacheManager
	5. 可以获取和创建 ConcurrentMapCache 类型的缓存组件;它的作用是将数据保存在 ConcurrentMap 中;
  
	运行流程:
		@Cacheable :
			1.方法运行之前,先去查询 Cache(查询组件),按照 cacheNames 指定的名字获取;(CacheManager 先获取相应的缓存),第一次获取缓存会自动创建,如果没有Cache 组件,会先自
				动创建。
			2.去 Cache 中查找缓存的内存,使用一个 key,默认就是方法的参数。
					key 是按照某种策略生成的;默认是使用 keyGenerator 生成的,默认使用 SimpleKeyGenerator 生成 key。
							SimpleKeyGenerator 生成 key 的默认策略:
									如果没有参数:key = new SimpleKey();
									如果有一个参数: key = 参数的值
                  如果有多个参数: key = new SimpleKey(params);
			3.没有查找缓存,就调用目标方法;
			4.将目标方法返回的结果,就放到缓存中。
      
    @Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为 key 去查询缓存,如果没有就运行方法并将结果放入缓存;
    以后再来调用就可以直接使用缓存中的数据;
    
    核心:
    	1.使用 CacheManager【ConcurrentMapCacheManager】 按照名字得到 Cache 【ConcurrentMapCache】 组件
    	2.key 使用 keyGenerator 生成的,默认是 SimpleKeyGenerator 
#项目启动时打开配置报告
debug:
  true
默认使用的缓存是 ConcurrentMapCacheManager == ConcurrentMapCache 组件 : 将数据保存在 ConcurrentMap<Object, Object> 中的。

3.整合 Redis

开发中使用的缓存中间件:redis, memcached, ehcache.
1.安装 Redis.使用 docker.  Linux 环境中安装 redis , docker pull registry.docker-cn.com/libray/redis
2.引入 redis 的 starter.
<denpendency>
	<groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</denpendency>
3.配置redis
spring.redis.host=主机地址ip
Redis 常见的五大数据类型:
	String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
SpringBoot RedisTemplate 操作 Redis:
	stringRedisTemplate.opsForValue()[String(字符串)]/redisTemplate
	stringRedisTemplate.opsForList()[List(列表)]/redisTemplate
	stringRedisTemplate.opsForSet()[Set(集合)]/redisTemplate
	stringRedisTemplate.opsForHash()[Set(散列)]/redisTemplate
	stringRedisTemplate.opsForSet()[Set(有序集合)]/redisTemplate
4.测试缓存
缓存原理:
	CacheManager === Cache 缓存组件来实际给缓存中存取数据
	1.引入 redis 的 starter ,容器中保存的是 RedisCacheManager
	2.RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件; RedisCache 通过操作 redis 缓存数据。
	3.默认保存数据 k-v 都是 Object; 利用序列化保存;如何保存为 json.
			1.引入了 redis 的 starter, cacheManager 变为 RedisCacheManager;
			2.默认创建的 RedisCacheManager 操作 Redis 的时候,使用的 RedisTemplate<Object,Object>.
			3.RedisTemplate<Object,Object> 是默认使用 jdk 的序列化机制。
	4.自定义 CacheManager;
自定义 CacheManager:

[外链图片转存失败(img-IKhuxLjy-1566610817820)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565884008014.png)]

[外链图片转存失败(img-qsCeAIBZ-1566610817821)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565884734941.png)]

将某个缓存器作为默认的:  @Primary

十.Spring Boot 与消息

1.概述

1.大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力
2.消息服务中两个重要概念:
	消息代理(message broker) 和目的地(destination)
	当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。
3.消息队列主要有两种形式的目的地
	1.队列(queue) : 点对点消息通信(point-to-point)
	2.主题(topic) : 发布(publish)/订阅(subsrcibe) 消息通信
4.点对点式:
	1.消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
	2.消息只有唯一的发送者和接受者,但并不是说只能有一个接收者。
			(唯一的接受者并不代表只有一个接收者,接收者可以是多个,但是只有一个接收者可以收到消息,一旦有人收到消息,消息就会销毁)
5.发布订阅式:
	1.发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达的同时接收到信息。
6.JMS(Java Message Service) : JAVA 消息服务
	基于 JVM 消息代理的规范。ActiveMQ、HornetMQ 是 JMS 实现。
7.AMQP (Advanced Message Queuing Protocol)
	1.高级消息队列协议,也是一个消息代理的规范,兼容 JMS
	2.RabbitMQ 是 AMQP 的实现
8.Spring 支持
	1.spring-jms 提供了对 JMS 的支持
	2.spring-rabbit 提供了对 AMQP 的支持
	3.需要 ConnectionFactory 的实现来连接消息代理
	4.提供 JmsTemplate、RabbitTemplate 来发送消息
	5.@JmsListener(JMS)、@RabbitListener(AMQP) 注解在方法上监听消息代理发布的消息
	6.@EnableJms、@EnableRabbit 开启支持
9.Spring boot 自动配置
	1.JmsAutoConfiguration
	2.RabbitAutoConfiguration

[外链图片转存失败(img-xIYf3XdU-1566610817821)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565885175101.png)]

[外链图片转存失败(img-AiS8YxtA-1566610817822)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565885253543.png)]

[外链图片转存失败(img-216tLM20-1566610817822)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565885267564.png)]

[外链图片转存失败(img-ujwL03CN-1566610817823)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565886210549.png)]

2.RabbitMQ 简介

RabbitMQ 是一个由 erlang 开发的AMQP(Advanced Message Queue Protocol)的开源实现。
核心概念:
	Message :
		消息,消息是不具名的,它有消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先
    权)、delivery-mode(指出该消息可能需要持久性存储)等。
   
  Publisher
  	消息的生产者,也是一个向交换器发布消息的客户端应用程序。
  
  Exchange
  	交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
  	Exchange 有4种类型: direct(默认)【点对点】,fanout【消息订阅】, topic【消息订阅】, 和 headers【消息订阅】, 不同类型的 exchange 转发消息的策略有所不同。
  	
  Queue
  	消息队列,用于保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在 队列里面,等待消费者连接到这个队列将其取走。
  	
  Binding
  	绑定,用于消息队列和交换器之间的关联。一个绑定就是基于理由键将交换器和消息队列连接起来的路有规则,所以可以将交换器理解成一个由绑定构成的路由表。
  	Exchange 和 Queue 的绑定可以是多对多的关系。
  	
  Connection 
  	网络连接
  	
  Channel 
  	信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的 TCP 连接内的虚拟连接, AMQP 命令都是通过信道发出去的,
  	不管是发布消息、订阅队列还是接受消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所
  	以引入了信道的概念,以复用一条 TCP 连接。
  	
  Consumer
  	消息的消费者,表示一个从消息队列中取得消息的客户端应用程序
  	
  Virtual Host 
  	虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上是一个 mini 版的
  	RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /.
  	
  Broker 
  	表示消息队列服务器实体。

[外链图片转存失败(img-R0hhobNK-1566610817823)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565887730719.png)]

3.RabbitMQ 运行机制

AMQP 中的消息路由
	AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,
	消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到哪个队列。

[外链图片转存失败(img-ZYNoSTRx-1566610817824)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565888209787.png)]

Exchange 类型:
	
	Exchange 分发消息时根据类型的不同分发策略有区别,目前共有四种类型:direct、fanout、topic、headers。headers 匹配 AMQP 消息的 header 而不是路由键,
	headers 交换器和 direct 交换器完全一致,但性能差很多,几乎用不到了。
	

direct : 

	消息中的路由键如果和 Binding 中的 binding key 一致,交换器就将消息发送到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换器
	要求路由键为 "dog",则只转发 routing key 标记为 "dog"的消息,不会转发"dog.puppy",也不会转发"dog.guard" 等等。它是完全匹配、单播的模式。

[外链图片转存失败(img-M1HCi0Q5-1566610817824)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565888673808.png)]

fanout:

	每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到
	与该交换器绑定的所有队列上。像子网广播,每台子网内的主机都获得了一份复制的消息。 fanout 类型转发消息时最快的。

[外链图片转存失败(img-NEynbAZu-1566610817825)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565889236504.png)]

Topic:
	
	topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定建的字符串切分成单词,这些单词
	之间用点隔开。它同样也会识别两个通配符:符号"#"和符号"*".#匹配 0 个或多个单词,* 匹配一个单词。

[外链图片转存失败(img-0R5obkwl-1566610817825)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1565889373598.png)]

4.RabbitMQ 整合

1.引入 spring-boot-starter-amqp
2.application.yml 配置
3.测试 RabbitMQ
	1.AmqpAdmin : 管理组件
	2.RabbitTemplate : 消息发送处理组件
利用 docker 安装 rabbitmq
	: docker pull registy.docker-cn.com/library/rabbitmq:3-management
自动配置:
   1.RabbitAutoConfigutation
   2.有自动配置了配置工厂  ConnectionFactory ;
   3.RabbitProperties 封装了 RabbitMQ 的配置
   4.RabbitTemplate : 给 RabbitMQ 发送和接受消息
   5.AdqpAdmin : RabbitMQ 系统管理功能组件
 	 6.@EnableRabbit + @RabbitListener 监听消息队列的内容
application.properties

spring.rabbitmq.host=ip
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672
#spring.rabbitmq.virtual-host=
测试

  @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 1.单播:(点对点)
     */
    @Test
    public void contextLoads() {
        //Mwssage 需要自己构造一个;定义消息体内容和消息头
       //rabbitTemplate.send(exchange,routeKey,message);

        //object 默认当成消息体,只需要传入要发送的对象,自动序列化发送给 rabbitmq
       // rabbitTemplate.convertAndSend(exchange, routeKey,object);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("msg","这是第一个消息");
        map.put("data", Arrays.asList("helloworld",123,true));
        //对象被默认序列化后发送出去
        rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",new Book("西游记","吴承恩"));
    }

    /**
     * 接受数据,如何将数据自动的转为 json 发送出去
     */
    @Test
    public void receive() {
        Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
        System.out.println(o.getClass());
        System.out.println(o);
    }

    /**
     * 广播
     */
    @Test
    public void sendMsg() {
        rabbitTemplate.convertAndSend("exchange.fanout", "", new Book("Java 编程","黎彭飞"));
    }
自定义发送的消息格式(json)
  

@Configuration
public class MyAMQPConfig {

    @Bean
    public MessageConverter messageConverter() {

        return new Jackson2JsonMessageConverter();
    }
}

4.1.@RabbitListener & @EnableRabbit

开启基于注解的 RabbitMQ
/**
 * 自动配置:
 *  1.RabbitAutoConfigutation
 *  2.有自动配置了配置工厂  ConnectionFactory ;
 *  3.RabbitProperties 封装了 RabbitMQ 的配置
 *  4.RabbitTemplate : 给 RabbitMQ 发送和接受消息
 *  5.AdqpAdmin : RabbitMQ 系统管理功能组件
 *  6.@EnableRabbit + @RabbitListener 监听消息队列的内容
 */
@EnableRabbit        //开启基于注解的 RabbitMQ
@SpringBootApplication
public class SpringBootRabbitmqApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootRabbitmqApplication.class, args);
    }

}
监听
@Service
public class BookService {

    @RabbitListener(queues = "atguigu.news")
    public void receive(Book book) {
        System.out.println("收到消息:"+book);
    }
  
  
  @RabbitListener(queues = "atguigu")
    public void receive02(Message message) {
        System.out.println(message.getBody());
        System.out.println(message.getMessageProperties());
    }
}

4.2.AmqpAdmin 管理组件的使用

@Autowired
    AmqpAdmin amqpAdmin;

    @Test
    public void createExchange() {

        //创建交换器
        amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
        System.out.println("创建交换器完成");

        //创建队列
        amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));

        //创建绑定
        amqpAdmin.declareBinding(new Binding("amqpadmin.queue",
                Binding.DestinationType.QUEUE,"amqpadmin.exchange","exchange.haha"
        ,null));
    }

十一.SpringBoot 与检索

我们的应用经常需要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的首选。他可以快速的存储、搜索和分析海量数据。 
SpringBoot 通过整合 Spring Data ElasticSearch 为我们提供了非常便捷的检索功能支持;

Elasticsearch 是一个分布式搜索服务,提供 Restful API,底层基于 Lucene, 采用多 shard(分片) 的方式保证数据安全,
并且提供自动 resharding 的功能,github等大型的站点也是采用了 ElasticSearch 作为其搜索服务,
利用 docker 安装 ElasticSearch
	docker putll registry.docker-cn.com/library/elasticsearch
运行 elasticsearch
	docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 - 9300:9300 --name ES01 镜像 id

1.ElasticSearch 快速入门

中文学习文档:  https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html

2.概念

1.以员工文档的形势存储为例:一个文档代表一个员工数据。存储数据到 ElasticSearch 的行为叫做索引,但在索引一个文档之前,需要确定将文档存储在哪里。
2.一个 ElasticSearch 集群可以包含多个索引,相应的每个索引可以包含多个类型。这些不同的类型存储着多个文档,每个文档又有多个属性。
3.类似关系:
	索引—数据库
	类型—表
	文档—表中的记录
	属性—列

[外链图片转存失败(img-hY2uu2d2-1566610817826)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566009928498.png)]

3.整合 ElasticSearch 测试

步骤 
	
	1.引入 spring-boot-starter-data-elasticsearch
	2.安装 Spring Data 对应版本的 ElasticSearch
	3.application.yml 配置
	4.Spring Boot 自动配置的 
			ElasticsearchRepository、ElasticsearchTemplate、Client
	5.测试 ElasticSearch
elasticsearch

<!--Spring Boot 默认使用 SpringData Elasticsearch模块进行操作-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
Jest

<dependency>
	<groupId>io.searchbox</groupId>
  <artifactId>jest</artifactId>
  <version>5.3.3</version>
</dependency>

/**
 * SpringBoot 默认支持两种技术和 ES 交互
 * 1.Jest(默认不生效)
 *      需要导入 jest 的工具包(io.searchbox.client.JestClient)
 * 2.SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
 				 版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
 				 		如果版本不适配:
 						1.升级 SpringBoot 版本
 						2.安装对应版本的 ES
 *      1.Client  节点信息 clusterNodes  clusterName
 *      2.ElasticsearchTemplate 操作 ES
 *      3.编写一个 ElasticsearchRepository 的子接口来操作 ES。
 */
@SpringBootApplication
public class SpringBootElasticsearchApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootElasticsearchApplication.class, args);
    }

}
测试 jest 方式:
spring.elasticsearch.jest.uris=http://127.0.0.1:9200

3.1.SpringBoot 使用 Jest 技术来和 ES 进行交互(默认不生效)

在  pom.xml 文件中加入依赖

<dependency>
	<groupId>io.searchbox</groupId>
  <artifactId>jest</artifactId>
  <version>5.3.3</version>
</dependency>
测试 Jest 方式:
spring.elasticsearch.jest.uris=http://127.0.0.1:9200
测试类:

 @Autowired
    JestClient jestClient;


    @Test
    public void contextLoads() {
        //1.给 ES 中索引一个文档;
        Article article = new Article();
        article.setId(1);
        article.setTitle("好消息");
        article.setAuthor("张三");
        article.setContent("hello world");
        //构建一个索引功能
        Index index = new Index.Builder(article).index("atguigu").type("news").build();
        try {
            jestClient.execute(index);
        }catch (Exception e) {
        }
    }

    /**
     * 测试搜索
     */
    @Test
    public void search() {

        //查询表达式
        String json = "{\n" +
                "            \"query\" : {\n" +
                "                \"match\" : {\n" +
                "                    \"content\" : \"hello\"\n" +
                "                }\n" +
                "            }\n" +
                "        }" ;
        //构建搜索功能
        Search search = new Search.Builder(json).addIndex("atguigu").addType("news").build();

        //执行
        try {
            SearchResult result = jestClient.execute(search);
            System.out.println(result.getJsonString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3.2.SpringBoot 使用 SpringData ElasticSearch 技术来和 ES 进行交互(默认生效)

pom.xml

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置文件

spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
 		版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
 		如果版本不适配:
 				1.升级 SpringBoot 版本
 				2.安装对应版本的 ES
SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
 		版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
 		如果版本不适配:
 				1.升级 SpringBoot 版本
 				2.安装对应版本的 ES
    1.Client  节点信息 clusterNodes  clusterName
    2.ElasticsearchTemplate 操作 ES
    3.编写一个 ElasticsearchRepository 的子接口来操作 ES。
 两种方法: 	
 		1.编写一个 ElasticsearchRepository
编写 repository

public interface BookRepository extends ElasticsearchCrudRepository<Book, Integer> {

    //参照 https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/
    public List<Book> findByBookNameLike(String bookName);
}
测试

  @Autowired
    BookRepository bookRepository;

    @Test
    public void test02() {
        for (Book book : bookRepository.findByBookNameLike("游")) {
            System.out.println(book);
        }
        ;
    }
/**
 * SpringBoot 默认支持两种技术和 ES 交互
 * 1.Jest(默认不生效)
 *      需要导入 jest 的工具包(io.searchbox.client.JestClient)
 * 2.SpringData Elasticsearch 【ES版本 有可能不合适,导致连接不上】
 *       版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch
 *       如果版本不适配:
 *  		 1.升级 SpringBoot 版本
 *  		 2.安装对应版本的 ES
 *      1.Client  节点信息 clusterNodes  clusterName
 *      2.ElasticsearchTemplate 操作 ES
 *      3.编写一个 ElasticsearchRepository 的子接口来操作 ES。
 *    两种方法:
 *      1.编写一个 ElasticsearchRepository
 */

十二.Spring Boot 与任务

1.异步任务

@EnableAsync                       :开启异步方法

@EnableAsync   //开启异步注解
@SpringBootApplication
public class SpringBootTaskApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootTaskApplication.class, args);
    }

}
@Async                     : 标注异步方法


@Service
public class AsyncService {

    //告诉 Spring 这是一个异步方法
    @Async
    public void hello() {
        try {
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("处理数据中...");
    }
}
controller 层简单调用

@RestController
public class AsyncController {

    @Autowired
    AsyncService asyncService;

    @GetMapping("/hello")
    public String hello() {
        asyncService.hello();
        return "success";
    }
}

2.定时任务

项目开发中经常需要执行一些定时任务,比如需要每天凌晨时候,分析一次前一天的日志信息。 Spring 为我们提供了异步执行任务调度的方式,提供 TaskExecutor、TashScheduler 接口。
两个注解 :     @EnableScheduling、@Scheduled 
cron 表达式
字段 允许值 允许的特殊字符
0-59 ,- * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 , - * /
星期 0-7或SUN-SAT,0,7是SUN , - * ? / L C #
特殊字符 代表含义
, 枚举
- 区间
* 任意
/ 步长
? 日/星期冲突匹配
L 最后
W 工作日
C 和 calendar 联系后计算过的值
# 星期,4#2,第2个星期三
@EnableScheduling : 开启基于注解的定时任务


@EnableScheduling         //开启基于注解的定时任务
@EnableAsync   //开启异步注解
@SpringBootApplication
public class SpringBootTaskApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootTaskApplication.class, args);
    }

}
@Scheduled : 标注定时方法

@Service
public class ScheduledService {

    //标注一个定时任务

    /**
     * second,minute,hour,day of month(日),month,day of week
     * 0 * * * MON-FRI
     */
    @Scheduled(cron = "0 * * * * MON-SAT")
    public void hello() {
        System.out.println("hello ...");
    }
}

【0 0/5 14,18 * * ?】 每天14点整,和18点整,每隔5分钟执行一次
【0 15 10 ? * 1-6】 每个月的周一至周六 10:15分执行一次
【0 0 2 ? * 6L】 每个月的最后一个周六凌晨2点执行一次
【0 0 2 LW * ?】每个月的最后应工作日凌晨2点执行一次
【0 0 2-4 ? * 1#1】每个月的第一个周一凌晨2点到4点期间,每隔整点都执行一次

[外链图片转存失败(img-4fkc23xh-1566610817826)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566025160508.png)]

3.邮件任务

1.邮件发送需要引入 spring-boot-starter-mail
2.Spring Boot 自动配置 MailSenderAutoConfiguration
3.定义 MailProperties 内容,配置在 application.yml 中
4.自动装配 JavaMailSender
5.测试邮件发送
1.引入 spring-boot-starter-mail
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

[外链图片转存失败(img-Awu9Tf3H-1566610817827)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566025823995.png)]

spring.mail.username=1054080979@qq.com
spring.mail.password=授权码
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.stmp.ssl.enable=true
测试

 @Autowired
    JavaMailSenderImpl javaMailSender;


    @Test
    public void contextLoads() {
        SimpleMailMessage message = new SimpleMailMessage();
        //邮件设置
        message.setSubject("通知");
        message.setText("测试内容");
        message.setTo("13600156336@163.com");
        message.setFrom("1054080979@qq.com");

        javaMailSender.send(message);
    }


    @Test
    public void test02() throws Exception {
        //1.创建一个复杂的消息邮件
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        //邮件设置
        mimeMessageHelper.setSubject("通知");
        mimeMessageHelper.setText("测试内容");
        mimeMessageHelper.setTo("13600156336@163.com");
        mimeMessageHelper.setFrom("1054080979@qq.com");

        //上传附件

        mimeMessageHelper.addAttachment("1.jpg",new File("D:\\..."));
        mimeMessageHelper.addAttachment("2.jpg",new File("D:\\..."));

        javaMailSender.send(mimeMessage);
    }

十三.Spring Boot 与安全

1.安全

Spring Security 是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型。它可以实现强大的 web 安全控制。对于安全控制,
我们仅仅需要引入 Spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理。
几个类

WebSecurityConfigurerAdapter : 自定义 Security 策略
AuthenicationManagerBuilder : 自定义认证策略
@EnableWebSecurity : 开启 WebSecurity 模式
应用程序的两个主要区域是"认证"和"授权"(或者访问控制)。
这两个主要区域是 Spring Security 的两个目标。

"认证"(Authentication) : 是建立一个它声明的主体的过程(一个"主体"一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)

"授权"(Authorization) : 指明确一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的话店,主体的身份已经有认证过程建立。

这个概是通用的,而不是只在 Spring Security 中。
/**
 * 1、引入SpringSecurity;
 * 2、编写SpringSecurity的配置类;
 * 		@EnableWebSecurity   extends WebSecurityConfigurerAdapter
 * 3、控制请求的访问权限:
 * 		configure(HttpSecurity http) {
 * 		 	http.authorizeRequests().antMatchers("/").permitAll()
 * 		 		.antMatchers("/level1/**").hasRole("VIP1")
 * 		}
 * 4、定义认证规则:
 * 		configure(AuthenticationManagerBuilder auth){
 * 		 	auth.inMemoryAuthentication()
 * 		 		.withUser("zhangsan").password("123456").roles("VIP1","VIP2")
 * 		}
 * 5、开启自动配置的登陆功能:
 * 		configure(HttpSecurity http){
 * 		 	http.formLogin();
 * 		}
 * 6、注销:http.logout();
 * 7、记住我:Remeberme();
 */
@SpringBootApplication
public class Springboot05SecurityApplication {

	public static void main(String[] args) {
		SpringApplication.run(Springboot05SecurityApplication.class, args);
	}
}
@Controller
public class KungfuController {
	private final String PREFIX = "pages/";
	/**
	 * 欢迎页
	 * @return
	 */
	@GetMapping("/")
	public String index() {
		return "welcome";
	}
	
	/**
	 * 登陆页
	 * @return
	 */
	@GetMapping("/userlogin")
	public String loginPage() {
		return PREFIX+"login";
	}
	
	
	/**
	 * level1页面映射
	 * @param path
	 * @return
	 */
	@GetMapping("/level1/{path}")
	public String level1(@PathVariable("path")String path) {
		return PREFIX+"level1/"+path;
	}
	
	/**
	 * level2页面映射
	 * @param path
	 * @return
	 */
	@GetMapping("/level2/{path}")
	public String level2(@PathVariable("path")String path) {
		return PREFIX+"level2/"+path;
	}
	
	/**
	 * level3页面映射
	 * @param path
	 * @return
	 */
	@GetMapping("/level3/{path}")
	public String level3(@PathVariable("path")String path) {
		return PREFIX+"level3/"+path;
	}


}

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        //定制请求的授权规则
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("VIP1")
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3");

        //开启自动配置的登陆功能,效果,如果没有登陆,没有权限就会来到登陆页面
        http.formLogin().usernameParameter("user").passwordParameter("pwd")
                .loginPage("/userlogin");
        //1、/login来到登陆页
        //2、重定向到/login?error表示登陆失败
        //3、更多详细规定
        //4、默认post形式的 /login代表处理登陆
        //5、一但定制loginPage;那么 loginPage的post请求就是登陆


        //开启自动配置的注销功能。
        http.logout().logoutSuccessUrl("/");//注销成功以后来到首页
        //1、访问 /logout 表示用户注销,清空session
        //2、注销成功会返回 /login?logout 页面;

        //开启记住我功能
        http.rememberMe().rememberMeParameter("remeber");
        //登陆成功以后,将cookie发给浏览器保存,以后访问页面带上这个cookie,只要通过检查就可以免登录
        //点击注销会删除cookie

    }

    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //super.configure(auth);
        auth.inMemoryAuthentication()
                .withUser("zhangsan").password("123456").roles("VIP1","VIP2")
                .and()
                .withUser("lisi").password("123456").roles("VIP2","VIP3")
                .and()
                .withUser("wangwu").password("123456").roles("VIP1","VIP3");

    }
}

十四.Spring Boot 与分布式

1.分布式应用

在分布式系统中,国内常用 zookeeper + dubbo 组合,而 Spring Boot 推荐使用全栈的 Spring , Spring Boot + Spring Cloud .

分布式系统;

[外链图片转存失败(img-TLFXqBSH-1566610817827)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566031783364.png)]

2.Zookeeper 和 Dubbo

Zookeeper:

	Zookeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式提供一致性服务的软件,提供的功能包括:
		配置维护、域名服务、分布式同步、组服务等。
		
Dubbo:
	
	Dubbo 是 Alibaba 开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者
	最大限度的松耦合)。从服务模型的角度来看,Dubbo 采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,
	所以基于这一点可以抽象出服务提供方和服务消费方两个角色。

[外链图片转存失败(img-O2g15SeD-1566610817828)(C:\Users\laughig\AppData\Roaming\Typora\typora-user-images\1566032198406.png)]

3.SpringBoot dubbo, zookeeper

4.Spring Boot 和 Spring Cloud

Spring Cloud 
	
	Spring Cloud 是一个分布式的整体解决方案。Spring Cloud 为开发者提供了 在分布式系统(配置管理,服务发现,熔断,路由,
	微代理,控制总线,一次性 token, 全局锁,leader 选举,分布式 session, 集群状态)中快速构建的工具,使用 Spring Cloud
	的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
	
Spring Cloud 分布式开发五大常用组件
	服务实现——Netflix Eureka
	客服端负载均衡——Netflix Ribbon
	断路器——Netflix Hystrix
	服务网关——Netflix Zuul
	分布式配置——Spring Cloud Config

4.1.注册中心

1.配置 Eureka 信息
2.@EnableEurekaServer : 启动注册中心
1.配置 Eureka 信息

server:
  port: 8761
euraka:
  instance:
    hostname: eureka-server        #  eureka 实例的主机名
  client:
    register-with-eureka: false   # 不将自己注册到 eureka 上
    fetch-registry: false         # 不从 eureka 上来获取服务的注册信息
    service-url:
      defaultZone: httpL//localhost:8761/eureka/        #注册中心地址

2.启动注册中心

/**
 * 注册中心
 *  1.配置 Eureka 信息
 *  2. @EnableEurekaServer : 启动注册中心
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

    public static void main(String[] args) {

        SpringApplication.run(EurekaServerApplication.class, args);
    }

}

4.2.服务注册

1.编写服务以及访问
2.配置服务
1.编写服务

@Service
public class TicketService {

    public String getTicket() {
        System.out.println("8082...");
        return "《厉害了,我的国》";
    }
}
1.服务访问

@RestController
public class TicketController {

    @Autowired
    TicketService ticketService;


    @GetMapping("/ticket")
    public String getTicket() {
       return ticketService.getTicket();
    }
}
2.配置服务

server:
  port: 8082
spring:
  application:
    name: provider-ticket

euraka:
  instance:
    prefer-ip-address: true     # 注册服务的时候使用服务的 ip 地址
  client:
    service-url:
      defaultZone: httpL//localhost:8761/eureka/        #注册中心地址

4.3.服务发现&消费

1.编写访问服务
2.配置服务
1.编写服务访问

@RestController
public class UserController {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/buy")
    public String buyTicket(String name) {
        String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
        return name+"购买了"+s;
    }
}
@EnableDiscoveryClient   :开启服务发现功能

                      
@EnableDiscoveryClient         //开启发现服务功能
@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {

        SpringApplication.run(ConsumerApplication.class, args);
    }

    @LoadBalanced          //使用负载均衡机制
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
配置服务

spring:
  application:
    name: consumer-user
server:
  port: 8181

euraka:
  instance:
    prefer-ip-address: true     # 注册服务的时候使用服务的 ip 地址
  client:
    service-url:
      defaultZone: httpL//localhost:8761/eureka/        #注册中心地址

十五. Spring Boot 与开发热部署

1.热部署

热部署

	在开发中我们修改一个 Java 文件后想看到效果不得不重启应用,这导致大量时间花费,我们希望不重启应用的情况下,程序可以自动部署(热部署)。
	有以下四种情况,如何实现热部署:
	
	1.模板引擎
		在 Spring Boot 中开发情况下禁用模板引擎的 cache
		页面模板修改 ctrl + F9 可重新编译当前页面并且生效
		
	2.Spring Loaded 
		Spring 官方提供的热部署程序,实现修改类文件的热部署
			下载 Spring Loaded (项目地址: https://github.com/spring-projects/spring-loaded)
			添加运行时参数:
				-javagent:C:/springloaded-1.2.5.RELEASE.jar --noverify
				
	3.JRebel
		收费的一个热部署软件
		安装插件使用即可
		
	4.Spring Boot Devtools(推荐)
		引入依赖
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-devtools</artifactId>
			</dependency>

十六. Spring Boot 与监控管理

通过引入 spring-boot-starter-actuator ,可以使用 Spring Boot 为我们提供的准生产环境下的应用监控和管理功能。
我们可以通过 HTTP、JMX、SSH 协议来进行操作,自动得到审计、健康及指标信息等。
步骤:
	1.引入 spring-boot-starter-actuator
	2.通过 http 方式访问监控端点
	3.可进行 shutdown(POST 提交,此端点默认关闭)
监控和管理端点
端点名 描述
autoconfig 所有自动配置信息
auditevents 审计事件
beans 所有 Bean 的信息
configprops 所有配置属性
dump 线程状态信息
env 当前环境信息
health 应用健康状况
info 当前应用信息
metrics 应用的各项指标
mappings 应用@RequesMapping 映射路径
shutdown 关闭当前应用(默认关闭)
trace 追踪信息(最新的 http 请求)

1.定制端点信息

 定制端点一般通过 endpoints + 端点名 + 属性名来设置。
 修改端点 id(endpoints.beans.id=mybeans)
 开启远程应用关闭功能(endpoints.shutdown.enabled=true)
 关闭端点(endpoints.beans.enabled=false)
 开启所需端点
 		endpoints.enabled=false
 		endpoints.beans.enabled=true
 定制端点访问路径
 		management.context-path=/manage
 关闭 http 端点
 		management.port=-1

2.自定义 HealthIndicator

自定义健康状态指示器
	1.编写一个指示器(必须实现 HealthIndicator 接口)
	2.指示器的名字为 xxxHealthIndicator
	3.加入容器中
public class MyAppHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {

        //自定义的检查方法
        //Health.up().build();       //代表健康
        return Health.down().withDetail("msg","服务异常").build();
    }
}
上一篇:Spring Boot总结


下一篇:camera中LENS和SENSOR的CRA是如何搭配的?