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

开发环境
jdk1.8
maven 3.3.9(注意官网下载zip格式解压)
Itellij Idea 2017.2.2
SpringBoot 1.5.9.RELEASE

Maven配置

      <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>  
 
      <repositories>
        <repository>
          <id>jdk18</id>
          <name>Repository for JDK 1.8 builds</name>
          <url>http://www.myhost.com/maven/jdk14</url>
          <layout>default</layout>
          <snapshotPolicy>always</snapshotPolicy>
        </repository>
      </repositories>
    </profile>

IDEA设置Maven

Spring Boot Hello world
①创建一个Maven工程
②导入Spring Boot相关依赖

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

event log报错
Unable to import maven project: See logs for details
help——>show log in explorer查看错误日志
2 errors
java.lang.RuntimeException: com.google.inject.CreationException: Unable to create injector, see the following errors:

1) No implementation for org.apache.maven.model.path.PathTranslator was bound.
while locating org.apache.maven.model.path.PathTranslator
for field at org.apache.maven.model.interpolation.AbstractStringBasedModelInterpolator.pathTranslator(Unknown Source)
at org.codehaus.plexus.DefaultPlexusContainer$1.configure(DefaultPlexusContainer.java:350)

2) No implementation for org.apache.maven.model.path.UrlNormalizer was bound.
while locating org.apache.maven.model.path.UrlNormalizer
for field at org.apache.maven.model.interpolation.AbstractStringBasedModelInterpolator.urlNormalizer(Unknown Source)
at org.codehaus.plexus.DefaultPlexusContainer$1.configure(DefaultPlexusContainer.java:350)
解决方案:调整Maven版本。
③.编写一个主程序

package com.wuxinzhe;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 * @SpringBootApplication来标注一个主程序类,说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class MainApplication {
 
    public static void main(String[] args) {
        // Spring Boot 应用启动
        SpringApplication.run(MainApplication.class, args);
    }
 
}

④编写一个Controller处理请求

package com.wuxinzhe.controller;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class HelloWorldController {
 
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }
 
}

⑤简化部署
在pom.xml中配置

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

使用maven的package提示如下:
[INFO] — maven-jar-plugin:2.6:jar (default-jar) @ SpringBootHelloworld —
[INFO] Building jar: E:\ideawordspace\SpringBootHelloworld\target\SpringBootHelloworld-1.0-SNAPSHOT.jar
cmd使用java -jar命令运行jar包
C:\Users\Dell\Desktop>java -jar SpringBootHelloworld-1.0-SNAPSHOT.jar

父工程

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

导入的依赖

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

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

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
		</dependency>
	</dependencies>

Spring Boot将所有功能场景都抽取出来,做成一个个starters(启动器),只需要在项目里面引入这些starters,相关场景的依赖就会导入进来,而且版本由springboot自动管理。

主程序类、主入口类

package com.wuxinzhe;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 * @SpringBootApplication来标注一个主程序类,说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class MainApplication {
 
    public static void main(String[] args) {
        // Spring Boot 应用启动
        SpringApplication.run(MainApplication.class, args);
    }
 
}

@SpringBootApplication标注在某个类上说明这个类是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配置类
@SpringBootConfiguration SpringBootConfiguration.class

package org.springframework.boot;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
 
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@Configuration配置类上来标注这个注解,配置类——配置文件;配置类也是容器中的一个组件@Component
@Configuration Configuration.class

package org.springframework.context.annotation;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
 
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

@EnableAutoConfiguration开启自动配置功能,以前我们需要配置的东西,SpingBoot帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置功能才能生效。
@EnableAutoConfiguration E:\devinstall\apache-maven-3.6.3\repo\org\springframework\boot\spring-boot-autoconfigure\1.5.9.RELEASE\spring-boot-autoconfigure-1.5.9.RELEASE.jar!\org\springframework\boot\autoconfigure\EnableAutoConfiguration.class

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

@AutoConfigurationPackage自动配置包,将主配置类(@SpringBootApplication)的所在包,及下面所有子包里面的所有组件容器扫描进来。
@Import({Registrar.class}),Spring的底层注解@import,给容器中导入一个组件,导入的组件由
org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar.class
@EnableAutoConfiguration EnableAutoConfiguration.class

@Import({EnableAutoConfigurationImportSelector.class})

EnableAutoConfigurationImportSelector:导入哪些组件的选择器,将所有需要的组件以全类名的方式返回,这些组件就会被添加到容器中;会给容器导入非常多的自动配置类(xxxAutoConfiguration),这些自动配置类就是给容器中导入这个场景需要的所有组件,并配置好这些组件。
使用向导快速创建Spring Boot应用
默认生成的SpringBoot项目:
①主程序已经生成好了,我们只需要写自己的逻辑
②resources文件夹中目录结构

  • static:保存所有静态资源,如js,css,images
  • templates:保存所有模板页面;(SpringBoot默认jar包使用欧冠嵌入式的tomcat,默认不支持jsp页面),我们可以使用模板引擎(freemaker、thymeleaf等)
  • application.properties:SpringBoot应用的配置文件,可以修改一些默认配置。

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

  1. application.properties
  2. application.yml

配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;YAML是”YAML Ain’t a Markup Language”(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
以前的配置大多都是xxx.xml文件;
YAML以数据为中心,比json,xml更适合做配置文件。
YAML配置例子

server:
  port: 8081

YAML语法
①基本语法
k:空格v:表示一对键值对(空格必须有)
空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的,属性和值是大小写敏感的。

server:
  port: 8081
  path: /hello

②值的写法
字面量:普通的值(数字,字符串,布尔)
k: v字面直接来写,字符串默认不用加上单引号或者双引号
“”:双引号,会转义字符里面的特殊字符,具有不同于该字符序列单独出现时的语义,如下
name: “zhangshan \n lisi” 输出zhangsan换行lisi
”:单引号;不转义特殊字符,特殊字符最终只是一个普通的字符串数据,name:’zhangshan \n lisi’ 输出zhangsan \n lisi
对象、Map(属性和值)(键值对)
k: v对象还是k: v的方式

  friends:
    lastName: zhangsan
	age: 20

行内写法:

friends: {lastName: zhangsan,age: 20}

数组(List/Set)
用-值来表示数组中的一个元素

pets:
  -cat
  -dog
  -pig

行内写法:

pets: [cat,dog,pig]

yaml配置文件值获取,配置文件值注入
配置文件

  person:
    lastName: 王二麻子
    age: 20
    boss: true
    date: 2020/02/18
    map: {k1: v1,k2: v2}
    lists:
          - 猪
          - 猫
          - 狗
    dog:
        name: 波比
        age: 18

javabean Person.java

package com.wuxinzhe.quickstart.bean;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
 
import java.util.Date;
import java.util.List;
import java.util.Map;
 
/**
 * 将配置文件中配置的每一个值,映射到这个组件中
 * @ConfigurationProperties 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
 * prefix = "person" 配置文件中哪个下面的所有属性一一映射
 * 只有这个组件是容器中的组件,才能使用容器中@ConfigurationProperties的功能
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
 
    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date date;
 
    private Map<String,Object> map;
    private List<Object> lists;
    private Dog dog;
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public Boolean getBoss() {
        return boss;
    }
 
    public void setBoss(Boolean boss) {
        this.boss = boss;
    }
 
    public Date getDate() {
        return date;
    }
 
    public void setDate(Date date) {
        this.date = date;
    }
 
    public Map<String, Object> getMap() {
        return map;
    }
 
    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
 
    public List<Object> getLists() {
        return lists;
    }
 
    public void setLists(List<Object> lists) {
        this.lists = lists;
    }
 
    public Dog getDog() {
        return dog;
    }
 
    public void setDog(Dog dog) {
        this.dog = dog;
    }
 
    @Override
    public String toString() {
        return "Person{" +
                "lastName='" + lastName + '\'' +
                ", age=" + age +
                ", boss=" + boss +
                ", date=" + date +
                ", map=" + map +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}

单元测试

package com.wuxinzhe.quickstart;
 
import com.wuxinzhe.quickstart.bean.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuickstartApplicationTests {
 
	@Autowired
	Person person;
 
	@Test
	public void contextLoads() {
		System.out.println(person);
	}
}

properties配置文件乱码
同上yaml内容,properties配置文件写法

person.last-name=王二麻子
person.age=20
person.boss=true
person.date=2020/02/18
person.map.k1=v1
person.map.k2=v2
person.lists=猪,猫,狗
person.dog.name=波比2
person.dog.age=18

注意:读取时候乱码,通过下图解决即可

@Value和@ConfigurationProperties区别

只是在某个业务逻辑中需要获取以下配置文件的某项值,则使用@Value;如果我们专门写了一个javabean来和配置文件进行映射时,我们就直接使用@ConfigurationProperties。

@PropertySource结合@ConfigurationProperties加载指定配置文件值

package com.wuxinzhe.quickstart.bean;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
 
import java.util.Date;
import java.util.List;
import java.util.Map;
 
/**
 * 将配置文件中配置的每一个值,映射到这个组件中
 *
 * @ConfigurationProperties 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
 * prefix = "person" 配置文件中哪个下面的所有属性一一映射
 * 只有这个组件是容器中的组件,才能使用容器中@ConfigurationProperties的功能
 */
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {}

@ImportResource加载自定义配置文件
①创建一个service

package com.wuxinzhe.quickstart.service;
 
public class HelloService {
}

②创建一个spring xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      <bean class="com.wuxinzhe.quickstart.service.HelloService" id="helloService">
      </bean>
</beans>

③主应用@ImportResource加载配置文件

@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class QuickstartApplication {
	public static void main(String[] args) {
		SpringApplication.run(QuickstartApplication.class, args);
	}
}

④单元测试

package com.wuxinzhe.quickstart;
 
import com.wuxinzhe.quickstart.bean.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuickstartApplicationTests {
 
	@Autowired
	ApplicationContext ioc;
 
	@Test
	public void helloService(){
		boolean bool = ioc.containsBean("helloService");
		System.out.println(bool);
	}
 
}

@Bean和@Configuration配置类将javabean加入容器(推荐使用)

package com.wuxinzhe.quickstart.config;
 
import com.wuxinzhe.quickstart.service.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class MyAppConfig {
    @Bean
    public HelloService helloService(){  //helloService为方法名
        return new HelloService();
    }
}

单元测试

	@Autowired
	ApplicationContext ioc;
 
	@Test
	public void helloService(){
		boolean bool = ioc.containsBean("helloService");  // 获取javabean名字对应注入类的方法名
		System.out.println(bool);
	}


配置文件使用占位符

hasVariable=玻璃
person.last-name=王二麻子
# 随机数
person.age=${random.int}
person.boss=true
person.date=2020/02/18
person.map.k1=v1
person.map.k2=v2
person.lists=猪,猫,狗
# 引用之前定义的变量
person.dog.name=${hasVariable} 
# 引用之前定义的变量 未定义给一个默认值
person.dog.age=${dogAge:22}

多个profile文件支持
resources\application-dev.properties文件

server.port=8084

创建resources\application-product.properties文件

server.port=8199

resources\application.properties文件

server:
  port: 8082
spring:
  profiles:
    active: product

服务器启用8199端口

三个短横线,多个profile区(文档快)

server:
  port: 8082
spring:
  profiles:
    active: prod
---
spring:
  profiles: prod
server:
  port: 8777
---
spring:
  profiles: development
server:
  port: 8999

服务器启用8777端口

同上配置文件,采用命令参数,使用development配置

①--spring.profiles.active=development


②-Dspring.profiles.active=development

③打成jar包后cmd命令行启动

E:\ideawordspace\quickstart\target>java -jar quickstart-0.0.1-SNAPSHOT.jar --spring.profiles.active=development


配置文件加载位置
①加载优先级
SpringBoot启动会扫描以下位置的application.properties或者application.yml文件作为springboot的默认配置文件
-project/config/配置文件名
-project/配置文件名
-classpath:/config/配置文件名
-classpath:配置文件名
优先级由高到低,同样的内容,优先级高的会覆盖优先级的内容。

application.properties

server.port=8081
#应用的上下文路径,也可以称为项目路径,是构成url地址的一部分。
server.servlet.context-path=/boot

访问路径就必须为 域名/boot/url

②通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置,指定配置文件和默认加载这些配置文件共同起作用形成互补配置。
‪C:\Users\Dell\Desktop\application.properties

server.port=8777

cmd执行java -jar springboot-config-0.0.1-SNAPSHOT.jar –spring.config.location=C:\Users\Dell\Desktop\application.properties命令

外部配置加载顺序
Springboot也可以从以下位置加载配置;优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互不配置。
1.命令行参数(多个配置用空格隔开 –配置项=值)
E:\ideawordspace\springboot-config\target>java -jar springboot-config-0.0.1-SNAPSHOT.jar –server.port=8092’
2.来自java:comp/env的NDI属性
3.java系统属性(System.getProperties)
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值
由jar包外优先级高于jar包内,然后优先加载带profile的
6.jar包外部的application-{profile-name}.properties或者application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile-name}.properties或者application.yml(带spring.profile)配置文件
8.jar包外部的application-properties或application.yml(不带spring.profile)配置文件

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

