【SpringBoot】学习笔记

尚硅谷出品的springboot教程,很详细的讲解springboot,此篇为学习笔记

Spring Boot 基础入门

Spring Boot 入门

简介

简化Spring应用开发的一个框架
整个Spring技术栈的一个大整合
J2EE开发的一站式解决方案

微服务

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

springboot 来简化 spring应用开发,约定大于配置

springboot底层就是spring

优点

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

Spring Boot HelloWorld

浏览器发送hello请求,服务器接收请求并处理,响应Hello World字符串

1.创建一个maven 工程

2.导入springboot相关依赖

3. 编写一个主程序

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

4.编写相关的Controller

@Controller
public class HelloController{
	
	@ResponseBody
	@RequestMapping("/hello")
	public String hello(){
		return "hello world";
	}
}

5.运行主程序测试

6.简化部署

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

@SpringBootApplication注解
标注一个主程序类,说明这是一个Spring Boot应用

springboot 依赖的版本管理

<!--springboot 的父项目,他来真正管理依赖版本-->
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-dependencies</artifactId>
   <version>2.5.6</version>
 </parent>

启动器

springboot场景启动器

例如spring-boot-starter-web,帮我们导入了web模块正常运行所依赖的组件

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

自动配置

主程序

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

@SpringBootApplication:Spring Boot应用,标注在某个类上,说明这个类是spring boot的主配置类,springboot就应该运行这个类的main方法来启动springboot应用

@SpringBootConfiguration:SpringBoot的配置类,标注在某个类上,表示这是一个springboot配置类(属于springboot框架)

@Configuration:配置类上标注这个注解(属于spring框架),配置类的作用就等同于配置文件,配置类也是容器中的一个组件;@Component

@EnableAutoConfiguration:开启自动配置功能;在使用spring框架时需要配置的东西,现在都会由SpringBoot帮我们自动配置;@EnableAutoConfiguration就是告诉springboot框架开启这样的自动配置功能,这样自动配置才能生效

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

@AutoConfigurationPackage:自动配置包;
@Import(AutoConfigurationPackages.Registrar.class):spring框架的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class决定;

AutoConfigurationPackages.Registrar.class:将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器(这里导入的组件指的是我们自己声明的@Component)

@Import(EnableAutoConfigurationImportSelector.class):给容器中导入EnableAutoConfigurationImportSelector

EnableAutoConfigurationImportSelector:导入哪些组件的选择器;将所有需要导入的组件,以全类名的方式返回,这些组件就会被添加到容器中;会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件;

有了自动配置类,免去了手动编写配置注入功能组件等的工作

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

在使用spring的时候,我们需要自己配置的东西,自动配置类都帮我们做完了

J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfiguration.jar

@RestController:功能@ResponseBody+@Controller;表示controller返回值是字符串

Spring Boot 配置

配置文件

SpringBoot使用一个全局的配置文件,配置文件名是固定的
application.properties
application.yml

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

yaml是一个标记语言,以数据为中心,比xml更适合做配置文件

值的写法

属性和值都是大小写敏感

字面量:普通的值(数字,字符串,布尔)
k:v 字面直接写
字符串默认不用加上单引号或双引号
双引号:不会转义字符串里面的特殊字符;特殊字符会作为本身像表示的意思显示
单引号:会转义特殊字符,特殊字符最终只能一个普通的字符串数据

对象、map(属性和值)(键值对)
k:v 在下一行来写对象的属性和值的关系;注意缩进

user:
	name: zhangsan
	age: 18

user: {name: zhangsan,age: 18}

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

pets:
	- cat
	- dog
	- pig

pets: [cat,dog,pig]

yml配置文件获取值

创建一个实体类

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

}

class Dog{
	String name;
	Integer age;
}

配置文件

person:
	lastName: zhangsan
	age: 19
	boss: false
	birth: 2000/03/22
	maps: {k1:v1,k2:v2}
	lists: 
		- lisi
		- wangwu
		- zhaoliu
	dog: 
		name: wangcai
		age: 1

我们可以导入配置文件处理器,以后编写配置就会有提示

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

@ConfigurationProperties:告诉springboot将本类中的所有属性和配置文件中相关的配置进行绑定(默认从全局配置文件中获取值)

@SpringBootTest:表示这是一个SpringBoot的单元测试

properties配置文件获取值

person.last-name=zhangsan
person.age=18
person.birth=2017/9/9
person.boss=false
person.maps.k1=v1
person.maps.k2=v2
person.lists=a,b,c
person.dog.name=wangcai
person.dog.age=15

中文会有乱码问题,是因为properties文件默认的编码方式是GBK

@Value(""):回忆spring配置文件xml中声明bean,并赋值时使用的就是value,如下

<bean class="Person">
	<property name="lastName" value=""/>
</bean>

@Value()括号中的值可以写字面量、${key}—从环境变量、配置文件中取值、#{SPEL}取值—spring的表达式语言

@Value和@ConfigurationProperties获取值比较

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

@Validated:写在类上,表示当前类的属性需要校验,启动JSR303校验

松散绑定:举例子,配置文件中last_name,可以赋值给类中的lastName属性;下划线后会变成大写

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

@PropertySource:加载指定的配置文件(非全局配置文件application.yaml)

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

@ImportResource:导入spring框架的配置文件(xml),让配置文件里面的内容生效
SpringBoot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;可以使用@ImportResource注解,加载到启动类上

// 导入spring的配置文件,让它生效
@ImportResource(locations = {"classpath:beans.xml"})

SpringBoot推荐给容器中添加组件的方式–全注解的方式
1.配置类======spring的配置文件
2.使用@Bean给容器中添加组件

@Configuration:指明当前类是一个配置类,配置类就相当于配置文件

@Bean:将方法的返回值添加到容器中,容器中这个组件默认的id就是方法名

配置文件占位符

随机数

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

占位符获取之前配置的值,如果没有可以使用:指定默认值

Profile多环境支持

1.多Profile文件
我们在主配置文件编写的时候,文件名可以是application-{profile}.properties/yaml

SpringBoot框架默认会将application.properties名字的文件作为配置文件

2.yml支持多文档块方式

server:
	port: 8001
spring:
	profiles:
		active: dev
---
server:
	port: 8002
spring:
	profiles: dev
---
server:
	port: 8003
spring:
	profiles: prod

3.激活指定profile

  • 在配置文件中指定spring.profiles.active=dev
  • 命令行: --spring.profiles.active=dev 可以直接在程序启动的时候输入命令
  • 虚拟机参数: -Dspring.profiles.active=dev

配置文件加载位置

file: ./config/
file: ./
classpath: /config/
classpath: /

file 表示项目路径,根路径(src同级别)
classpath 类路径(resources)

优先级由高到底,高优先级和低优先级相同的配置时,高会覆盖低;不同的配置,会出现互补的情况