10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性

自动配置原理(18.自动配置原理++++++++需要多听几遍)
SpringBoot配置文件所有属性(SpringBoot不同版本可能有稍微差异)https://docs.spring.io/spring-boot/docs/2.3.0.BUILD-SNAPSHOT/reference/html/appendix-application-properties.html#common-application-properties
①SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration
org\springframework\boot\autoconfigure\SpringBootApplication.class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {

②@EnableAutoConfiguration作用
org\springframework\boot\autoconfigure\EnableAutoConfiguration.class

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

利用AutoConfigurationImportSelector.class给容器中导入一些组件?
可以通过AutoConfigurationImportSelector中selectImports方法查看

  List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//获取候选的配置
 
  (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

扫描所有jar包路径下的META-INF/spring.factories
把扫描到的这些文件的内容包装成properties对象
从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把它们添加到容器中
将类路径下META-INF\spring.factories里面配置的所有EnableAutoConfiguration的值加入到容器中

③spring-boot-autoconfigure-2.2.4.RELEASE.jar

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
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.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\
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.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
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.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
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.class(Http编码自动配置)为例来看自动配置原理

@Configuration(
    proxyBeanMethods = false
)  //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties({HttpProperties.class}) // 启用指定类的ConfigurationProperties功能,将配置文件中对应的值和HttpProperties绑定起来,并把HttpProperties加入到ioc容器中
@ConditionalOnWebApplication(type = Type.SERVLET) //Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效,判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass({CharacterEncodingFilter.class}) // ConditionalOnClass:当给定的类名在类路径上存在,则实例化当前Bean
CharacterEncodingFilter:springMVC进行乱码处理的过滤器
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的;即使我们配置文件中不配置spring.http.encoding.enabled=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中获取的,这些类里边的每一个属性又是和配置组件绑定的。

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

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

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

xxxAutoConfiguration:自动配置类,给容器中添加组件
xxxProperties:封装配置文件中相关属性

自动配置类
自动配置类必须在一定的条件下才能生效。
我们怎么知道哪些自动配置类生效;可以在配置文件application.properties使用debug=true,在控制台就可以查看哪些配置类生效

============================
CONDITIONS EVALUATION REPORT
============================
 
 
Positive matches:  // 启用
-----------------
 
   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
 
   AopAutoConfiguration.ClassProxyingConfiguration matched:
      - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
      - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)
 
Negative matches:  // 未启用
-----------------
 
   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
 
   AopAutoConfiguration.AspectJAutoProxyingConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice' (OnClassCondition)

SLF4j的使用
日志门面(日志的一个抽象层):SLF4J
日志实现(实现抽象层):Log4j Logback

SpringBoot底层是Spring框架,Spring框架默认是用JCL;SpringBoot选用SLF4j和Logback。

以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里的方法,给系统里面导入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");
  }
}


每一个日志的实现框架都有自己的配置文件,使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件。
其他日志统一转换为slf4j
如何让系统中所有的日志都统一到slf4j
1.将系统中其他日志框架先排除出去;
2.用中间包来替换原有的日志框架;
3.我们导入slf4j其他的实现
http://www.slf4j.org/images/legacy.png

总结
1.SpringBoot底层也是使用slf4j+logback的方式进行日志记录
2.SpringBoot也把其他的日志都替换成了slf4j

如果我们要引入其他框架,一定要把这个框架的默认日志依赖移除掉。
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉
SpringBoot默认配置
logging.level设置日志级别,后面跟生效的区域,比如root表示整个项目,也可以设置为某个包下,也可以具体到某个类名(日志级别的值不区分大小写),如

logging.level.root=warn
logging.level.com.wuxinzhe=debug
 
//在当前项目下生成日志文件
logging.file=springbootlog.log
 
指定磁盘路径生成日志文件
logging.file=E:\\springbootlog.log
 
指定在项目对应的磁盘根目录下创建/spring/log/spring.log文件
logging.path=/spring/log
 
#规定日志在控制台输出格式
logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n
 
#规定日志在文件输出格式
logging.pattern.file=%d{yyyy/MM/dd-HH:mm} [%thread] %-5level %logger- %msg%n
 
SpringBoot指定日志文件和日志Profile功能
给类路径下放上每个日志框架自己的配置文件即可,SpringBoot就不使用它默认配置的了。

单元测试

package com.wuxinzhe;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class test {
 
   Logger logger =  LoggerFactory.getLogger(getClass());
 
   @Test
    public void testLog(){
      //日志的级别由低到高
      // 可以调整日志的输出级别,日志就只会在这个级别和这个级别以后的高级别生效
       logger.trace("这是trace日志...");
       logger.debug("这是debug日志");
       // 默认采用info级别
       logger.info("这是info日志");
      logger.warn("这是warn日志");
      logger.error("这是error日志");
   }
}

SpringBoot指定日志文件
在类路径下放指定日志框架的配置文件就可以了
https://docs.spring.io/spring-boot/docs/2.1.12.RELEASE/reference/html/boot-features-logging.html#boot-features-custom-log-configuration
logback.xml:直接就被日志框架识别了
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpirngBoot来解析日志配置,可以使用SpringBoot高级Profile功能,否则就会报错。

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

logback.xml配置参考

logback.xml常用配置详解


logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
	debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 value表示的是打印到哪里的-->
    <property name="LOG_HOME" value="/logs/"/> >
    <!-- 定义日志文件名称  value表示的是log的名称-->
    <property name="appName" value="u-plan"/>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
            %msg:日志消息,%n是换行符 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>
 
    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="appLogAppender"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!-- 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名 TimeBasedRollingPolicy:
            最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 %i:当文件大小超过maxFileSize时,按照i进行文件滚动 -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log
            </fileNamePattern>
            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动, 且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
                那些为了归档而创建的目录也会被删除。 -->
            <MaxHistory>30</MaxHistory>
            <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy -->
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
            %msg:日志消息,%n是换行符 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <!--springProfile指定某个环境下生效,如果配置此标签,必须在配置文件中指定环境spring.profiles.active-->
            <springProfile name="dev">
               <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ]   [%logger{50} : %line ] - %msg%n</pattern>
            </springProfile>
            <springProfile name="prod">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [%logger{60} : %line ] - %msg%n</pattern>
            </springProfile>
        </layout>
    </appender>
 
    <!-- root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应, 要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration>

切换日志框架为log4j
①logback排除掉
②log4j-over-sel4j排除掉

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

③添加sel4j依赖

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>

④类路径下添加log4j.properties

#you cannot specify every priority with different file for log4j
log4j.rootLogger=debug,stdout,info,debug,warn,error
#log4j.rootLogger=info,stdout,error
 
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
 
#info log
log4j.logger.info=info
log4j.appender.info=org.apache.log4j.DailyRollingFileAppender 
log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.info.File=/src/com/hp/log/info.log
log4j.appender.info.Append=true
log4j.appender.info.Threshold=INFO
log4j.appender.info.layout=org.apache.log4j.PatternLayout 
log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
 
#debug log
log4j.logger.debug=debug
log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender
log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.debug.File=./src/com/hp/log/debug.log
log4j.appender.debug.Append=true
log4j.appender.debug.Threshold=DEBUG
log4j.appender.debug.layout=org.apache.log4j.PatternLayout
log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
 
#warn log
log4j.logger.warn=warn
log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender
log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.warn.File=./src/com/hp/log/warn.log
log4j.appender.warn.Append=true
log4j.appender.warn.Threshold=WARN
log4j.appender.warn.layout=org.apache.log4j.PatternLayout
log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
 
#error
log4j.logger.error=error
log4j.appender.error = org.apache.log4j.DailyRollingFileAppender
log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.error.File = /src/com/hp/log/error.log
log4j.appender.error.Append = true
log4j.appender.error.Threshold = ERROR
log4j.appender.error.layout = org.apache.log4j.PatternLayout
log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n

webjars和静态资源的映射规则

org\springframework\boot\autoconfigure\web\ResourceProperties.class

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {
  //可以设置和静态资源有关的参数,缓存时间
      private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
      private String[] staticLocations;
      private boolean addMappings;
      private final ResourceProperties.Chain chain;
      private final ResourceProperties.Cache cache;

org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.class

        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(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }
 
            }
        } 
 
 
		// 配置欢迎页映射
		@Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(this.getInterceptors());
            return welcomePageHandlerMapping;
        }
 
 
 
		@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.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);
            }
        }

1.webjars资源访问 所有/webjars/**都去classpath:/META-INF/resources/webjars/去寻找资源
①.导入依赖(https://www.webjars.org/)

		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>jquery</artifactId>
			<version>2.2.0</version>
		</dependency>

②.http://localhost:8080/webjars/jquery/2.2.0/jquery.min.js

“/**”访问当前项目的任何资源,静态资源文件夹

2.访问静态资源

classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/   当前项目的根路径

3.欢迎页,静态资源文件夹下所有index.html页面
http://localhost:8080/ 找index.html页面

4.图标(在静态资源文件夹下找)
**/favicon.ico

thymeleaf模板引擎
1.导入thymeleaf依赖

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

2.thymeleaf示例

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
	// 只要我们把html页面放在类路径下的template里边,就可以渲染
    public static final String DEFAULT_PREFIX = "classpath:/templates/";  
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
    private boolean cache;
    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;