可以通过spring.config.location来改变默认的配置文件位置

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

外部配置加载顺序

SpringBoot也可以从以下位置加载配置,按照优先级顺序从高到低,高优先级的配置会覆盖低优先级的相同配置,不同的所有配置会形成互补

  1. 命令行参数( Java -jar x.jar --server.port=8080 --server.context-path=/abc)
Java -jar x.jar --server.port=8080 --server.context-path=/abc
多个参数用空格分隔; --配置项=
  1. 来自java:comp/env的NDI属性
  2. java系统属性(System.getProperties())
  3. 操作系统环境变量
  4. RandomValuePropertySource配置的random.*属性值

由jar包外向jar包内进行寻找
优先加载带profile
6. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
7. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件

再来加载不带profile
8. jar包外部的application.properties或application.yml(不带spring.profile)配置文件
9. jar包内部的application.properties或application.yml(不带spring.profile)配置文件

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

自动配置原理

spring boot 启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration

@EnableAutoConfiguration 作用:
利用EnableAutoConfigurationImportSelector给容器中导入一些组件
可以查看selectImport()方法;

// 获取候选的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

// 扫描所有依赖的jar包类路径下的  META-INF/spring.factories
// 把扫描到的这些文件的内容包装成properties对象
// 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加到容器中
SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

将 类路径下 META-INF/spring.factories 里面配置的所有以EnableAutoConfiguration作为key,对应的值加入到了容器中

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

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

HttpEncodingAutoConfiguration为例解释自动配置原理

//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration 
//启动ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties类绑定起来
//并把HttpEncodingProperties类加入到IOC容器中
@EnableConfigurationProperties(HttpEncodingProperties.class)
//spring 底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效
// 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication
// 判断当前项目有没有这个类
// CharacterEncodingFilter是springMVC中进行乱码解决的过滤器
@ConditionalOnClass(CharacterEncodingFilter.class)
// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled, 如果不存在,设置默认值是true
// 即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的,默认值是true
@ConditionalOnProperty(prefix="spring.http.encoding",value="enabled",matchIfMissing=true)
public class HttpEncodingAutoConfiguration{
	//已经和springboot的配置文件映射了
	private final HttpEncodingProperties properties;
	// **只有一个有参构造器的情况下,参数的值就会从容器中拿**
	public HttpEncodingAutoConfiguration(HttpEncodingProperties properties){
		this.properties=properties;
	}

	//给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@Bean
	//如果IOC容器中没有CharacterEncodingFilter类,才可以执行下面方法
	@ConditionalOnMissingBean(CharacterEncodingFilter.class)
	public CharacterEncodingFilter characterEncodingFilter(){
		CharacterEncodingFilter filter = new CharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}

}

根据当前不同的条件,判断,决定这个配置类是否生效
一但这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性是从对应的 xxxproperties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的

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

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

精髓:

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

xxxAutoConfiguration:自动配置类
给容器中添加组件

xxxProperties:封装配置文件中相关属性

@Conditional自动配置报告

1.在soringboot中基于@Conditional派生了很多注解(@Conditional是Spring框架原生注解)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效

自动配置类必须在一定的条件下下能生效
我们怎么知道哪些自动配置类生效
可以通过启动 debug=true 属性;来让控制台打印自动配置报告,这样就可以很方便的查看哪些自动配置类生效了

// 自动配置类启用的
Positive matches

// 没启动的
Netative matches

Spring Boot 与日志

日志框架分类和选择

常见的日志框架:

日志抽象层
JCL,slf4j,jboss-longging

日志实现层
JUL,log4j2,logback

日志框架的实现,是选一个门面(抽象层),再来选一个实现

springboot:底层是spring框架,spring框架默认是JCL

springboot选用slf4j和logback

slf4j 使用原理

1.如何在系统中使用slf4j
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层的方法

给系统里面导入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

1.将系统中其他的日志框架先排除出去
2.用中间包来替换原有的日志框架
3.我们导入slf4j其他的实现

Springboot日志关系

springboot的日志功能依赖

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

springboot 底层也是使用 slf4j+logback 的方式进行日志记录的方式
springboot 也把其他的日志都替换成了slf4j

如果想要引入其他框架,比如需要引入spring框架,一定要把这个框架的默认日志依赖移除掉
spring框架用的是commons-logging

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

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

Springboot 默认配置

1.默认配置
spring boot 默认帮我们配置好了日志

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootApplication{
	// 记录器
	Logger logger = LoggerFactory.getLogger(getClass());

	@Test
	public void contextLoads(){
		// 日志的级别
		// 由低到高
		// 可以调整需要输出的日志级别;日志就只会在这个级别以后的高级别生效
		logger.trace("这是trace日志...");
		logger.debug("这是trace日志...");
		// springboot 默认给我们使用的是info级别的,没有指定级别的就用springboot默认规定的级别,root级别
		logger.info("这是trace日志...");
		logger.warn("这是trace日志...");
		logger.error("这是trace日志...");
	}
}

application.properties 配置文件中可以定义每一个包的日志级别

# 设置com.hupf.pojo包的日志级别是trace
# 表示trace级别以后的日志会输出
logging.level.com.hupf.pojo=trace

# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用spring.log作为默认文件
logging.path=/spring/log

# 不指定路径,就在当前项目下生成springboot.log日志
# 可以指定完整的路径
logging.file=G:/springboot.log

# 在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n

# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} - %msg%n

# 日志的输出格式:
# %d  表示日期时间
# %thread  表示线程名
# -5level  级别从左显示5个字符宽度
# logger{50}  表示logger名字最长50个字符,否则按照句点分割
# %msg  日志消息
# %n  换行符

1.不指定logging.file,不指定logging.path 只在控制台输出
2.指定logging.file=my.log,不指定logging.path 输出日志到my.log文件
3.不指定logging.file,指定logging.path=/var/log 输出到指定目录的spring.log文件中

指定日志文件和日志profile功能

指定配置:
给类路径(resources)下放上每个日志框架自己的配置文件即可,springboot 就不会使用他默认配置的了
Logback:配置文件名为logback-spring.xml,或者logback.xml
Log4j2:log4j2-spring.xml 或者 log4j2.xml
JDK:logging.properties

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

<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
	<layout class="ch.qos.logback.classic.PatternLayout">
		<springProfile name="dev">
			<pattern>%d{yyyy-MM-dd} ---> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
		</springProfile>
		<springProfile name="!dev">
			<pattern>%d{yyyy-MM-dd} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
		</springProfile>
	</layout>
</appender>

切换日志框架

可以按照slf4j的日志适配图,进行相关切换
slf4j+log4j的方式

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

切换为log4j

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Spring Boot 与web开发

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

自动配置原理?
这个场景springboot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?

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

静态资源映射规则

第一种规则

@ConfigurationProperties(prefix="spring.resources",ignoreUnknowFields=false)
public class ResourceProperties implements RessourceLoaderAware{
	// 可以设置和静态资源有关的参数,缓存时间等
	
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}
		
// 配置欢迎页
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
    return welcomePageHandlerMapping;
}

springboot规定静态资源的存放位置
所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源
webjars:以jar包的方式引入静态资源
http://www.webjars.org/

比如访问localhost:8080/webjars/jquery/3.3.1/jquery.js

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

第二种规则

/** 访问当前项目的任何资源,对应到以下位置(静态资源的文件夹)

classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/   根路径

例如访问localhost:8080/abc === 去静态资源文件夹里找abc

也可以在application.properties中修改静态资源的位置

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

第三种欢迎页

欢迎页,静态资源文件夹下的所有index.html页面,被 /** 映射;所以访问localhost:8080/,就会找到静态资源文件夹中的index.html

第四种图标

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

模板引擎

JSP、Velocty、Freemarker、Thymeleaf

springboot推荐的Thymeleaf
语法更简单,功能更强大

引入thymeleaf

<properties>
	<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
	<!--布局功能的支持程序  thymeleaf3主程序  layout2以上版本-->
	<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>

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

Thymeleaf使用和语法

@ConfigurationProperties(prefix="spring.thymeleaf")
public class ThymeleafProperties{
	private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
    private static final MineType DEFAULT_CONTENT_TYPE = MineType.valueOf("text/html");
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";

	// 只要我们把html页面放在classpath:/templates/路径下,thymeleaf就会帮我们自动渲染(转发或者重定向)
}

只要我们把html页面放在classpath:/templates/路径下,thymeleaf就会帮我们自动渲染(转发或者重定向)

使用:
1.导入thymeleaf命名空间

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

2.使用thymeleaf的语法

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>
<body>
	<h1>成功</h1>
	<!-- th:text 将div里面的文本内容设置为-->
	<!-- <h1>你好</h1>-->
	<div th:text="${hello}"></div>
	<!--你好-->
	<div th:utext="${hello}"></div>
	<!--th:each每次遍历都会生成当前这个标签-->
	<h4 th:text=${users} th:each="user:${users}"></h4>
	
	<span th:each="user:${users}">[[${user}]]</span>
</body>
</html>

测试代码

@Controller
public class HelloController{
	@RequestMapping("/success")
	public String success(Map<String,Object> map){
		map.put("hello","<h1>你好</h1>");
		map.put("users",Arrays.asList("zhangsan","lisi","wangwu"));
		// classpath:/templates/success.html
		return "success";
	}
}

语法规则

th:text 改变当前元素里面的文本内容
th:任意html属性;来替换原生属性的值

表达式语法,详见thymeleaf语法可以查看官网

${}
获取变量值;OGNL;
1.获取对象的属性,调用方法
2.使用内置的基本对象
3.内置的一些工具对象

*{}
和${}在功能上是一样的
补充:配合th:object="${user.name}"使用

#{}
获取国际化内容

@{}
定义URL

~{}
片段,引用的表达式

+
文本操作
+,-*,/,%
数学运算
and or
! not
布尔运算
> < >= <= (gt lt ge le)
== != (eq ne)
比较运算
(if)?(then)
(if)?(then):(else)
(value)?:(default value)
条件运算

SpringMVC自动配置原理

Spring Boot 自动配置了SpringMVC,以下是SpringBoot 对SpringMVC的默认配置:

  • 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定渲染(转发?重定向?))
  • ContentNegotiatingViewResolver:组合所有的视图解析器;
  • 定制:我们可以自己给容器中添加一个视图解析器,ContentNegotiatingViewResolver会自动将我们定制的ViewResolver组合起来
  • 静态资源文件夹路径
  • 静态首页访问,图标
  • 自动注册了Converter,GenericConverter,Formatter的bean
  • Converter:转换器;public String hello(User user):类型转换使用Converter
  • Formatter:格式化器;2015.2.4===Date(也可以定制格式化转换器,我们只需要放在容器中即可)
@Bean
// 在文件中配置日期格式化的规则
@ConditionalOnProperty(prefix="spring.mvc",name="date-format")
public Formatter<Date> dateFormatter(){
	// 日期格式化组件
	return new DateFormatter(this.mvcProperties.getDateFormat());
}
  • HttpMessageConverter:SpringMVC用来转换http请求和响应的
  • HttpMessageConverters:是从容器中确定;获取所有的HttpMessageConverter;自己给容器中添加HttpMessageConverter,只需要将自己的组件注册到容器中(@Bean,@Component)
  • MessageCodesResolver:定义错误代码生成规则
  • ConfigurableWebBindingInitializer:我们可以配置一个ConfigurableWebBindingInitializer来替换默认的(添加到容器中);web数据绑定器

org.springframework.boot.autoconfigure.web:web的所有自动场景

扩展springMVC

<mvc:view-controller path="/hello" view-name="success" />

<!--拦截器-->
<mvc:interceptors>
	<mvc:interceptor>
		<mvc:mapping path="/hello"/>
		<bean></bean>
	</mvc:interceptor>
</mvc:interceptors>

编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc
既保留了所有的自动配置,也能用我们的扩展配置

// 使用WebMvcConfigurerAdapter可以来扩展springMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
	
	@Override
	public void addViewControllers(ViewControllerRegistry registry){
		// 浏览器发送/atguigu请求,跳转到success.html
		registry.addViewController("/atguigu").setViewName("success");
	}

}

原理:
1.WebMvcAutoConfigurer是SpringMVC的自动配置类
2.在做其他自动配置时会导入@Import(EnableWebMvcConfiguration.class)

@Configuration
public static class EnableWebMvcConfgiration extends DelegatingWebMvcConfiguration{
	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
}

// @Autowired注解让方法从容器中取参数
// 从容器中获取所有的WebMvcConfigurer
@Autowired(required=false)
public void setConfigurers(List<WebMvcConfigurer> configurers){
	if(!CollectionUtils.isEntry(configurers)){
		this.configurers.addWebMvcConfigurers(configurers);
		// 一个参考实现;将所有的WebMvcConfigurer相关的配置都来一起调用
		//public void addViewControllers(ViewControllerRegistry registry){
		//	for(WebMvcConfigurer delegate:this.delegates){
		//		delegate.addViewControllers(registry);
		//	}
		}
	}
}

3.容器中所有的WebMvcConfigurer都会一起起作用
4.我们的配置类也会被调用
效果:SpringMVC的自动配置和我们的扩展配置都会起作用

DelegatingWebMvcConfiguration 解析

DelegatingWebMvcConfiguration是对Spring MVC进行配置的一个代理类。它结合缺省配置和用户配置定义Spring MVC运行时最终使用的配置。

DelegatingWebMvcConfiguration继承自WebMvcConfigurationSupport。而WebMvcConfigurationSupport为Spring MVC提供缺省配置。这就是上面所提到的缺省配置。

DelegatingWebMvcConfiguration又会被注入一组WebMvcConfigurer,顾名思义,这是一组"Spring MVC配置器"。这组配置器由开发人员或者框架某一部分提供,是接口WebMvcConfigurer的实现类,按照Spring的建议,通常也是一个配置类,也就是使用了注解@Configuration的类。这组WebMvcConfigurer就是上面提到的用户配置。

DelegatingWebMvcConfiguration本身使用了注解@Configuration,所以它是一个配置类,因此它也会被作为一个单例bean注册到容器和被容器注入相应的依赖。WebMvcConfigurer注入到DelegatingWebMvcConfiguration正是因为他是一个bean并且在方法void setConfigurers(List configurers)使用了注解@Autowired(required = false)。

WebMvcConfigurationSupport提供缺省配置的主要方式是定义一组bean供Spring MVC在运行时使用,而这些bean定义方法所用的配置原料供应方法给子类保留了可覆盖的回调方法用于定制化(这些定制化回调方法正好通过接口WebMvcConfigurer抽象建模)。WebMvcConfigurationSupport为这些用于定制的回调方法提供了缺省实现(比如空方法),而DelegatingWebMvcConfiguration则覆盖了这些方法,使用所注入的WebMvcConfigurers对相应的配置原料进行定制从而起到定制整个Spring MVC配置的作用。

注解@EnableWebMvc的作用其实就是引入一个DelegatingWebMvcConfiguration用于配置Spring MVC。而通常,我们正式使用注解@EnableWebMvc在某个实现了WebMvcConfigurer的配置类上来配置Spring MVC的


@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	// WebMvcConfigurerComposite 其实就是对多个 WebMvcConfigurer 的一个组合,
	// 从命名就可以看出这一点
	// WebMvcConfigurerComposite 自身也实现了接口 WebMvcConfigurer,
	// 问 : 为什么要组合多个 WebMvcConfigurer 然后自己又实现该接口 ?
	// 答 : 这么做的主要目的是在配置时简化逻辑。调用者对 WebMvcConfigurerComposite
	// 可以当作一个 WebMvcConfigurer 来使用,而对它的每个方法的调用都又会传导到
	// 它所包含的各个 WebMvcConfigurer 。
	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	// 注入一组WebMvcConfigurer,这些WebMvcConfigurer由开发人员提供,或者框架其他部分提供,已经
	// 以bean的方式注入到容器中
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}


	// 以下各个方法都是对WebMvcConfigurationSupport提供的配置原材料定制回调方法的覆盖实现,
	// 对这些方法的调用最终都转化成了对configurers的方法调用,从而实现了定制化缺省Spring MVC 
	// 配置的作用
	
	@Override
	protected void configurePathMatch(PathMatchConfigurer configurer) {
		this.configurers.configurePathMatch(configurer);
	}

	@Override
	protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		this.configurers.configureContentNegotiation(configurer);
	}

	@Override
	protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
		this.configurers.configureAsyncSupport(configurer);
	}

	@Override
	protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		this.configurers.configureDefaultServletHandling(configurer);
	}

	@Override
	protected void addFormatters(FormatterRegistry registry) {
		this.configurers.addFormatters(registry);
	}

	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		this.configurers.addInterceptors(registry);
	}

	@Override
	protected void addResourceHandlers(ResourceHandlerRegistry registry) {
		this.configurers.addResourceHandlers(registry);
	}

	@Override
	protected void addCorsMappings(CorsRegistry registry) {
		this.configurers.addCorsMappings(registry);
	}

	@Override
	protected void addViewControllers(ViewControllerRegistry registry) {
		this.configurers.addViewControllers(registry);
	}

	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
		this.configurers.configureViewResolvers(registry);
	}

	@Override
	protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		this.configurers.addArgumentResolvers(argumentResolvers);
	}

	@Override
	protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		this.configurers.addReturnValueHandlers(returnValueHandlers);
	}

	@Override
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.configureMessageConverters(converters);
	}

	@Override
	protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.extendMessageConverters(converters);
	}

	@Override
	protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
	}

	@Override
	protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
	}

	@Override
	@Nullable
	protected Validator getValidator() {
		return this.configurers.getValidator();
	}

	@Override
	@Nullable
	protected MessageCodesResolver getMessageCodesResolver() {
		return this.configurers.getMessageCodesResolver();
	}
}

全面接管SpringMVC

springboot对springmvc的自动配置不需要了,所有都是我们自己配置;所有的springmvc的自动配置都失效
我们只需要在配置类中添加一个@EnableWebMvc

@EnableWebMvc //全面接管springmvc自动配置
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
	
	@Override
	public void addViewControllers(ViewControllerRegistry registry){
		// 浏览器发送/atguigu请求,跳转到success.html
		registry.addViewController("/atguigu").setViewName("success");
	}

}

原理:
为什么加上@EnableWebMvc,自动配置就失效了
1.EnableWebMvc的核心

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc{}

//进入DelegatingWebMvcConfiguration类
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport{}

//DelegatingWebMvcConfiguration继承WebMvcConfigurationSupport

2.再看一下

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
//IOC容器中没有WebMvcConfigurationSupport类才生效
//所以标注@EnableWebMvc注解后,WebMvcAutoConfiguration失效
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {

4.@EnableWevMvc将WebMvcConfigurationSupport组件导入进来
5.导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能

如何修改SpringBoot的默认配置

模式:

  1. springboot在自动配置很多组件的时候,先卡容器中有没有用户自己配置的(@Bean,@Component),如果有,就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个,比如ViewResolver,将用户配置的和自己默认的组合起来
  2. 在SpringBoot中会有很多xxxConfigurer帮助我们进行扩展配置
  3. 在springboot中会有很多xxxCustomizer帮助我们进行定制配置

RestfulCRUD

默认访问首页

// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
//@EnableWebMvc 不要配置,表示不全面接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    // 视图控制器
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 将浏览器发送的/atguigu转到success
        registry.addViewController("/atguigu").setViewName("success");
    }

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

国际化

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

步骤:
1.编写国际化配置文件,抽取页面需要显示的国际化消息
2.SpringBoot自动配置好了管理国际化资源文件的组件

@ConfigurationProperties(prefix="spring.messages")
public class MessageSourceAutoConfiguration {
	// 我们的配置文件可以直接放在类路径下叫messages.properties
	private String basename = "messages";

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

}

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

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

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

@Bean
@ConditionalOnMissingBean
@ConditionOnProperty(prefix="spring.mvc",name="locale")
public LocaleResolver localeResolver(){
	if(this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED){
		return new FixedLocaleResolver(this.mvcProperties.getLocale());
	}
	AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
	localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
	return localeResolver;
}
// 默认的就是根据请求头带来的区域信息获取Locale进行国际化

4.点击链接切换国际化

/**
 * 可以在链接上携带区域信息
 */
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();
        if(!StringUtils.isEmpty(1)){
            String[] split = l.split("_");
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

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

    }

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