3.thymeleaf语法
①获取变量值
获取对象的属性或者方法
示例:${userName}

	/*
	* Access to properties using the point (.). Equivalent to calling property getters.
	*/
	${person.father.name}
	/*
	* Access to properties can also be made by using brackets ([]) and writing
	* the name of the property as a variable or between single quotes.
	*/
	${person['father']['name']}
	/*
	* If the object is a map, both dot and bracket syntax will be equivalent to
	* executing a call on its get(...) method.
	*/
	${countriesByCode.ES}
	${personsByName['Stephen Zucchini'].age}
	/*
	* Indexed access to arrays or collections is also performed with brackets,
	* writing the index without quotes.
	*/
	${personsArray[0].name}
	/*
	* Methods can be called, even with arguments.
	*/
	${person.createCompleteName()}
	${person.createCompleteNameWithSeparator('-')}

内置基本对象

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

示例:${session.user}

变量选择表达式Selection Variable Expressions
*{…} 配合th:object使用

	<div th:object="${session.user}">
		<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
		<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
		<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
	</div>

Message Expressions国际化
#{…}
Link URL Expressions链接url
@{…}

 <!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

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文本操作
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations数学运算
Binary operators: + , – , * , / , %
Minus sign (unary operator): –
④Boolean operations布尔运算
Binary operators: and , or
Boolean negation (unary operator): ! , not
⑤Comparisons and equality比较运算
Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) ⑥Conditional operators条件运算 If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) ⑦Special tokens: Page 17 of 106 No-Operation: _ All these features can be combined and nested: 'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))

[[…]]会被转义,[(…)]不转义。
 变量值为<b>AAA</b>     
[[${}]]   <b>AAA</b>
[(${})]   <b>AAA</b>

国际化
SpringMVC国际化步骤
1.编写国际化配置文件
2.使用ResourceBundleMessageResource管理国际化资源文件
3.在页面使用fmt:message取出国际化内容

@Configuration
@ConditionalOnMissingBean(
    value = {MessageSource.class},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];
 
    public MessageSourceAutoConfiguration() {
    }
 
    @Bean
    @ConfigurationProperties(
        prefix = "spring.messages"
    )
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }
 
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
		    //设置国际化资源文件的基础名(去掉语言国家代码的)
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }
 
        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
 
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }
 
        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }

SpringBoot国际化步骤
1.创建国家化文件

如login_en_US.properties
login.btn=login
login.password=Password
login.remember=Remember me
login.tip=please login in
login.username=UserName
2.application.properties中配置

spring.messages.basename=i18n.login

3.视图文件中引用

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}"></h1>

通过链接切换国际化
原理:国际化Locale(区域信息对象) LocaleResolver(获取区域信息对象)
org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.class

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

org\springframework\web\servlet\i18n\AcceptHeaderLocaleResolver.class

		public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();
        if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;
        } else {
            Locale requestLocale = request.getLocale();
            List<Locale> supportedLocales = this.getSupportedLocales();
            if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
                Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
                if (supportedLocale != null) {
                    return supportedLocale;
                } else {
                    return defaultLocale != null ? defaultLocale : requestLocale;
                }
            } else {
                return requestLocale;
            }
        }
    }

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

①创建一个实现LocaleResolver的java类

package com.wuxinzhe.springweb.component;
 
import org.springframework.lang.Nullable;
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, @Nullable HttpServletResponse httpServletResponse, @Nullable Locale locale) {
 
    }
 
}

②在SpringwebApplication.java注册到容器中

	@Bean
	public LocaleResolver localeResolver(){ // 方法名必须为localeResolver 或者 @Bean("localeResolver")
		return  new MylocaleResolver();
	}

③就可以通过http://localhost:8080/index.html?l=en_US和http://localhost:8080/index.html?l=zh_CN进行中英切换了。

开发期间模板引擎页面修改以后,如何生效
1.禁用模板引擎的缓存spring.thymeleaf.cache=false
2.页面修改完成以后ctrl+f9,重新编译

themeleaf抽取公用部分
fragment定义模板

<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

引入模板三种方式

<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>

渲染结果

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

①根据ID选择器引用模板

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

②根据fragment定义的模板引入模板

<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

向模板传递值
模板页面使用引用模板页面变量

<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 th:class="${activeUrl=='main'?'nav-link active':'nav-link'}" 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>

引用模板页面

<div th:replace="common/admin_template::#sidebar(activeUrl='main')"></div>

添加日期参数问题
日期格式化:SpringMVC将页面提交的日期数据需要转换为指定的类型
private Date birth;
默认为1990/11/24这种格式,当然可以在application.properties配置文件中指定日期格式,如

spring.mvc.date-format=yyyy-MM-dd

curd之更新

<form th:action="@{/emp}" method="POST">
	<!--
	发送put请求更新数据
	①springMVC中配置HiddenHttpMethodFilter(springboot中自动配置好了)
	②页面中创建一个post表单
	③创建一个input隐藏域
	-->
	<input type="hidden" value="put" name="_method" th:if="${emp!=null}"/>
	<input type="hidden" th:value="${emp.id}" name="id"  th:if="${emp!=null}"/>

如果高版本springboot无法发送一个put请求,则请开启以下内容
spring.mvc.hiddenmethod.filter.enabled=true

curd之删除

<button class="btn btn-sm btn-danger delBtm" th:attr="del_url=@{/emp/}+${emp.id}" onclick="delEmp(this)">删除</button>
<form method="post" id="deleteForm">
	<input type="hidden" value="delete" name="_method">
</form>
	<script>
		feather.replace()
 
		$('.delBtm').click(function(){
			$('#deleteForm').attr("action",$(this).attr('del_url')).submit()
		})
	</script>

———————————————————————————————————————————————————————————————————————————

错误处理&定制错误页面