登录

@Controller
public class LoginController{
	// @DeleteMapping
	// @PutMapping
	// @GetMapping
	// @RequestMapping(value="/user/login",method=RequestMethod.POST)

	@PostMapping(value="/user/login")
	public String login(@RequestParam("username") Stirng username, @RequestParam("password") String password){
		if(password.equals("admin")){
			return "success";
		}else{
			return "login";
		}
		return "";
	}
}

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

1.禁用模板引擎的缓存

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

2.页面修改完成后ctrl+F9,重新编译
登录错误信息的显示

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

3.拦截器进行登陆检查

// 创建一个拦截器,必须实现拦截器接口(HandlerInterceptor)
/**
 * 登录检查
 */
public class LoginHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        Object user = request.getSession().getAttribute("loginUser");
        if(user==null){
            // 未登录,返回登陆页面
            request.setAttribute("msg","没有权限请先登录");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else{
            return true;
        }
    }


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

    }

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

    }
}

将拦截器注册到容器

// 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
//@EnableWebMvc 不要配置,表示不全面接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    // 视图控制器
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 将浏览器发送的/atguigu转到success
        registry.addViewController("/atguigu").setViewName("success");
    }

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

					@Override
            public void addInterceptors(InterceptorsRegistry registry) {
                // SpringBoot已经做好了静态资源映射
                registry.addInterceptor(new LoginHandlerInterceptor())
			                .addPathPatterns("/**")// 添加拦截信息
			                .excludePathPatterns("/index.html","/","","/user/login")// 排除
            }
        };
        return adapter;
    }
}