springboot根据请求头来判断不同客户端,根据不同错误做出不同响应
Chrome浏览器
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
响应数据html页面
postman:
Accept: */*
响应数据:

{
    "timestamp": "2020-02-26T03:43:22.227+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/cur44"
}

①DefaultErrorAttributes
②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
			public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
				HttpStatus status = this.getStatus(request);
				if (status == HttpStatus.NO_CONTENT) {
					return new ResponseEntity(status);
				} else {
					Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
					return new ResponseEntity(body, status);
				}
			}

③ErrorPageCustomizer
④DefaultErrorViewResolver
一旦系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到error请求,就会被BasicErrorController处理
①响应页面;去哪个页面是由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;
    }
 
	    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;
    }

如何定制错误的页面
①有模板引擎的情况下:error/状态码.html(error文件夹下的 状态码.html),发生错误就会来到对应页面
我们可以使用4xx和5xx作为错误页面来匹配这种类型的所有错误页面,精确优先(优先寻找精确的 状态码.html)

页面能获取的信息
timestamp时间戳
status状态码
error错误提示
exception异常对象
message异常消息
errors JSR303数据校验错误的对象

②没有模板引擎就在静态资源文件夹下寻找(无法解析上边的变量)
③都找不到,就来到springboot默认错误处理页面
如何定制错误的json数据

页面无法获取错误变量,请在application.properties中配置server.error.include-exception=true
异常类UserNotExist.java

package com.wuxinzhe.springweb.exception;
 
public class UserNotExist extends RuntimeException{
    public UserNotExist(){
        super("用户不存在");
    }
}

application.properties

server.error.include-exception=true

控制器

    @RequestMapping("/hello")
    public String hello(Map<String,Object> map,@RequestParam("user") String UserName){
        if(UserName.equals("libai")){
            throw new UserNotExist();
        }
        map.put("hello","hello world!!!");
        map.put("users", Arrays.asList("<b>AAA</b>","李四","王五"));
        return "hello";
    }

5xx.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	[[${timestamp}]]<br>
	[[${status}]]<br>
	[[${error}]]<br>
	[[${exception}]]<br>
	[[${message}]]<br>
</body>
</html>

浏览器访问http://localhost:8080/hello?user=libai

Wed Feb 26 14:04:31 CST 2020
500
Internal Server Error
com.wuxinzhe.springweb.exception.UserNotExist
用户不存在

postman访问http://localhost:8080/hello?user=libai

{
    "timestamp": "2020-02-26T06:07:43.008+0000",
    "status": 500,
    "error": "Internal Server Error",
    "exception": "com.wuxinzhe.springweb.exception.UserNotExist",
    "message": "用户不存在",
    "path": "/hello"
}

将我们定制的数据携带
出现错误以后,会来到/error请求,会被BasicErrorController.class类处理,响应出去的出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController.class抽象类中规定的方法)
①完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
②页面上能用的数据,或者是json返回能用的数据,都是通过errorAttributes.getErrorAttributes得到;
DefaultErrorAttributes.getErrorAttributes

++++++++++++++++++++++++++++++++完整处理案例
UserNotExist.java

package com.wuxinzhe.springweb.exception;
 
public class UserNotExist extends RuntimeException{
    public UserNotExist(){
        super("用户不存在");
    }
}

MyExceptionHandler.java

package com.wuxinzhe.springweb.controller;
 
import com.wuxinzhe.springweb.exception.UserNotExist;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
 
 
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
 
@ControllerAdvice
public class MyExceptionHandler  {
 
//    1.浏览器客户端返回的都是json 没有自适应效果
//    @ResponseBody
//    @ExceptionHandler(UserNotExist.class)
//    public Map<String, Object> handerException(Exception e){
//        Map<String,Object> map = new HashMap<>();
//        map.put("code","user not exist");
//        map.put("message","用户不存在");
//        return map;
//    }
 
//    可以自适应不同端的请求  但是无法定制数据
// @ExceptionHandler(UserNotExist.class)
//    public String handerException(Exception e, HttpServletRequest request){
//        Map<String,Object> map = new HashMap<>();
//        map.put("code","user not exist");
//        map.put("message","用户不存在");
//        // 传入我们自己的错误状态码 4xx 5xx 否则就不会进入错误定制页面的解析流程
//        request.setAttribute("javax.servlet.error.status_code",500);
//        return "forward:/error";
//    }
 
 
    @ExceptionHandler(UserNotExist.class)
    public String handerException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
       map.put("code","user not exist");
       map.put("message","用户不存在");
       // 传入我们自己的错误状态码 4xx 5xx 否则就不会进入错误定制页面的解析流程
        request.setAttribute("javax.servlet.error.status_code",533);
        request.setAttribute("ext",map);
        return "forward:/error";
    }
 
}

MyErrorAttributes.java

package com.wuxinzhe.springweb.component;
 
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
 
import java.util.Map;
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    // 返回值map就是页面和json能获取的所有字段
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String,Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("customMsg","自定义内容");
        // 获取异常处理器携带的参数
        Map<String,Object> ext = (Map<String,Object>) webRequest.getAttribute("ext",0);
        map.put("ext",ext);
        return map;
    }
}

http://localhost:8080/hello?user=libai

{
    "timestamp": "2020-02-26T09:40:04.530+0000",
    "status": 533,
    "error": "Http Status 533",
    "message": "用户不存在",
    "path": "/hello",
    "customMsg": "自定义内容",
    "ext": {
        "code": "user not exist",
        "message": "用户不存在"
    }
}

嵌入式servlet容器配置修改
1.application.properties配置文件修改

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private Boolean useForwardHeaders;
    private String serverHeader;
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);
    private Duration connectionTimeout;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression = new Compression();
    @NestedConfigurationProperty
    private final Http2 http2 = new Http2();
    private final ServerProperties.Servlet servlet = new ServerProperties.Servlet();
    private final ServerProperties.Tomcat tomcat = new ServerProperties.Tomcat();
    private final ServerProperties.Jetty jetty = new ServerProperties.Jetty();
    private final ServerProperties.Netty netty = new ServerProperties.Netty();
    private final ServerProperties.Undertow undertow = new ServerProperties.Undertow();

2.通过TomcatWebServerFactoryCustomizer修改配置

    @Autowired
    private Environment environment;
    @Autowired
    ServerProperties serverProperties;
 
	@Bean
    public TomcatWebServerFactoryCustomizer tomcatWebServerCustomizer() {
        serverProperties.setPort(8118);
        System.out.println("测试服务器启动"+serverProperties.toString()+serverProperties.getPort());
        return new TomcatWebServerFactoryCustomizer(environment, serverProperties) {
        };
    }

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

MyServlet.java(定义一个Servlet)

package com.wuxinzhe.springweb.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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //resp.setCharacterEncoding("gbk");
        doPost(req,resp);
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servlet console");
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("自定义的servlet");
    }
}

MyServerConfig.java(ServletRegistrationBean注册Servlet)

@Configuration
public class MyServerConfig {
 
    @Bean
    public ServletRegistrationBean myservlet(){
        return new ServletRegistrationBean<HttpServlet>(new MyServlet(),"/myservlet");
    }
}

2.注册Filter
MyFilter.java类

package com.wuxinzhe.springweb.filter;
 
import javax.servlet.*;
import java.io.IOException;
 
public class MyFilter implements  Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter Test");
        filterChain.doFilter(servletRequest,servletResponse);
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
 
    @Override
    public void destroy() {
    }
}

MyServerConfig.java

@Configuration
public class MyServerConfig {
    @Autowired
    private Environment environment;
    @Autowired
    ServerProperties serverProperties;
 
    @Bean
    public FilterRegistrationBean filterRegistration(){
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new MyFilter());
        filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/myservlet","/hello"));
        return filterFilterRegistrationBean;
    }
}

3.注册Listener
MyListener.java

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项目销毁");
    }
}

加入容器中

    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean(){
        return new ServletListenerRegistrationBean<MyListener>(new MyListener());
    }

使用其他Servlet容器
引入web默认就是用tomcat做嵌入式servlet容器

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<!--<exclusions>-->
				<!--<exclusion>-->
					<!--<artifactId>spring-boot-starter-tomcat</artifactId>-->
					<!--<groupId>org.springframework.boot</groupId>-->
				<!--</exclusion>-->
			<!--</exclusions>-->
		</dependency>
 
切换为其他servlet容器
    // 排除tomcat依赖
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<artifactId>spring-boot-starter-tomcat</artifactId>
					<groupId>org.springframework.boot</groupId>
				</exclusion>
			</exclusions>
		</dependency>
    // 添加jetty依赖   ,也可以替换为 spring-boot-starter-undertow 
		<dependency>
			<artifactId>spring-boot-starter-jetty</artifactId>
			<groupId>org.springframework.boot</groupId>
		</dependency>

嵌入式servlet容器自动配置原理
根据不同依赖,注入不同servlet容器
EmbeddedWebServerFactoryCustomizerAutoConfiguration.java

package org.springframework.boot.autoconfigure.web.embedded;
 
import io.undertow.Undertow;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.xnio.SslClientAuthMode;
import reactor.netty.http.server.HttpServer;
 
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
    public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
    }
 
	//  Netty
    @Configuration
    @ConditionalOnClass({HttpServer.class})    // reactor.netty.http.server.HttpServer
    public static class NettyWebServerFactoryCustomizerConfiguration {
        public NettyWebServerFactoryCustomizerConfiguration() {
        }
 
        @Bean
        public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
 
	// Undertow
    @Configuration
    @ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
    public static class UndertowWebServerFactoryCustomizerConfiguration {
        public UndertowWebServerFactoryCustomizerConfiguration() {
        }
 
        @Bean
        public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
 
	// Jetty
    @Configuration
    @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
    public static class JettyWebServerFactoryCustomizerConfiguration {
        public JettyWebServerFactoryCustomizerConfiguration() {
        }
 
        @Bean
        public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new JettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
 
	// Tomcat
    @Configuration
    @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
    public static class TomcatWebServerFactoryCustomizerConfiguration {
        public TomcatWebServerFactoryCustomizerConfiguration() {
        }
 
        @Bean
        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
}

TomcatWebServerFactoryCustomizer.java

package org.springframework.boot.autoconfigure.web.embedded;
 
import java.time.Duration;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.ErrorReportValve;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Resource;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
 
public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
    private final Environment environment;
    private final ServerProperties serverProperties;
 
    public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
        this.environment = environment;
        this.serverProperties = serverProperties;
    }
 
    public int getOrder() {
        return 0;
    }
 
    public void customize(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties properties = this.serverProperties;
        Tomcat tomcatProperties = properties.getTomcat();
        PropertyMapper propertyMapper = PropertyMapper.get();
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds).as(Long::intValue).to(factory::setBackgroundProcessorDelay);
        this.customizeRemoteIpValve(factory);
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive).to((maxThreads) -> {
            this.customizeMaxThreads(factory, tomcatProperties.getMaxThreads());
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive).to((minSpareThreads) -> {
            this.customizeMinThreads(factory, minSpareThreads.intValue());
        });
        propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull().asInt(DataSize::toBytes).when(this::isPositive).to((maxHttpHeaderSize) -> {
            this.customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize.intValue());
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes).to((maxSwallowSize) -> {
            this.customizeMaxSwallowSize(factory, maxSwallowSize.intValue());
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when((maxHttpFormPostSize) -> {
            return maxHttpFormPostSize.intValue() != 0;
        }).to((maxHttpFormPostSize) -> {
            this.customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize.intValue());
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getAccesslog).when(Accesslog::isEnabled).to((enabled) -> {
            this.customizeAccessLog(factory);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
        properties.getClass();
        propertyMapper.from(properties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> {
            this.customizeConnectionTimeout(factory, connectionTimeout);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> {
            this.customizeConnectionTimeout(factory, connectionTimeout);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive).to((maxConnections) -> {
            this.customizeMaxConnections(factory, maxConnections.intValue());
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive).to((acceptCount) -> {
            this.customizeAcceptCount(factory, acceptCount.intValue());
        });
        this.customizeStaticResources(factory);
        this.customizeErrorReportValve(properties.getError(), factory);
    }
 
    private boolean isPositive(int value) {
        return value > 0;
    }
 
    private DataSize determineMaxHttpHeaderSize() {
        return this.serverProperties.getTomcat().getMaxHttpHeaderSize().toBytes() > 0L ? this.serverProperties.getTomcat().getMaxHttpHeaderSize() : this.serverProperties.getMaxHttpHeaderSize();
    }
 
    private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol)handler;
                protocol.setAcceptCount(acceptCount);
            }
 
        }});
    }
 
    private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, int maxConnections) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol)handler;
                protocol.setMaxConnections(maxConnections);
            }
 
        }});
    }
 
    private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol)handler;
                protocol.setConnectionTimeout((int)connectionTimeout.toMillis());
            }
 
        }});
    }
 
    private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {
        Tomcat tomcatProperties = this.serverProperties.getTomcat();
        String protocolHeader = tomcatProperties.getProtocolHeader();
        String remoteIpHeader = tomcatProperties.getRemoteIpHeader();
        if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader) || this.getOrDeduceUseForwardHeaders()) {
            RemoteIpValve valve = new RemoteIpValve();
            valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader : "X-Forwarded-Proto");
            if (StringUtils.hasLength(remoteIpHeader)) {
                valve.setRemoteIpHeader(remoteIpHeader);
            }
 
            valve.setInternalProxies(tomcatProperties.getInternalProxies());
            valve.setPortHeader(tomcatProperties.getPortHeader());
            valve.setProtocolHeaderHttpsValue(tomcatProperties.getProtocolHeaderHttpsValue());
            factory.addEngineValves(new Valve[]{valve});
        }
 
    }
 
    private boolean getOrDeduceUseForwardHeaders() {
        if (this.serverProperties.isUseForwardHeaders() != null) {
            return this.serverProperties.isUseForwardHeaders().booleanValue();
        } else {
            CloudPlatform platform = CloudPlatform.getActive(this.environment);
            return platform != null && platform.isUsingForwardHeaders();
        }
    }
 
    private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol protocol = (AbstractProtocol)handler;
                protocol.setMaxThreads(maxThreads);
            }
 
        }});
    }
 
    private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol protocol = (AbstractProtocol)handler;
                protocol.setMinSpareThreads(minSpareThreads);
            }
 
        }});
    }
 
    private void customizeMaxHttpHeaderSize(ConfigurableTomcatWebServerFactory factory, int maxHttpHeaderSize) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractHttp11Protocol) {
                AbstractHttp11Protocol protocol = (AbstractHttp11Protocol)handler;
                protocol.setMaxHttpHeaderSize(maxHttpHeaderSize);
            }
 
        }});
    }
 
    private void customizeMaxSwallowSize(ConfigurableTomcatWebServerFactory factory, int maxSwallowSize) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractHttp11Protocol) {
                AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol)handler;
                protocol.setMaxSwallowSize(maxSwallowSize);
            }
 
        }});
    }
 
    private void customizeMaxHttpFormPostSize(ConfigurableTomcatWebServerFactory factory, int maxHttpFormPostSize) {
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
            connector.setMaxPostSize(maxHttpFormPostSize);
        }});
    }
 
    private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) {
        Tomcat tomcatProperties = this.serverProperties.getTomcat();
        AccessLogValve valve = new AccessLogValve();
        valve.setPattern(tomcatProperties.getAccesslog().getPattern());
        valve.setDirectory(tomcatProperties.getAccesslog().getDirectory());
        valve.setPrefix(tomcatProperties.getAccesslog().getPrefix());
        valve.setSuffix(tomcatProperties.getAccesslog().getSuffix());
        valve.setRenameOnRotate(tomcatProperties.getAccesslog().isRenameOnRotate());
        valve.setFileDateFormat(tomcatProperties.getAccesslog().getFileDateFormat());
        valve.setRequestAttributesEnabled(tomcatProperties.getAccesslog().isRequestAttributesEnabled());
        valve.setRotatable(tomcatProperties.getAccesslog().isRotate());
        valve.setBuffered(tomcatProperties.getAccesslog().isBuffered());
        factory.addEngineValves(new Valve[]{valve});
    }
 
    private void customizeStaticResources(ConfigurableTomcatWebServerFactory factory) {
        Resource resource = this.serverProperties.getTomcat().getResource();
        factory.addContextCustomizers(new TomcatContextCustomizer[]{(context) -> {
            context.addLifecycleListener((event) -> {
                if (event.getType().equals("configure_start")) {
                    context.getResources().setCachingAllowed(resource.isAllowCaching());
                    if (resource.getCacheTtl() != null) {
                        long ttl = resource.getCacheTtl().toMillis();
                        context.getResources().setCacheTtl(ttl);
                    }
                }
 
            });
        }});
    }
 
    private void customizeErrorReportValve(ErrorProperties error, ConfigurableTomcatWebServerFactory factory) {
        if (error.getIncludeStacktrace() == IncludeStacktrace.NEVER) {
            factory.addContextCustomizers(new TomcatContextCustomizer[]{(context) -> {
                ErrorReportValve valve = new ErrorReportValve();
                valve.setShowServerInfo(false);
                valve.setShowReport(false);
                context.getParent().getPipeline().addValve(valve);
            }});
        }
 
    }
}

——————————————————————————————————————————————————————————

自动配置服务器工厂类

ServletWebServerFactoryAutoConfiguration.java

package org.springframework.boot.autoconfigure.web.servlet;
 
import javax.servlet.ServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedJetty;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedTomcat;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedUndertow;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
 
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
 
@Configuration
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
// 仅基于servlet的Web应用程序
@ConditionalOnWebApplication(    
    type = Type.SERVLET
)
// ServerProperties 配置中包括了常见的 server.port 等配置属性
@EnableConfigurationProperties({ServerProperties.class})
// 通过 @Import 导入嵌入式容器相关的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
    public ServletWebServerFactoryAutoConfiguration() {
    }
 
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new ServletWebServerFactoryCustomizer(serverProperties);
    }
 
    @Bean
    @ConditionalOnClass(
        name = {"org.apache.catalina.startup.Tomcat"}
    )
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }
 
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        private ConfigurableListableBeanFactory beanFactory;
 
        public BeanPostProcessorsRegistrar() {
        }
 
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
            }
 
        }
 
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (this.beanFactory != null) {
			    //WebServerFactoryCustomizerBeanPostProcessor:作用是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer,然后调用 WebServerFactoryCustomizer 中的 customize 方法来处理WebServerFactory。
                this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
                this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
            }
        }
 
        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
 
        }
    }
}
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
    private ListableBeanFactory beanFactory;
    private List<WebServerFactoryCustomizer<?>> customizers;
 
    public WebServerFactoryCustomizerBeanPostProcessor() {
    }
 
    public void setBeanFactory(BeanFactory beanFactory) {
        Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, "WebServerCustomizerBeanPostProcessor can only be used with a ListableBeanFactory");
        this.beanFactory = (ListableBeanFactory)beanFactory;
    }
 
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }
 
        return bean;
    }
 
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
 
	// // 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
            customizer.customize(webServerFactory);
        });
    }
 
    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
        if (this.customizers == null) {
            this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
 
        return this.customizers;
    }
 
    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
        return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }
}

上面这段代码中,注册了两个 bean,一个 WebServerFactoryCustomizerBeanPostProcessor,一个 errorPageRegistrarBeanPostProcessor;这两个都实现类 BeanPostProcessor 接口,属于 bean 的后置处理器,作用是在 bean 初始化前后加一些自己的逻辑处理。

WebServerFactoryCustomizerBeanPostProcessor:作用是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer ,然后调用 WebServerFactoryCustomizer 中的 customize 方法来 处理 WebServerFactory。

errorPageRegistrarBeanPostProcessor:和上面的作用差不多,不过这个是处理 ErrorPageRegistrar 的。

下面简单看下 WebServerFactoryCustomizerBeanPostProcessor 中的代码:

public class WebServerFactoryCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
// 省略部分代码
 
// 在 postProcessBeforeInitialization 方法中,如果当前 bean 是 WebServerFactory,则进行
// 一些后置处理
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	if (bean instanceof WebServerFactory) {
		postProcessBeforeInitialization((WebServerFactory) bean);
	}	
	return bean;
}
// 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
	LambdaSafe
	.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
	webServerFactory)
	.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
	.invoke((customizer) -> customizer.customize(webServerFactory));
	}
 
}

自动配置类中注册的两个 Customizer Bean,这两个 Customizer 实际上就是去处理一些配置值,然后绑定到 各自的工厂类的。

WebServerFactoryCustomizer

将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 对象实例上。
ServletWebServerFactoryCustomizer.java

@Override
public void customize(ConfigurableServletWebServerFactory factory) {
	PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
	// 端口
	map.from(this.serverProperties::getPort).to(factory::setPort);
	// address
	map.from(this.serverProperties::getAddress).to(factory::setAddress);
	// contextPath
	map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
	// displayName
	map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
	// session 配置
	map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
	// ssl
	map.from(this.serverProperties::getSsl).to(factory::setSsl);
	// jsp
	map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
	// 压缩配置策略实现
	map.from(this.serverProperties::getCompression).to(factory::setCompression);
	// http2
	map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
	// serverHeader
	map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
	// contextParameters
	map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
}

TomcatServletWebServerFactoryCustomizer相比于上面那个,这个customizer主要处理Tomcat相关的配置值

    public void customize(TomcatServletWebServerFactory factory) {
	    // // 拿到 tomcat 相关的配置
        Tomcat tomcatProperties = this.serverProperties.getTomcat();
        if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
            factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
        }
 
        if (tomcatProperties.getRedirectContextRoot() != null) {
            this.customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot().booleanValue());
        }
 
        if (tomcatProperties.getUseRelativeRedirects() != null) {
            this.customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects().booleanValue());
        }
 
    }

——————————————————————————————————————————————————————————

	/**
	ServletWebServerFactoryConfiguration是一个针对ServletWebServerFactory进行配置的配置类。它通过检测应用classpath存在的类,从而判断当前应用要使用哪个Servlet容器:Tomcat,Jetty还是Undertow。检测出来之后,定义相应的Servlet Web服务器工厂组件bean 
	**/