CRUD-员工系列

1.Restful CRUD :CRUD满足Rest风格
URI:/资源名称/资源标识 HTTP请求方式区分资源CRUD操作

普通CRUDRestfulCRUD
查询getEmpemp–GET
添加getEmp?xxxemp–POST
修改updateEmp?id=xxx&xxx=xxxemp/{id}–PUT
删除deleteEmp?id=xemp/{id}–DELETE

2.实验的请求架构

请求URI请求方式
查询所有的员工empsGET
查询某个员工(来到修改页面)emp/{id}GET
来到添加页面empGET
添加员工empPOST
来到修改页面(查出员工进行信息回显)emp/{id}GET
修改员工empPUT
删除员工emp/{id}DELETE

3.员工列表
thymeleaf公共页面元素抽取

1.抽取公共片段
<div th:fragment="copy">
&copy;2011 The Good Thymes Virtual Grocery
</div>

2.引入公共片段
<div th:insert="~{footer :: copy}"></div>

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

3.默认效果
insert的功能片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}

三种引入功能片段的th属性:
th:insert 将公共片段整个插入到声明引入的元素中
th:replace 将声明引入的元素替换为公共片段
th:include 将被引入的片段的内容包含进这个标签中

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

<!--引入方式-->
<div th:insert="footer::copy"></div>
<div th:insert="footer::copy"></div>
<div th:insert="footer::copy"></div>

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

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

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

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

<!--引入侧边栏,传入参数-->
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>

CRUD-员工添加

1.添加页面
在这里插入图片描述在这里插入图片描述

CRUD-员工修改

1.添加页面,修改添加二合一表单
在这里插入图片描述在这里插入图片描述在这里插入图片描述

CRUD-员工删除

1.添加页面
在这里插入图片描述在这里插入图片描述

错误处理机制

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

默认效果:
浏览器返回一个默认的错误页面
在这里插入图片描述
浏览器发送的请求头:
在这里插入图片描述
postMan等软件发送请求,返回的就是json数据

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

给容器中添加了以下组件:
1.DefaultErrorAttributes:

// 帮我们在页面共享信息
private Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = this.getError(request);
        MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations.from(error.getClass(), SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
        HttpStatus errorStatus = this.determineHttpStatus(error, responseStatusAnnotation);
        errorAttributes.put("status", errorStatus.value());
        errorAttributes.put("error", errorStatus.getReasonPhrase());
        errorAttributes.put("message", this.determineMessage(error, responseStatusAnnotation));
        errorAttributes.put("requestId", request.exchange().getRequest().getId());
        this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
        return errorAttributes;
    }

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

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
	// 产生html类型的数据
	@RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(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.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

3.ErrorPageCustomizer:

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

4.DefaultErrorViewResolver:

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

        return modelAndView;
    }

private ModelAndView resolve(String viewName, Map<String, Object> model) {
        // 默认Springboot可以去找到一个页面  “error/404”
        String errorViewName = "error/" + viewName;
        // 如果模板引擎可以解析这个页面地址,那么就用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        // 如果模板引擎可以用,返回到errorViewName指定的视图地址
        // 如果模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面  “error/404”
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

步骤:
一旦系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
1.响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

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

        return modelAndView;
    }

2.如何定制错误响应

如何定制错误响应

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

    我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的错误,精确优先(优先寻找精确的 状态码.html)
    就是如果有404.html,优先404.html,其次找4xx.html
    页面能获取的信息;
    timestamp:时间戳
    status:状态码
    error:错误提示
    exception:异常对象
    message:异常消息
    errors:JSR303数据校验的错误都在这里

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

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

如何定制错误的json数据

自定义异常处理器&返回定制json数据
@ControllerAdvice
public class MyExceptionHandler{
	// 这种方式返回的都是json字符串
	@ResponseBody
	@ExceptionHandler(UserNotExistException.class)
	public Map<String,Object> handleException(Exception e){
		Map<String,Object> map = new HashMap<>();
		map.put("code","user.notexist");
		map.put("message",e.getMessage());
		return map;
	}
转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e,HttpServletRequest request){
	Map<String,Object> map = new HashMap<>();
	/**
	* 传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面解析流程
	* Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
	*/
	request.setAttribute("javax.servlet.error.status_code",500);
	map.put("code","user.notexist");
	map.put("message",e.getMessage());

	request.setAttribute("ext",map);
	// 转发到/error
	return "forward:/error";
}

3.将我们的定制数据携带出去

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

1.完全来编写一个ErrorController的实现类(或者是编写AbstractErrorController的子类),放如容器中;
2.页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;

容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;

自定义ErrorAttributes

//给容器中加入我们自己定义的errorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes{
	@Override
	public Map<String,Object> getErrorAttributes(RequestAttributes requestAttributes,boolean includeStackTrace){
		Map<String,Object> map = super.getErrorAttributes(requestAttributes,includeStackTrace);
		map.put("company","atguigu");

		// 我们的一场处理器携带的数据
		Map<String,Object> ext = (Map<String,Object>)requestAttributes.getAttribute("ext",0);
		return map;
	}
}

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

配置嵌入式servlet容器

SpringBoot默认是用的嵌入的Servlet容器(Tomcat)

1、如何定制和修改Servlet容器的相关配置
1)修改和server有关的配置(ServerProperties类【底层也是EmbeddedServletContainerCustomizer】)

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

// 通用的Servlet容器设置
server.xxx

// Tomcat的设置
server.tomcat.xxx

2)编写一个EmbeddedServletContainerCustomizer:嵌入式的容器的定制器;来修改Servlet容器的配置

@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
	@Bean
	public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
		// 定制嵌入式的Servlet容器相关的规则
		@Override
		public void customize(ConfigurableEmbeddedServletContainer container){
			container.setPort(8083);
		}
	}
}

2、注册Servlet三大组件【Servlet、Filter、Listener】
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件

注册三大组件用以下方式
ServletRegistrationBean

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

FilterRegistrationBean

// 注册Filter组件
@Bean
public FilterRegistrationBean myFilter(){
	FilterRegistrationBean registration = new FilterRegistrationBean();
	registration.setFilter(new MyFilter());
	registration.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
	return registration;
}

ServletListenerRegistrationBean

// 注册Listener组件
@Bean
public ServletListenerRegistrationBean myListener(){
	ServletListenerRegistrationBean<MyListener> registration = new ServletListenerRegistrationBean<>(new MyListener());
	return registration;
}

SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器(DispatcherServlet);
DispatcherServletAutoConfiguration中:

@Bean(name=DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value=DispatcherServlet.class,name=DEFAULT_DISPATCHER_SERVLET_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet){
    ServletRegistrationBean registration = new ServletRegionBean(dispatcherServlet,this.serverProperties.getServletMapping());
    // 默认拦截: / 所有请求;包括静态资源,但是不拦截jsp请求;  /* 会拦截jsp
    // 可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
    
    registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
    if(this.multipartConfig != null){
        registration.setMultipartConfig(this.multipartConfig);
    }
    return registration;
}

更改springboot默认配置的servlet容器

SpringBoot能支持其他的Servlet容器
替换为其他嵌入式Servlet容器

默认支持:
Tomcat(默认使用)

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<!--引入web模块就是使用嵌入式的Tomcat作为Servlet容器-->
</dependency>

jetty

<!--引入web模块-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>

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

Undertow

<!--引入web模块-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>

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

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

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

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

    @Configuration
    @ConditionalOnClass({Servlet.class,Server.class,Loader.class,WebAppContext.class})
    @ConditionalOnMissingBean(value=EmbeddedServletContainerFactory.class,search=SearchStrategy.CURRENT)
    public static class EmbeddedJetty{
        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory(){
            return new JettyEmbeddedServletContainerFactory();
        }

    }

	@Configuration
    @ConditionalOnClass({Servlet.class,Undertow.class,SslClientAuthMode.class})
    @ConditionalOnMissingBean(value=EmbeddedServletContainerFactory.class,search=SearchStrategy.CURRENT)
    public static class EmbeddedUndertow{
        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory(){
            return new UndertowEmbeddedServletContainerFactory();
        }

    }

}

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

public interface EmbeddServletContainerFactory{
	// 获取嵌入式Servlet容器
	EmbeddServletContainer getEmbeddServletContainer(ServletContextInitializer... initializers);
}

在这里插入图片描述
2.EmbeddServletContainer:(嵌入式的Servlet容器)
在这里插入图片描述
3.以TomcatEmbeddedServletContainerFactory为例

@Override
public EmbeddedServletContainer getEmbeddedServletContainer(){
	// 创建一个Tomcat
	Tomcat tomcat = new Tomcat();
	// 配置Tomcat的基本环节
	File baseDir = (this.baseDirectory!=null?this.baseDirectory:createTempDir("tomcat"));
	tomcate.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protool);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for(Connector additionalConnector:this.additionalTomcatConnectors){
		tomcat.getService().addConnector(additionalConnector);
	}
	prepareContext(tomcat.getHost(),initializers);

	// 将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并启动tomcat服务器
	return getTomcatEmbeddedServletContainer(tomcat);
}

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

ServerProperties
EmbeddedServletContainerCustomizer

EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置
5.容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor

// 初始化之前

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


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

private Collection<EmbeddedServletContainerCustomizer> getCustomizers(){
	if(this.customizers == null){
		this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
			//从容器中获取锁头这个类型的组件:EmbeddedServletContainerCustomizer
			//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
			this.beanFactory.getBaenOfType(EmbeddedServletContainerCustomizer.class,false,false).values()
		);
		Collections.sort(this.customizers,AnnotationAwareOrderComparator.INSTANCE);
		this.customizers = Collections.unmodifiableList(this.costomizers);
	}
	return this.customizers;
}

ServerProperies也是定制器

步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法

嵌入式Servlet容器启动原理

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

获取嵌入式的Servlet容器工厂:
1)、Springboot应用启动运行run方法

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

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


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

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

5)、web ioc容器会创建嵌入式的Servlet容器;create Embedded Servlet Container()

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


EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

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

7)、使用容器工厂获取嵌入式的Servlet容器;


this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSeifinitializer());

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

使用外置的Servlet容器

使用springboot内置嵌的入式Servlet容器:需要将应用打成可执行的jar
优点:简单、便携
缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainizerFactory】)

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

步骤:

1)、必须创建一个war项目,利用idea创建号目录结构,添加webapp/META-INF文件夹

2)、将嵌入式的Tomcat指定为provided


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

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


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

4)、启动服务器就可以使用

原理

jar包:执行SpringBoot朱磊的main方法,启动ioc容器,创建嵌入式的Serlvet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;

servlet3.0(spring注解版):
Shared libraries / runtimes pluggability:
规则:
1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面的ServletContainerInitializer实例

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

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

流程:
1)、启动Tomcat

2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer

3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set;为这些WebApplicationInitializer类型的类创建实例

4)、每一个WebApplicationInitializer都调用自己的onStartup

5)、相当于我们的SpringBootServletInitializer的累会被创建对象,并执行onStartup方法

6)、SpringBootServletInitializer实例执行onStartup方法的时候会createRootApplicationContext;创建容器


protected WebApplicationContext createRootApplicationContext(ServletContext servletContext){
    // 1.创建SpringApplicationBuilder
    SpringApplicationBuiledr builder = createSpringApplicationBuilder();
    StandardServletEnvironment environment = new StandardServletEnvironment();
    environment.initPropertySources(servletContext,null);
    builder.enviroment(environment);
    builder.mian(getClass());
    ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
    if(parent != null){
        this.logger.info("Root context already creted (using as parent).");
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,null);
        builder.initializer(new ParentContextApplicationContextInitializer(parent));
    }
    builder.initializer(new ServletContextApplicationContextInitializer(servletContext));
    builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);

    // 调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
    builder = configure(builder);

    // 使用builder创建一个Spring应用
    SpringApplication application = builder.build();
    if(application.getSources().isEmpty() && AnnotationUtils.findAnnotation(getClass().Configuration.class)){
        application.getSources().add(getClass());
    }

    Assert.state(!application.getSources().isEmpty(),"No SpringApplication sources have been defind.Either override the configure method or add an @Configuretion annotation");
    if(this.registerErrorPageFilter){
        application.getSources().add(ErrorPageFilterConfiguration.class);
    }
    // 启动Spring应用
    return run(application);        
}