ServletWebServerFactoryConfiguration.java
package org.springframework.boot.autoconfigure.web.servlet;
 
import io.undertow.Undertow;
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.xnio.SslClientAuthMode;
 
@Configuration
class ServletWebServerFactoryConfiguration {
    ServletWebServerFactoryConfiguration() {
    }
 
    @Configuration
    @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedUndertow {
        EmbeddedUndertow() {
        }
 
        @Bean
        public UndertowServletWebServerFactory undertowServletWebServerFactory() {
            return new UndertowServletWebServerFactory();
        }
    }
 
    @Configuration
    @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedJetty {
        EmbeddedJetty() {
        }
 
        @Bean
        public JettyServletWebServerFactory JettyServletWebServerFactory() {
            return new JettyServletWebServerFactory();
        }
    }
 
    @Configuration
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedTomcat {
        EmbeddedTomcat() {
        }
 
        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }
    }
}

—————————————————————————————————————————————————————————————————
嵌入式Servlet启动原理
①主程序调用run方法

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

② context = this.createApplicationContext();

Springboot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】,如果是web应用创建AnnotationConfigServletWebServerApplicationContext,如下:
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }

③刷新IOC容器this.refreshContext(context);

AbstractApplicationContext.java

		public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);
 
            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
 
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }
 
        }
    }

④onRefresh();
webioc容器AnnotationConfigServletWebServerApplicationContext的父类 ServletWebServerApplicationContext.class重写了onRefresh方法

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

⑤this.createWebServer(); 创建嵌入式web容器

	    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
		    // 从ioc容器中获取ServletWebServerFactory
            ServletWebServerFactory factory = this.getWebServerFactory();
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }
 
        this.initPropertySources();
    }
 
	    public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();
 
        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }
 
        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }

⑥获取嵌入式的Servlet容器工厂 getWebServerFactory

	    protected ServletWebServerFactory getWebServerFactory() {
        String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
        } else if (beanNames.length > 1) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
        } else {
            return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
        }
    }

从ioc容器中获取ServletWebServerFactory,就会惊动后置处理器,后置处理器就会获取所有定制器来先定制Servlet的相关配置。

⑦嵌入式的Servlet容器创建对象并启动Servlet容器

使用外部Servlet容器&JSP支持
嵌入式Servlet容器:应用打成Jar包,默认不支持JSP
外部Servlet容器:外部安装的Tomcat容器->应用war包的方式打包,步骤如下

①new project=》springinitializr=》选择packaging为war

②Project Structure=》Modules=》Web=》Web Resources Directories=》创建webapp文件夹 E:\ideawordspace\wartest\src\main\webapp
Deployment Descriptors 添加web.xml文件 E:\ideawordspace\wartest\src\main\webapp\WEB-INF\web.xml

③edit configuration配置web容器


④application.properties配置
spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp

创建一个控制器,映射url http://localhost:8081/helloworld

package com.springboot.wartest.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
public class HelloController {
    @RequestMapping("helloworld")
    public String helloWorld(){
        return "helloworld";
    }
}

完整项目路径

外部Servlet容器启动SpringBoot应用原理
ServletInitializer继承SpringBootServletInitializer重写configure方法
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器
servlet3.0
规则:
1.服务器启动(web应用启动)会创建当前web应用里的每一个jar包里面ServletContainerInitializer的实例
2.ServletContainerInitializer的实现放在jar包META-INF/services文件夹下,文件夹下有一个文件名为javax.servlet.ServletContainerInitializer,内容就是ServletContainerInitializer的实现类的全类名。
3.还可以使用@HandleTypes注解,在我们应用启动的时候加载我们感兴趣的类。

流程:
1.启动tomcat
2.org\springframework\spring-web\5.2.4.RELEASE\spring-web-5.2.4.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer
3.ServletContainerInitializer中将@HandlesTypes({WebApplicationInitializer.class})标注的所有这个类型的类都传入到onStartup方法中的@Nullable Set> webAppInitializerClasses参数里,为这些WebApplicationInitializer类型的类创建实例
4.每一个WebApplicationInitializer都调用自己的onStartup方法
5.相当于SpringBootServletInitializer类会被创建对象,并执行onStartup方法
6.SpringBootServletInitializer示例执行onStartup的时候会createRootApplicationContext创建容器

	    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
		// 1.创建spring应用的构造器 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);
		// 2.调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入进来
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext, null)});
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }
 
        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
 
        return this.run(application);
    }


7. spring的应用就启动并且创建IOC容器

application.yml配置数据源

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://111.230.6.126:3307/test
    driver-class-name: com.mysql.cj.jdbc.Driver

单元测试

	@Autowired
	private DataSource dataSource;
 
	@Test
	void datasourceTest() throws SQLException {
		System.out.println(dataSource.getClass());
		Connection connection = dataSource.getConnection();
		System.out.println(connection);
		connection.close();
	}

输出如下:

class com.zaxxer.hikari.HikariDataSource
HikariProxyConnection@1789376127 wrapping com.mysql.cj.jdbc.ConnectionImpl@6bcc3f27

数据源自动配置原理
springframework\boot\spring-boot-autoconfigure\2.2.5.RELEASE\spring-boot-autoconfigure-2.2.5.RELEASE.jar!\org\springframework\boot\autoconfigure\jdbc\DataSourceConfiguration.class

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({HikariDataSource.class})
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "com.zaxxer.hikari.HikariDataSource",
        matchIfMissing = true
    )
    static class Hikari {
        Hikari() {
        }
 
        @Bean
        @ConfigurationProperties(
            prefix = "spring.datasource.hikari"
        )
        HikariDataSource dataSource(DataSourceProperties properties) {
            HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setPoolName(properties.getName());
            }
 
            return dataSource;
        }
    }