7)、Spring的应用就启动并且创建IOC容器


public ConfigurationApplicationContext run(String... args){
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();

    try{
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        prepareContext(context,environment,listeners,applicationArguments,printedBanner);

        // 刷新IOC容器
        refreshContext(context);
        afterRefresh(context,applicationArguments);
        listeners.finished(context,null);
        stopWatch.stop();
        if(this.logStartupInfo){
            new StartupInfoLogger(this.mainApplicaitonClass).logStarted(getApplication(),stopWatch);
        }
        return context;
    }catch(Throwable ex){
        HandlerRunFailure(context,listeners,analyzers,ex);
        throw new IllegalAgeException(ex);
    }
}

启动Servlet容器,再启动SpringBoot应用

Docker

简介

Docker是一个开源的应用容器引擎;是一个轻量级容器技术;
Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快捷的。

核心概念

docker主机(host):安装了docker程序的机器(Docker直接安装在操作系统之上)

docker客户端(client):连接docker主机进行操作

docker仓库(Registry):用来保存各种打包好的软件镜像

docker镜像(Images):软件打包好的镜像;放在docker仓库中

docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或者一组应用

使用docker的步骤:
1)、安装docker
2)、去docker仓库找到这个软件对应的镜像
3)、使用docker运行这个镜像,这个镜像就会生成一个docker容器
4)、对容器的启动停止就是对软件的启动停止

docker常用命令

1.镜像操作

查找镜像
docker search 名字

下载镜像
docker pull 名字

查看所有本地镜像
docker images

删除指定的本地镜像
docker rmi image-id

2.容器操作
软件镜像—运行镜像----产生一个容器

搜索镜像
docker search tomcat

拉去镜像
docker pull tomcat

根据镜像启动容器
docker run --name mytomcat -d tomcat:latest

查看运行中的容器
docker ps

停止运行中的容器
docker stop 容器ID

查看所有的容器
docker ps -a

启动容器
docker start 容器ID

删除一个容器
docker rm 容器ID

启动一个做了端口映射的tomcat
docker run -d -p 8888:8080 tomcat
-d 后台运行
-p 将主机的端口映射到容器的一个端口  主机端口:容器内部的端口

查看防火墙状态
service firewalld status

关闭防火墙
service firewalld stop

查看容器的日志
docker logs 容器名字/容器ID

安装MySQL示例

下载镜像
docker pull mysql

启动运行镜像
docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql

添加端口映射
docker run -p 3306:3306 --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql

把主机的/conf/mysql文件挂在到mysqldocker容器的/etc/mysql/conf.d文件夹里面
改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)
docker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

指定mysql的一些配置参数
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode-ci

SpringBoot与数据访问

JDBC

导入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

配置文件

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

效果:
默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源
数据源的相关配置都在DataSourceProperties里面

自动配置原理:
org.springframework.boot.autoconfigure.jdbc:
1.参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用spring.datasource.type指定自定义的数据源类型

2.SpringBoot默认可以支持
DataSource、HikarDataSource、BasicDataSource

3.自定义数据源类型

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

4.DataSourceInitializer:ApplicationListener
作用:
1.runSchemaScripts();运行建表语句
2.runDataScripts();运行插入数据的失去了语句
默认只需要将文件命名为:

schema-*.sql、data-*.sql
默认规则:schema.sql  schema-all.sql
可以使用
	schema:
		- classpath: department.sql
		// 指定位置

5.操作数据库:自动配置了jdbcTemplate操作数据库

整合Druid数据源

// 导入druid数据源
@Configuration
public class DruidConfig{
	@ConfigurationProperties(prefix="spring.datasource")
	@Bean
	public DataSource druid(){
		return new DruidDataSource();
	}

	// 配置druid的监控
	// 1.配置一个管理后台的Servlet
	@Bean
	public ServletRegistrationBean statViewServlet(){
		ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet,"/druid/**");
		Map<String,String> initParams = new HashMap<>();
		
		initParams.put("loginUsername","admin");
		initParams.put("loginPassword","123456");
		initParams.put("allow","");// 默认是允许访问的
		initParams.put("dency","127.0.0.1");

		bean.setInitParameters(initParams);
		return bean;
	}

	// 2.配置一个web监控的filter
	@Bean
	public FilterRegistrationBean webStatFilter(){
		FilterRegistrationBean bean = new FilterRegistrationBean();
		bean.setFilter(new WebStatFilter());

		Map<String,String> initParams = new HashMap<>();
		initParams.put("exclusions","*.js,*.css,/druid/*");
		bean.setInitParameters(initParams);
		bean.setUrlPatterns(Arrays.asList("/*"));

		return bean;
	}
}

整合MyBatis

引入依赖

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

步骤:
1.配置数据源相关属性
2.给数据库建表
3.创建javabean
4.注解版

// 指定这是一个操作数据库的mapper
@Mapper
public interface DepartmentMapper{
	@Select("select * from deparment where id=#{id}")
	public Department getDeptById(Integer id);

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

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

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

问题:
自定义MyBatis的配置规则(例如配置驼峰命名);给容器中添加一个ConfigurationCustomizer;

@org.springframework.context.annotation.Configuration
public class MyBatisConfig{
	@Bean
	public ConfigurationCustomizer configurationCustomizer(){
		return new ConfigurationCustomizer(){
			@Override
			public void customize(Configuration configuration){
				configuration.setMapUnderscoreToCameCase(true);
			}
		}
	}
}
// 使用MapperScan批量扫描所有的Mapper接口
@MapperScan(value="com.atguigu.springboot.mapper")
@SpringBootApplication
public class SpringBoot06DataMybatisApplication{
	public static void mian(String[] args){
		SpringApplication.run(SpringBoot06DataMybatisApplication.class,args);
	}
}

5.配置文件版

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

整合SpringData JPA

SpringData简介

JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,百度百科说是JDK为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。

整合SpringData JPA

JPA:ORM(Object Relational Mapping)

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

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

	// 省略默认列名就是属性名
	@Column
	private Stirng email;
}

2)、编写一个Dao接口来操作实体类对应的数据表(Repository)

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

3)、基本的配置JpaProperties

spring:
	jpa:
		hibernate:
			# 更新或撞见数据表结构
			ddl-auto: update
		# 控制台显示sql
		show-sql: true