自定义数据源

	@Configuration(
      proxyBeanMethods = false
    )
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }
 
        @Bean
        DataSource dataSource(DataSourceProperties properties) {
		    // 使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
            return properties.initializeDataSourceBuilder().build();
        }
    }

org\springframework\boot\autoconfigure\jdbc\DataSourceInitializer.class
createSchema建表
runScripts 执行sql语句

	    private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
        if (resources != null) {
            return this.getResources(propertyName, resources, true);
        } else {
            String platform = this.properties.getPlatform();
            List<String> fallbackResources = new ArrayList();
            fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
            fallbackResources.add("classpath*:" + fallback + ".sql");
            return this.getResources(propertyName, fallbackResources, false);
        }
    }

schema
默认sql文件名schema.sql或者schema-all.sql,也可以通过在yml,类似如下,自定义多个文件
schema:
-classpath:xxx.sql

JdbcTemplate的简单使用

	public class JdbcController {
 
    @Autowired
    JdbcTemplate jdbcTemplate;
    @ResponseBody
    @GetMapping("/query")
    public Map<String,Object> getEmp(){
        List<Map<String,Object>> lists = jdbcTemplate.queryForList("SELECT * FROM department");
        return  lists.get(0);
    }
}

整合Druid和配置数据源监控
①引入依赖

		<!--引入druid-->
		<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.20</version>
		</dependency>

②application.yml配置数据源类型type

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://111.230.6.126:3307/test
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    # Druid 数据源其他配置
    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

③Configuration 包中编写 druid 监控类

package com.springbootjdbctest.demo.config;
 
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import javax.servlet.Filter;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
 
@Configuration
public class DruidConfig {
 
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid(){
        return new DruidDataSource();
    }
 
    // 配置Druid的监控
    // 1.配置一个管理后台的Servlet
    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        // 可配的属性都在 StatViewServlet 和其父类下
        initParams.put("loginUsername", "admin-druid");
        initParams.put("loginPassword", "111111");
        servletRegistrationBean.setInitParameters(initParams);
        return servletRegistrationBean;
    }
 
    // 2.配置一个监控的Filter
    @Bean
    public FilterRegistrationBean druidWebStatFilter() {
        FilterRegistrationBean<WebStatFilter> webStatFilterFilterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");
        webStatFilterFilterRegistrationBean.setInitParameters(initParams);
        webStatFilterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
        return webStatFilterFilterRegistrationBean;
    }
}

通过 http://127.0.0.1:8080/druid/login.html 即可查看Druid监控数据

遇到问题:

***************************
APPLICATION FAILED TO START
***************************
 
Description:
 
Failed to bind properties under 'spring.datasource' to javax.sql.DataSource:
 
    Property: spring.datasource.filters
    Value: stat,wall,log4j
    Origin: class path resource [application.yml]:20:14
    Reason: org.apache.log4j.Logger
 
Action:
 
Update your application's configuration

解决方案,引入log4j依赖

 
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
		<dependency>
		    <groupId>log4j</groupId>
		    <artifactId>log4j</artifactId>
		    <version>1.2.17</version>
		</dependency>

参考网址:
https://blog.csdn.net/xingkongtianma01/article/details/81624313

整合Mybatis基础设置

SpringInitialer
选中 Web Mysql JDBC MyBatis模块
引入依赖

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

Mybatis注解版

DepartmentMapper.java

package com.springboot.mybatis.mapper;
 
import com.springboot.mybatis.bean.Department;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
 
@Mapper
public interface DepartmentMapper {
 
    @Select("SELECT * FROM department WHERE id=#{id}")
    public Department getDepartmentById(Integer id);
 
    @Options(useGeneratedKeys=true,keyProperty = "id")
    @Insert("INSERT INTO department(departmentName) VALUES (#{departmentName})")
    public int insertDepartment(Department department);
 
}

开启字段下划线和属性驼峰映射

package com.springboot.mybatis.config;
 
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class BatisConfig {
 
    @Bean
    public ConfigurationCustomizer configurationCustomizer(){
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                //  字段属性和javabean开启驼峰映射
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

批量扫描Mapper文件

@MapperScan(basePackages="com.springboot.mybatis.mapper")
@SpringBootApplication
public class MybatisApplication {
 
	public static void main(String[] args) {
		SpringApplication.run(MybatisApplication.class, args);
	}
 
}

测试

package com.springboot.mybatis.controller;
 
import com.springboot.mybatis.bean.Department;
import com.springboot.mybatis.mapper.DepartmentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
public class DepartmentController {
 
    @Autowired
    DepartmentMapper departmentMapper;
 
    @ResponseBody
    @GetMapping("/department")
    public Department department(Department department){
        departmentMapper.insertDepartment(department);
        return department;
    }
 
    @ResponseBody
    @GetMapping("/department/{id}")
    public Department department(@PathVariable("id") Integer id){
        Department department = departmentMapper.getDepartmentById(id);
        return department;
    }
}

MyBatis配置版
application.yml配置mybatis配置文件和Sql映射文件位置

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: mybatis/mapper/*.xml

src\main\resources\mybatis\mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- javabean属性名驼峰str1Str2 自动对应    数据表字段 str1_str2 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

com\springboot\mybatis\mapper\EmployeeMapper.java

package com.springboot.mybatis.mapper;
 
import com.springboot.mybatis.bean.Employee;
import org.apache.ibatis.annotations.Mapper;
 
@Mapper
public interface EmployeeMapper {
 
    public Employee getEmpById(Integer id);
 
    public int insertEmp(Employee employee);
 
}

src\main\resources\mybatis\mapper\EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.mybatis.mapper.EmployeeMapper">
 
    <select id="getEmpById" resultType="com.springboot.mybatis.bean.Employee">
        select * from employee where id = #{id}
    </select>
 
    <insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">
       INSERT INTO employee(lastName,email,gender,d_id) VALUES (#{lastName},#{email},#{gender},#{dId})
    </insert>
 
</mapper>

SpringBoot整合JPA

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷。可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

1.引入spring-boot-starter-data-jpa
2.配置文件打印sql语句
3.创建entity标注JPA注解
4.创建Repository接口继承JpaRepository
5.测试方法

spring Initializer => Web、JPA Mysql JDBC模块

报错:java.sql.SQLException: The server time zone value ‘�й���׼ʱ��’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the ‘serverTimezone’ configuration property) to use a more specifc time zone value if you want to utilize time zone support.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.19.jar:8.0.19]
解决方案:https://blog.csdn.net/qq_43371004/article/details/98385445第二种方案

报错:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.springboot.springdatajpa.entity.User$HibernateProxy$5KWlGNm8[“hibernateLazyInitializer”])
解决方案:https://blog.csdn.net/liu_yulong/article/details/84594771

1.引入依赖

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

2.配置文件application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/tx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      #更新或者创建数据表结构
      ddl-auto: update
    show-sql: true

3.实体类src\main\java\com\springboot\springdatajpa\entity\User.java

package com.springboot.springdatajpa.entity;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
import javax.persistence.*;
 
@JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler"})
@Table(name="USER")
@Entity
public class User {
    private Integer id;
    private String lastName;
    private String email;
 
    @GeneratedValue
    @Id
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    @Column(name="last_name")
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

4.继承自JpaRepository接口类

package com.springboot.springdatajpa.repository;
 
import com.springboot.springdatajpa.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
 
public interface UserRepository extends JpaRepository<User,Integer>{
 
 
}

5.测试方法

@Controller
public class UserController {
    @Autowired
    UserRepository userRepository;
 
    @ResponseBody
    @GetMapping("/user/{id}")
    public User getUserById(@PathVariable("id") Integer id){
        User user = userRepository.getOne(1);
        return user;
    }
}

SpringBoot缓存
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对。
每一个存储在Cache中的条目有一个定义的有效期,即Expiry Duration。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

SpringInitializer选择Cache、Web、Mysql 和 MyBatis模块

引入缓存依赖

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

缓存工作原理,@Cacheable运行流程

1.自动配置类
CacheAutoConfiguration.class
2.缓存配置类
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration(默认生效的配置类)
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
3.给容器中注册一个CacheManager:ConcurrentMapCacheManager
4.可以获取和创建ConcurrentMapCache类型的缓存组件,它的作用是将数据保存在ConcurrentMap中
运行流程:
1.方法运行之前,先去查询Cache缓存组件,按照cacheNames 指定的名字获取,获取缓存,如果没有,会自动创建出来
2.去Cache中查找缓存的内容,使用一个key,默认就是方法和参数,key是按照某种策略生成的,默认是使用SimpleKeyGenerator生成key
SimpleKeyGenerator生成key的默认策略:如果没有参数,则 new SimpleKey(new Object[0]);如果只有一个参数,则key=参数的值;如果有多个参数,new SimpleKey(params)。
3.没有查到缓存,就调用目标方法,并且将目标方法缓存的结果放进缓存中

@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用就可以直接使用缓存中的数据

核心:
1.使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2.key使用keyGenerator生成的,默认是SimpleKeyGenerator

RedisTemplate&序列化机制

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

使用redis做缓存
application.properties

spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost/yongda?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#控制台输出sql日志
logging.level.com.springboot.springcache.mapper=debug
spring.redis.host=####
spring.redis.port=####
spring.redis.password=####

SpringcacheApplication

package com.springboot.springcache;
 
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
 
/**
 * 快速体验缓存
 * 步骤:
 *   1.开启基于注解的缓存 @EnableCaching
 *   2.标注缓存注解
 *       @Cacheable
 *       @CachePut
 *       @CacheEvict
 *       整合redis缓存
 *       1.安装redis服务器
 *       2.引入redis的starter
 *       3.配置redis
 *       4.测试缓存
 *           原理 CacheManager===Cache缓存组件来实际给缓存中存取数据
 *           1.引入redis的starter,容器中存储的是RedisCacheManager
 *           2.RedisCacheManager帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据
 *           3.默认保存数据 k-v 都是Object,利用序列化保存,如何保存为json
 *             ①引入了redis的starter,cacheManager变为RedisCacheManager
 *             ②默认创建的 RedisCacheManager 操作redis 的时候,使用的是RedisTemplate<Object, Object>
 *             ③RedisTemplate<Object, Object>是默认使用jdk的序列化机制
 *           4.自定义CacheManager
 *             springboot缓存默认的序列化是jdk提供的 Serializable 方式
 */
@MapperScan(basePackages = {"com.springboot.springcache.mapper"})
@SpringBootApplication
@EnableCaching
public class SpringcacheApplication {
 
	public static void main(String[] args) {
		SpringApplication.run(SpringcacheApplication.class, args);
	}
 
}

src\main\java\com\springboot\springcache\config\BatisConfig.java

package com.springboot.springcache.config;
 
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class BatisConfig {
 
    @Bean
    public ConfigurationCustomizer configurationCustomizer(){
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                //  字段属性和javabean开启驼峰映射
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

src\main\java\com\springboot\springcache\config\MyRedisConfig.java

package com.springboot.springcache.config;
 
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.omg.CORBA.OBJECT_NOT_EXIST;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
import java.time.Duration;
 
@Configuration
public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object,Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> ser = new Jackson2JsonRedisSerializer<Object>(Object.class);
        template.setDefaultSerializer(ser);
        return template;
    }
 
 
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
 
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
 
        // 配置序列化(解决乱码的问题),过期时间30秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(30))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
 
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

src\main\java\com\springboot\springcache\mapper\EmployeeMapper.java

package com.springboot.springcache.mapper;
 
import com.springboot.springcache.bean.Employee;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
 
public interface EmployeeMapper {
 
    @Select("SELECT * FROM employee WHERE id = #{id}")
    public Employee getEmpById(Integer id);
 
    @Update("UPDATE employee SET last_name=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
    public void updateEmp(Employee employee);
 
    @Select("SELECT * FROM employee WHERE last_name=#{lastName}")
    public Employee getEmpByLastName(String lastName);
 
}

src\main\java\com\springboot\springcache\service\EmployeeService.java

package com.springboot.springcache.service;
 
import com.springboot.springcache.bean.Employee;
import com.springboot.springcache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
 
// 缓存的公共配置,其他的地方就可以省略
@CacheConfig(cacheNames="emp")
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;
 
    /**
     *  @Cacheable 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中取,不用调用方法
     *  CacheManager管理多个Cache组件的,对缓存的真正CURD操作在Cache组件中,每一个缓存组件有自己的唯一一个名字
     *  几个属性:
     *     cacheNames/value 指定缓存的名字,将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
     *     key:缓存数据使用的key,可以使用它来指定,默认是使用 方法 + 参数 的值
     *     cacheManager缓存管理器 或者 指定 cacheResolver缓存解析器  二选一
     *     condition:指定符合条件的情况下,才缓存
     *     unless 否定缓存 ;当unless指定的条件为true,方法的返回值就不会被缓存,可以获得结果进行判断
     *     sync:
     */
    @Cacheable(condition = "#id >2")
    public Employee getEmpById(Integer id){
        return employeeMapper.getEmpById(id);
    }
 
    /**
     * @CachePut 既调用方法,又更新缓存数据
     * 修改了数据库的某个数据,同时更新缓存,使用CachePut注解,方法每次都会执行
     * 运行时机
     *  1.先调用目标方法,然后将目标方法的结果缓存起来
     *
     *  测试步骤
     *
     *      步骤1 查询3号员工 {"id":3,"lastName":"44444","email":"2222@","gender":1,"dId":1}
     *      步骤2 更新3号员工 {"id":3,"lastName":"hahaha","email":"444@qq.com","gender":1,"dId":1}
     *      步骤3 查询3号员工 继续从缓存中取出数据 因为默认情况下,key值不同,
     */
    @CachePut(value={"emp"},key="#employee.id")
    public Employee updateEmp(Employee employee){
         employeeMapper.updateEmp(employee);
         return employee;
    }
 
    /**
     * 清除缓存
     * allEntries 清除所有缓存,默认为false
     * beforeInvocation 缓存清除操作是否在方法之前执行 默认为false(在方法之后执行)
     */
    @CacheEvict(value={"emp"},/*key="#id",*/allEntries=true)
    public void deleteEmp(Integer id){
        System.out.println("delete"+id);
    }
 
 
    @Caching(
            cacheable={
                    @Cacheable(value="emp",key="#lastName")
            },
           put={
                   @CachePut(value="emp",key="#result.id"),
                   @CachePut(value="emp",key="#result.email")
            }
    )
    public Employee getEmpByLastName(String lastName){
        return employeeMapper.getEmpByLastName(lastName);
    }
}

src\main\java\com\springboot\springcache\controller\EmployeeController.java

package com.springboot.springcache.controller;
 
import com.springboot.springcache.bean.Employee;
import com.springboot.springcache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
 
 
@RestController
public class EmployeeController {
 
    @Autowired
    EmployeeService employeeService;
 
    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id){
        Employee employee = employeeService.getEmpById(id);
        return employee;
    }
 
 
    @GetMapping("/emp")
    public Employee updateEmp(Employee employee) {
        employeeService.updateEmp(employee);
        return employee;
    }
 
    @GetMapping("getEmpByLastName/{lastName}")
    public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
        return employeeService.getEmpByLastName(lastName);
    }
 
}

RabbitMQ
透彻rabbitmq https://zhuanlan.zhihu.com/p/63700605

docker pull rabbitmq:3.6-management
 
docker run -p 5672:5672 -p 15672:15672 --name myrabiitmq rabbitmq
 
[root@VM_0_8_centos ~]# docker exec -it myrabiitmq sh
# rabbitmq-plugins enable rabbitmq_management
Enabling plugins on node rabbit@81a526814fd2:
rabbitmq_management
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@81a526814fd2...
The following plugins have been enabled:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch

默认账号密码guest

无法访问rabbitmq管理后台问题 https://www.cnblogs.com/yuebo/p/11732769.html

application.properties文件

spring.rabbitmq.host=111.230.6.126
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672

RabbitmqtestApplication入口类

package com.springboot.rabbitmqtest;
 
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 *  1. RabbitAutoConfiguration
 *  2. 连接工厂 ConnectionFactory
 *  3. RabbitProperties封装了RabbitMq的配置
 *  4. RabbitTemplate 操作Rabbitmq,给Rabbitmq发送和接收消息
 *  5. AmqpAdmin 系统功能管理功能组件
 */
@EnableRabbit
@SpringBootApplication
public class RabbitmqtestApplication {
 
	public static void main(String[] args) {
		SpringApplication.run(RabbitmqtestApplication.class, args);
	}
 
}

MyAMQPConfig json存取消息

package com.springboot.rabbitmqtest.config;
 
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class MyAMQPConfig {
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

BookRabbitService监听消息队列

package com.springboot.rabbitmqtest.service;
 
import com.springboot.rabbitmqtest.bean.Book;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
 
@Service
public class BookRabbitService {
    @RabbitListener(queues = "atguigu")
    public void receive(Book book){
        System.out.println("收到一条消息"+book);
    }
}

单元测试

package com.springboot.rabbitmqtest;
 
import com.springboot.rabbitmqtest.bean.Book;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import java.util.HashMap;
import java.util.Map;
 
@SpringBootTest
class RabbitmqtestApplicationTests {
 
	@Autowired
	RabbitTemplate rabbitTemplate;
 
	@Autowired
	AmqpAdmin amqpAdmin;
 
	@Test
	void contextLoads() {
		// Message需要自己构造一个,定义消息体内容和消息头
       // rabbitTemplate.send( exchange ,rootKey , Message );
 
		//Object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq
		// rabbitTemplate.convertAndSend( exchange ,rootKey , object );
		// 默认序列化会产生乱码
		Map<String,Object> map = new HashMap<>();
		map.put("msg","这是第一条消息");
		map.put("param","paraminfo");
		rabbitTemplate.convertAndSend("exchange.direct","atguigu",map);
	}
 
	@Test
	void sendBook(){
		rabbitTemplate.convertAndSend("exchange.direct","atguigu",new Book(1,"西游记"));
	}
 
	@Test
	 void receiveMsg(){
		Object object = rabbitTemplate.receiveAndConvert("atguigu");
		//System.out.println(object.getClass());
		System.out.println(object);
	}
 
	@Test
	void amqpAdminTest(){
	 	// 创建队列
//		Queue amqpAdminQuee = new Queue("amqpAdminQuee");
//		amqpAdmin.declareQueue(amqpAdminQuee);
 
		// 创建交换器
		//DirectExchange amqpexhange = new DirectExchange("amqpexhange");
		//amqpAdmin.declareExchange(amqpexhange);
 
		amqpAdmin.declareBinding(new Binding("amqpAdminQuee", Binding.DestinationType.QUEUE,"amqpexhange","amqpkey",null));
 
	}
 
}

SpringBoot使用Elasticsearch

PUT /megacorp/employee/1
 
raw    JSON
 
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}
 
{
    "_index": "megacorp",
    "_type": "employee",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "created": true
}
GET /megacorp/employee/1
{
    "_index": "megacorp",
    "_type": "employee",
    "_id": "1",
    "_version": 1,
    "found": true,
    "_source": {
        "first_name": "John",
        "last_name": "Smith",
        "age": 25,
        "about": "I love to go rock climbing",
        "interests": [
            "sports",
            "music"
        ]
    }
}

HEAD /megacorp/employee/5
发出一个HEAD请求,如果存在这个数据,则响应200,如果不存在这个数据,则响应404

DELETE /megacorp/employee/4

GET /megacorp/employee/_search
{
    "found": true,
    "_index": "megacorp",
    "_type": "employee",
    "_id": "4",
    "_version": 2,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    }
}
 
{
    "took": 26,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1.0,
        "hits": [
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "2",
                "_score": 1.0,
                "_source": {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "age": 32,
                    "about": "I like to collect rock albums",
                    "interests": [
                        "music"
                    ]
                }
            },
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "1",
                "_score": 1.0,
                "_source": {
                    "first_name": "John",
                    "last_name": "Smith",
                    "age": 25,
                    "about": "I love to go rock climbing",
                    "interests": [
                        "sports",
                        "music"
                    ]
                }
            },
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "3",
                "_score": 1.0,
                "_source": {
                    "first_name": "Douglas",
                    "last_name": "Fir",
                    "age": 35,
                    "about": "I like to build cabinets",
                    "interests": [
                        "forestry"
                    ]
                }
            }
        ]
    }
}
GET /megacorp/employee/_search?q=last_name:Smith
 
{
    "took": 11,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "2",
                "_score": 0.2876821,
                "_source": {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "age": 32,
                    "about": "I like to collect rock albums",
                    "interests": [
                        "music"
                    ]
                }
            },
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "1",
                "_score": 0.2876821,
                "_source": {
                    "first_name": "John",
                    "last_name": "Smith",
                    "age": 25,
                    "about": "I love to go rock climbing",
                    "interests": [
                        "sports",
                        "music"
                    ]
                }
            }
        ]
    }
}
GET /megacorp/employee/_search
JSON数据
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}
 
{
    "took": 24,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "2",
                "_score": 0.2876821,
                "_source": {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "age": 32,
                    "about": "I like to collect rock albums",
                    "interests": [
                        "music"
                    ]
                }
            },
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "1",
                "_score": 0.2876821,
                "_source": {
                    "first_name": "John",
                    "last_name": "Smith",
                    "age": 25,
                    "about": "I love to go rock climbing",
                    "interests": [
                        "sports",
                        "music"
                    ]
                }
            }
        ]
    }
}

复杂搜索

GET /megacorp/employee/_search
json 数据
{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}
响应
{
    "took": 57,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "2",
                "_score": 0.2876821,
                "_source": {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "age": 32,
                    "about": "I like to collect rock albums",
                    "interests": [
                        "music"
                    ]
                }
            }
        ]
    }
}
全文检索 /megacorp/employee/_search
json数据
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}
 
响应:
{
    "took": 15,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 0.53484553,
        "hits": [
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "1",
                "_score": 0.53484553,
                "_source": {
                    "first_name": "John",
                    "last_name": "Smith",
                    "age": 25,
                    "about": "I love to go rock climbing",
                    "interests": [
                        "sports",
                        "music"
                    ]
                }
            },
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "2",
                "_score": 0.26742277,
                "_source": {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "age": 32,
                    "about": "I like to collect rock albums",
                    "interests": [
                        "music"
                    ]
                }
            }
        ]
    }
}
短语搜索
GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}
 