启动配置原理

几个重要的时间回调机制
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener

只需要放在ioc容器中
ApplicationRunner
CommandLineRunner

1、创建SpringApplication对象

// 初始化方法
initialize(sources);

private void initialize(Object[] sources){
	// 保存主配置类
	if(sources!=null && sources.length>0){
		this.sources.addAll(Arrays.asList(sources));
	}
	// 判断当前是否一个web应用
	this.webEnviroment = deduceWebEnviroment();

	// 从类路径下,找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然后保存起来
	setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
	// 从类路径下,找到META-INF/spring.factories配置的所有ApplicationListener
	setListeners(Collection)getSpringFactoriesInstances(ApplicationListener.class));
	// 从多个配置类中找到有main方法的主配置类
	this.mainApplicationClass = deduceMainApplicationClass();
}

运行run方法

public ConfigurableApplicationContext run(String... args){
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();

	ConfigurableApplicationContext context = null;
	FailureAnalyzers analyzers = null;
	configureHeadlessProperty();

	// 获取SpringApplicationRunListeners;从类路径META-INF/spring.factories
	SpringApplicationRunListeners listeners = getRunListeners(args);
	// 回调所有的获取SpringApplicationRunListener.starting()方法
	listeners.starting();
	try{
		//封装命令参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//准备环境
		//创建环境完成后回调SpringApplicationRunListener.enviromentPrepared();表示环境准备完成
		ConfigurableEnviroment enviroment = prepareEnviroment(listeners,applicationArguments);
		Banner printBanner = printBanner(enviroment);
		//创建ApplicationContext;决定创建web的ioc韩式普通的ioc
		context = createApplicationContext();
		analyzers = new FailureAnalyzers(context);

		//准备上下文环境;将enviroment保存到ioc中;而且applyInitializers();
		//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
		//回调所欲的SpringApplicationRunListener的contextPrepared();
		prepareContext(context,enviroment.listeners,applicationArguments,printedBanner);
		// prepareContext运行完成以后,回调所有的SpringApplicationRunListener的contextLoaded()
		// 刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的tomcat)
		// 扫描,创建,加载所有组件的地方(配置类,组件,自动配置)
		refreshContext(context);
		// 从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
		// ApplicationRunner先回调,CommandLineRunner再回调
		afterRefresh(context,applicationArguments);
		//所有的SpringApplicationRunListeners回调finished方法
		listeners.finished(context,null);
		if(this.logStartupInfo){
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);
		}
		// 整个springBoot应用启动完成以后返回启动ioc容器
		return context;
	}catch(Throwable ex){
		handleRunFailure(context,listeners,analyzers,ex);
		throw new IllegalStateException(ex);
	}
	
}

事件监听机制

配置在META-INF/spring.factories
ApplicationContextInitializer

public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
	@Override
	public void initialize(ConfigurableApplicationContext applicationContext){
		System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
	}
}

SpringApplicationRunListener


public class HelloSpringApplicationRunListener implements SpringApplicationRunListener{

	// 必须有的构造器
	public HelloSpringApplicationRunListener(SpringApplication application,String[] args){
	}

	@Override
	public void starting(){
		System.out.println("SpringApplicationRunListener...starting...");
	}

	@Override
	public void enviromentPrepared(ConfigurableEnviroment enviroment){
		Object o = enviroment.getSystemProperties().get("os.name");
		System.out.println("SpringApplicationRunListener...enviromentPrepared..."+o);
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context){
		System.out.println("SpringApplicationRunListener...contextPrepared...");
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context){
		System.out.println("SpringApplicationRunListener...contextLoaded...");
	}

	@Override
	public void finished(ConfigurableApplicationContext context, Throwable exception){
		System.out.println("SpringApplicationRunListener...finished...");
	}
}

配置(META-INF/spring.factories)

org.springframework.context.ApplicationContextInitializer=com.atguigu.springboot.listener.HelloApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=com.atguigu.springboot.Listener.HelloSpringApplicationRunListener

只需要放在ioc容器中
ApplicationRunner

@Component
public class HelloApplicationRunner implements ApplicationRunner{
	@Override
	public void run(ApplicationArguments args)throws Exception{
		System.out.println("ApplicationRunner...run...");
	}
}

CommandLineRunner

@Component
public class HelloCommandLineRunner implements CommandLineRunner{
	@Override
	public void run(String... args)throws Exception{
		System.out.println("CommandLineRunner...run..."+Arrays.asList(args));
	}
}

自定义starter

starter:
1、这个场景需要使用到的依赖是什么
2、如何编写自动配置


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

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

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutConfiguration,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

3、模式:
启动器只用来做依赖导入;
专门来写一个自动配置模块;
启动器依赖自动配置;别人只需要引入启动器(starter)
mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter

步骤:
1、启动器模块


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	smlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
	http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.atguigu.starter</groupId>
	<artifactId>atguigu-spring-boot-starter</artifactId>
	<version>1.0-SNAPSHOT</version>

	<dependencies>
		<!--引入自动配置模块-->
		<dependency>
			<groupId>com.atguigu.starter</groupId>
			<artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>

</project>

2、自动配置模块


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	smlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
	http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.atguigu.starter</groupId>
	<artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>atguigu-spring-boot-starter-autoconfigurer</name>
	<description>Demo project for Spring Boot</description>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!--引入spring-boot-starter;所有starter的基本配置-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>

</project>
@ConfigurationProperties(prefix="atguigu.hello")
public class HelloProperties{
	private String prefix;
	private String suffix;

	//get
	//set
}
public class HelloServlet{
	HelloProperties helloProperties;

	public HelloProperties getHelloProperties(){
		return helloProperties;
	}

	public void setHelloProperties(HelloProperties helloProperties){
		this.helloProperties = helloProperties;
	}

	public String sayHelloAtguigu(String name){
		return helloProperties.getPrefix+"-"+name+helloProperties.getSuffix();
	}

}
@Configuration
@CondittionalOnWebApplication// web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration{
	@Autowired
	HelloProperties helloProperties;
	@Bean
	public HelloService helloService(){
		HelloService service = new HelloService();
		service.setHelloProperties(helloProperties);
		return service;
	}
}

Spring Boot 高级进级

尚硅谷springboot课堂笔记
1、Spring Boot尚硅谷笔记整理高级篇-缓存
2、Spring Boot尚硅谷笔记整理高级篇-消息
3、Spring Boot尚硅谷笔记整理高级篇-检索
4、Spring Boot尚硅谷笔记整理高级篇-任务
5、Spring Boot尚硅谷笔记整理高级篇-安全
6、Spring Boot尚硅谷笔记整理高级篇-分布式
7、Spring Boot尚硅谷笔记整理高级篇-热部署
8、Spring Boot尚硅谷笔记整理高级篇-监控

SpringBoot 2 学习笔记

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值