返回结果
{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         }
      ]
   }
}
高亮搜索 GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}
 
{
   ...
   "hits": {
      "total":      1,
      "max_score":  0.23013961,
      "hits": [
         {
            ...
            "_score":         0.23013961,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            },
            "highlight": {
               "about": [
                  "I love to go <em>rock</em> <em>climbing</em>" 
               ]
            }
         }
      ]
   }
}

Springboot整合springdata-elasticsearch操作Elasticsearch

springboot版本

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

添加spring-boot-starter-data-elasticsearch依赖

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

ElasticsearchApplication入口类

	/**
	 * 2. Springdata Elasticsearch
	 *       	1.ElasticSearchTemplate
	 * 			2.Client节点信息 clusterNodes、clusterName
	 */
	public static void main(String[] args) {
		SpringApplication.run(ElasticsearchApplication.class, args);
	}

Book.java

package com.springboot.elasticsearch.bean;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
 
@Document(indexName = "bookindex",type = "bookindex")
public class Book{
    @Id
    private Integer id;
    private String bookName;
    private String author;
 
    public Book() {
    }
 
    public Book(Integer id, String bookName, String author) {
        this.id = id;
        this.bookName = bookName;
        this.author = author;
    }
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    public String getBookName() {
        return bookName;
    }
 
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
 
    public String getAuthor() {
        return author;
    }
 
    public void setAuthor(String author) {
        this.author = author;
    }
 
    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", bookName='" + bookName + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}

BookRespository.java

package com.springboot.elasticsearch.respository;
 
import com.springboot.elasticsearch.bean.Book;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
 
import java.util.List;
 
public interface BookRespository  extends  ElasticsearchRepository<Book, Integer> {
 
    public List<Book> findByBookNameLike(String bookName);
 
}

测试类

package com.springboot.elasticsearch;
 
import com.springboot.elasticsearch.bean.Book;
import com.springboot.elasticsearch.respository.BookRespository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
// RunWith(SpringRunner.class) 让测试在Spring容器环境下执行。如测试类中无此注解,将导致service,dao等自动注入失败
@RunWith(SpringRunner.class)
@SpringBootTest
public class ElasticsearchApplicationTests {
 
	@Autowired
	private BookRespository bookRespository;
 
	@Test
	public void contextLoads() {
		Book book = new Book();
		book.setId(2);
		book.setAuthor("吴承恩");
		book.setBookName("西游记");
	//	System.out.println(book);
		bookRespository.index(book);
		System.out.println(bookRespository.findByBookNameLike("游"));
	}
}
failed to load elasticsearch nodes : org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{LEaUN9xGR6mUP5NQcUV-jw}{111.230.6.126}{111.230.6.126:9300}]

springdata elastisearch 和 elastisearch版本适配关系
https://docs.spring.io/spring-data/elasticsearch/docs/3.2.0.RC3/reference/html/#preface.versions

docker运行elasticsearch镜像

docker run -e ES_JAVA_OPTS="-Xms250m -Xmx250m" -d -p 9201:9200 -p 9301:9300 --name ES02 elasticsearch:6.8.1

indexName值必须小写

@Document(indexName = "bookindex",type = "book")

查看elasticsearch过往版本
https://www.elastic.co/cn/downloads/past-releases#elasticsearch

{"error":{"root_cause":[{"type":"index_not_found_exception","reason":"no such index","resource.type":"index_or_alias","resource.id":"bookindex","index_uuid":"_na_","index":"bookindex"}],"type":"index_not_found_exception","reason":"no such index","resource.type":"index_or_alias","resource.id":"bookindex","index_uuid":"_na_","index":"bookindex"},"status":404}

ElasticsearchRepository.class

package org.springframework.data.elasticsearch.repository;
 
import java.io.Serializable;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.repository.NoRepositoryBean;
 
@NoRepositoryBean
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
    <S extends T> S index(S var1);
 
    Iterable<T> search(QueryBuilder var1);
 
    Page<T> search(QueryBuilder var1, Pageable var2);
 
    Page<T> search(SearchQuery var1);
 
    Page<T> searchSimilar(T var1, String[] var2, Pageable var3);
 
    void refresh();
 
    Class<T> getEntityClass();
}

Elasticsearch 6.0及以上版本 一个索引只允许有一个type

异步任务、定时任务、邮件任务

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

application.properties

spring.mail.host=smtp.qq.com
spring.mail.username=412198579@qq.com
spring.mail.password=ynwrralgwyyxbjhi
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

AsynctaskApplication.java 入口类

package com.springboot.asynctask;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
 
@EnableAsync  // 开启基于注解的异步功能
@EnableScheduling  // 开启基于注解的定时任务功能
@SpringBootApplication
public class AsynctaskApplication {
 
	public static void main(String[] args) {
		SpringApplication.run(AsynctaskApplication.class, args);
	}
 
}

AsyncService 异步任务

package com.springboot.asynctask.service;
 
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
 
@Service
public class AsyncService {
 
 
    @Async  // 声明这是一个异步方法
    public void hello(){
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello AsyncService");
    }
}

定时任务

package com.springboot.asynctask.service;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
 
@Service
public class SchedulingService {
 
    @Scheduled(cron="1 * * * * *")  // [秒] [分] [小时] [日] [月] [周] [年]
    public void hello(){
        System.out.println("hello schedulingService");
    }
 
}

异步任务测试

package com.springboot.asynctask.controller;
 
import com.springboot.asynctask.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class HelloController {
 
    @Autowired
    AsyncService asyncService;
 
    @GetMapping("/hello")
    public String hello() {
        asyncService.hello();
        return "hello";
    }
}

测试邮件发送

package com.springboot.asynctask;
 
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.internet.MimeMessage;
import java.io.File;
 
@SpringBootTest
class AsynctaskApplicationTests {
 
	@Autowired
	JavaMailSender javaMailSender;
 
	@Test
	void contextLoads() throws Exception {
// 创建一个简单邮件
//		SimpleMailMessage message = new SimpleMailMessage();
//		message.setSubject("测试邮件");  // 邮件标题
//		message.setText("邮件文本");  // 邮件内容
//		message.setFrom("412198579@qq.com");
//		message.setTo("412198579@qq.com");
//		javaMailSender.send(message);
 
		// 创建一个复杂邮件
		MimeMessage mimeMessage = javaMailSender.createMimeMessage();
		MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
		mimeMessageHelper.setFrom("412198579@qq.com");
		mimeMessageHelper.setTo("412198579@qq.com");
		mimeMessageHelper.setSubject("subject");
		mimeMessageHelper.setText("<b>hello</b>",true);
		mimeMessageHelper.addAttachment("1.jpg",new File("E:\\image\\1.jpg"));
		javaMailSender.send(mimeMessage);
	}
 
}

Spring-Security安全

引入Spring Security模块

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
 
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
 
<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

编写Spring Security配置
config\MySecurityConfig.java

package com.springboot.mysecurity.config;
 
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 
@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("username").passwordParameter("password").loginPage("/userlogin");
        //  ①/login来到登录页
        // ②重定向到 /login?error表示登录失败
        // ③更多详细
        // ④默认post形式的/login代表处理登录 ,一旦定制loginPage,那么loginPage的post请求就是登录
        // 开启自动配置的注销功能(访问logout清空session)   配置注销成功重定向的页面
        http.logout().logoutSuccessUrl("/");
        // 记录功能 关闭浏览器依旧可以访问
        http.rememberMe();
    }
 
    // 定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //super.configure(auth);
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2")
                .and()
                .withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3")
                .and()
                .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3");
    }
}

KungfuController控制器

package com.springboot.mysecurity.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
 
@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;
	}
}

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1 align="center">欢迎登陆武林秘籍管理系统</h1>
	<hr>
	<div align="center">
		<form th:action="@{/userlogin}" method="post">
			用户名:<input name="username"/><br>
			密码:<input name="password"><br/>
			<input type="checkbox" name="remeber"> 记住我<br/>
			<input type="submit" value="登陆">
		</form>
	</div>
</body>
</html>

welcome.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	  xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">欢迎光临武林秘籍管理系统</h1>
<div sec:authorize="!isAuthenticated()">
	<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/userlogin}">请登录</a></h2>
</div>
<div sec:authorize="isAuthenticated()">
	<h2><span sec:authentication="name"></span>,您好,您的角色有:
		<span sec:authentication="principal.authorities"></span></h2>
	<form th:action="@{/logout}" method="post">
		<input type="submit" value="注销"/>
	</form>
</div>
 
<hr>
 
<div sec:authorize="hasRole('VIP1')">
	<h3>普通武功秘籍</h3>
	<ul>
		<li><a th:href="@{/level1/1}">罗汉拳</a></li>
		<li><a th:href="@{/level1/2}">武当长拳</a></li>
		<li><a th:href="@{/level1/3}">全真剑法</a></li>
	</ul>
 
</div>
 
<div sec:authorize="hasRole('VIP2')">
	<h3>高级武功秘籍</h3>
	<ul>
		<li><a th:href="@{/level2/1}">太极拳</a></li>
		<li><a th:href="@{/level2/2}">七伤拳</a></li>
		<li><a th:href="@{/level2/3}">梯云纵</a></li>
	</ul>
 
</div>
 
<div sec:authorize="hasRole('VIP3')">
	<h3>绝世武功秘籍</h3>
	<ul>
		<li><a th:href="@{/level3/1}">葵花宝典</a></li>
		<li><a th:href="@{/level3/2}">龟派气功</a></li>
		<li><a th:href="@{/level3/3}">独孤九剑</a></li>
	</ul>
</div>
</body>
</html>

devtools开发热部署
1.引入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
</dependency>

2.快捷键ctrl+f9

actuator监管端点测试
引入依赖

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

应用程序启动,控制台显示:
Exposing 2 endpoint(s) beneath base path ‘/actuator’,默认只开启了health,info两个端点,访问http://localhost:8080/actuator响应如下:

{
_links: {
		self: {
			href: "http://localhost:8080/actuator",
			templated: false
		},
        health: {
			href: "http://localhost:8080/actuator/health",
			templated: false
		},
		health-path: {
			href: "http://localhost:8080/actuator/health/{*path}",
			templated: true
		},
		info: {
			href: "http://localhost:8080/actuator/info",
			templated: false
		}
   }
}

application.properties

# 开启所有端点
management:
  endpoints:  # 这里是 endpoints
    web:
      # 默认路径
      base-path: /actuator
      exposure:
        #  Endpoint IDs that should be included or '*' for all.
        include: '*'
    # 显示详细的 health 信息
    jmx:
      # Whether unique runtime object names should be ensured.
      domain: org.springframework.boot
      exposure:
        # Endpoint IDs that should be included or '*' for all.
        include: '*'
  # 显示详细的 health 信息
  endpoint: # 这里是 endpoint
    health:
      show-details: always
    # 打开 shutdown 端点,通过 POST 访问该端点可以关闭应用
    shutdown:
      enabled: true

发表评论

邮箱地址不会被公开。 必填项已用*标注