1、SpringBoot2学习路线说明

说明:SpringBoot2分为核心技术和响应式编程2大类。

  1. SpringBoot2核心技术
    基础篇:SpringBoot的快速掌握和基本原理。
    核心功能篇:配置文件,web开发,Juint5单元测试…
    场景整合:缓存技术,分布式入门…

  2. SpringBoot2响应式编程:SpringBoot以前的基础开发都可以使用另一套方案响应式编程来替代,所谓的响应式编程即:解决一个问题,如何使用少量的资源能编写一个极高的吞吐量,承担大并发的一个应用。
    响应式编程:是未来的流行趋势,spring一直在大力推广。

1.1、SpringBoot2核心技术

1.2、SpringBoot2响应式编程

2、SpringBoot2核心技术-基础入门

2.1、SpringBoot2学习的前提

学习要求
● 熟悉Spring基础
● 熟悉Maven使用

环境要求
● Java8及以上
● Maven 3.3及以上

2.2、Spring与SpringBoot

2.2.1、Spring能做什么

(1)Spring的能力

(2)Spring的生态
https://spring.io/projects/spring-boot

  • 覆盖了:
  • web开发
  • 数据访问
  • 安全控制
  • 分布式
  • 消息服务
  • 移动开发
  • 批处理

(3)Spring5重大升级

响应式编程:

内部源码设计:

基于Java8的一些新特性,如:接口默认实现。重新设计源码架构。

2.2.2、为什么用SpringBoot

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

能快速创建出生产级别的Spring应用

(1)SpringBoot优点

  • Create stand-alone Spring applications
    ○ 创建独立Spring应用
  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
    ○ 内嵌web服务器
  • Provide opinionated ‘starter’ dependencies to simplify your build configuration
    ○ 自动starter依赖,简化构建配置
  • Automatically configure Spring and 3rd party libraries whenever possible
    ○ 自动配置Spring以及第三方功能
  • Provide production-ready features such as metrics, health checks, and externalized configuration
    ○ 提供生产级别的监控、健康检查及外部化配置
  • Absolutely no code generation and no requirement for XML configuration
    ○ 无代码生成、无需编写XML

SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架

(2)SpringBoot缺点

  • 人称版本帝,迭代快,需要时刻关注变化
  • 封装太深,内部原理复杂,不容易精通

2.2.3、时代背景

(1)微服务
James Lewis and Martin Fowler (2014) 提出微服务完整概念。https://martinfowler.com/microservices/

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.-- James Lewis and Martin Fowler (2014)

  • 微服务是一种架构风格
  • 一个应用拆分为一组小型服务
  • 每个服务运行在自己的进程内,也就是可独立部署和升级
  • 服务之间使用轻量级HTTP交互
  • 服务围绕业务功能拆分
  • 可以由全自动部署机制独立部署
  • 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

(2)分布式

分布式的困难

  • 远程调用
  • 服务发现
  • 负载均衡
  • 服务容错
  • 配置管理
  • 服务监控
  • 链路追踪
  • 日志管理
  • 任务调度

分布式的解决

  • SpringBoot + SpringCloud

(3)云原生

原生应用如何上云。 Cloud Native

上云的困难

  • 服务自愈
  • 弹性伸缩
  • 服务隔离
  • 自动化部署
  • 灰度发布
  • 流量治理

上云的解决

2.2.4、如何学习SpringBoot

(1)官网文档架构



查看版本新特性:

https://github.com/spring-projects/spring-boot/wiki#release-notes

2.3、SpringBoot2入门

2.3.1、系统要求

  • Java 8 & 兼容java14 .
  • Maven 3.3+
  • idea 2019.1.2

(1)maven设置

说明:配置阿里云镜像,设置Maven使用jdk1.8进行项目编译。

<mirrors><mirror><id>nexus-aliyun</id><mirrorOf>central</mirrorOf><name>Nexus aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public</url></mirror></mirrors><profiles><profile><id>jdk-1.8</id><activation><activeByDefault>true</activeByDefault><jdk>1.8</jdk></activation><properties><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion></properties></profile></profiles>

2.3.2、HelloWorld

需求:浏览发送/hello请求,响应 Hello,Spring Boot 2

(1)创建maven工程

(2)引入依赖

 <!--想要使用springboot的功能,就必须引入springboot的父项目--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version></parent><dependencies><!--想要开发web,就要引入web场景--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>

(3)创建主程序

/*** 主程序类;可以看成是一个主配置类* @SpringBootApplication:这是一个SpringBoot应用*/
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}
}

(4)编写业务

//@ResponseBody
//@Controller@RestController
public class HelloController {@RequestMapping("/hello")public String handle01(){return "Hello, Spring Boot 2!";}}

(5)测试

直接运行main方法

(6)简化配置

application.properties

server.port=8888


(7)简化部署

说明:如果是以前把项目部署到目标服务器上,目标服务器需要安装tomact等一大堆配置。而现在只需要引入这个springboot给我们提供的一个插件,就可以直接把这个项目打成一个jar包,这个jar包自带了我们整套的运行环境,所以我们可以直接运行。

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

把项目打成jar包,直接在目标服务器执行即可。

注意点:

  • 取消掉cmd的快速编辑模式

2.4、了解自动配置原理

2.4.1、SpringBoot特点

(1)依赖管理

  • 父项目做依赖管理
依赖管理
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version>
</parent>它的父项目<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.3.4.RELEASE</version></parent>几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
  • 开发导入starter场景启动器
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope>
</dependency>
  • 无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
  • 可以修改默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置<properties><mysql.version>5.1.43</mysql.version></properties>

(2) 自动配置

  • 自动配好Tomcat
    ○ 引入Tomcat依赖。引入web依赖就会自动依赖Tomcat依赖。
    ○ 配置Tomcat

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope></dependency>
  • 自动配好SpringMVC, 前提是引入web依赖
    ○ 引入SpringMVC全套组件
    ○ 自动配好SpringMVC常用组件(功能)

package com.atguigu.boot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;/*** 主程序类* @SpringBootApplication:这是一个SpringBoot应用*/
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {//1、返回我们IOC容器,这个容器包含了当前应用的所以组件。ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);//2、查看容器里面的组件String[] names = run.getBeanDefinitionNames();//获取所有组件的名字for (String name : names) {System.out.println(name);}}
}

  • 自动配好Web常见功能,如:字符编码问题
    ○ SpringBoot帮我们配置好了所有web开发的常见场景

  • 默认的包结构
    ○ 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    ○ 无需以前的包扫描配置
    ○ 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.atguigu”)

    • 或者@ComponentScan 指定扫描路径

@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
  • 各种配置拥有默认值
    ○ 默认配置最终都是映射到某个类上,如:MultipartProperties
    ○ 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象


  • 按需加载所有自动配置项
    ○ 非常多的starter
    ○ 引入了哪些场景这个场景的自动配置才会开启
    ○ SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

2.4.2、容器功能

准备实体类:

(1)组件添加

(1.1)@Configuration
说明:代替之前的spring框架中的xml方式创建对象
原先写法:

  • 基本使用
  • Full模式与Lite模式
    ○ 示例
    ○ 最佳实战

    • 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断(多实例)
    • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(单实例)
      测试步骤:
      添加依赖关系:

      配置类:

      主启动类:
#############################Configuration使用示例######################################################
/*** 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的* 2、配置类本身也是组件* 3、proxyBeanMethods:代理bean的方法*      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】*      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】*      组件依赖必须使用Full模式默认。其他默认是否Lite模式****/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == xml配置文件
public class MyConfig {/*** Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象* @return*/@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例public User user01(){User zhangsan = new User("zhangsan", 18);//user组件依赖了Pet组件zhangsan.setPet(tomcatPet());return zhangsan;}@Bean("tom")//自定义Bean的名字public Pet tomcatPet(){return new Pet("tomcat");}
}################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {public static void main(String[] args) {//1、返回我们IOC容器ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);//2、查看容器里面的组件String[] names = run.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}//3、从容器中获取组件Pet tom01 = run.getBean("tom", Pet.class);Pet tom02 = run.getBean("tom", Pet.class);System.out.println("组件:"+(tom01 == tom02));//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892MyConfig bean = run.getBean(MyConfig.class);System.out.println(bean);//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。//保持组件单实例User user = bean.user01();User user1 = bean.user01();System.out.println(user == user1);User user01 = run.getBean("user01", User.class);Pet tom = run.getBean("tom", Pet.class);System.out.println("用户的宠物:"+(user01.getPet() == tom));}
}

(1.2)@Bean、@Component、@Controller、@Service、@Repository
说明:之前的spring框架的注解方式创建对象,仍然可以使用。即:包扫描+注解方式,只不过包扫描现在默认是主启动类所在的包以及其子包中,当然也可修改包扫描的路径,2种方式修改。

(1.3)@ComponentScan、@Import
说明:

  1. @ComponentScan配置包扫描,已经讲过了,略。
  2. @Import:给容器中导入一个组件,可以写在任何一个配置类或组件(controller、srvice…)里面 spring注解驱动开发讲过了,略。
 * 4@Import({User.class, DBHelper.class})*      给容器中自动创建出这两个类型的组件(对象)、默认组件的名字就是全类名*      可以是自己创建的类,也可以是引入的第三方jar包里面的类***/@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}



@Import 高级用法: https://www.bilibili.com/video/BV1gW411W7wy?p=8

(1.4)@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入

测试:
配置类:

主启动类:

=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom") //加在类上,当条件成立时,下边的所有配置才生效。
@ConditionalOnMissingBean(name = "tom") //容器中没有tom时,添加组件
public class MyConfig {/*** Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象* @return*/@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例//@ConditionalOnBean(name = "tom")//加在方法上,当满足条件时,组件才会被注册到容器中public User user01(){User zhangsan = new User("zhangsan", 18);//user组件依赖了Pet组件zhangsan.setPet(tomcatPet());return zhangsan;}@Bean("tom22")public Pet tomcatPet(){return new Pet("tomcat");}
}
----------------------主启动类---------------------
public static void main(String[] args) {//1、返回我们IOC容器ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);//2、查看容器里面的组件String[] names = run.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}boolean tom = run.containsBean("tom");System.out.println("容器中Tom组件:"+tom);boolean user01 = run.containsBean("user01");System.out.println("容器中user01组件:"+user01);boolean tom22 = run.containsBean("tom22");System.out.println("容器中tom22组件:"+tom22);}

(2)原生配置文件引入
说明:公司里面很对老项目使用的还是xml方式注册组件,现在一个个的改为注解方式太麻烦,可以使用@ImportResource注解直接把xml文件解析放到容器中。
(2.1)@ImportResource


======================beans.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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><bean id="haha" class="com.atguigu.boot.bean.User"><property name="name" value="zhangsan"></property><property name="age" value="18"></property></bean><bean id="hehe" class="com.atguigu.boot.bean.Pet"><property name="name" value="tomcat"></property></bean>
</beans>
/**** 5、@ImportResource("classpath:beans.xml")导入Spring的配置文件*/
@ImportResource("classpath:beans.xml")
public class MyConfig {}======================测试=================boolean haha = run.containsBean("haha");boolean hehe = run.containsBean("hehe");System.out.println("haha:"+haha);//trueSystem.out.println("hehe:"+hehe);//true

(3)配置绑定
说明:把经常变化的东西配置到配置文件中,比如数据库连接信息,之后在一一解析到JavaBean中,这个过程如果用原生的java代码实现会非常麻烦,springboot提供了对应的注解进行实现。。

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

//原生方式:使用java代码读取
public class getProperties {public static void main(String[] args) throws FileNotFoundException, IOException {Properties pps = new Properties();pps.load(new FileInputStream("a.properties"));Enumeration enum1 = pps.propertyNames();//得到配置文件的名字while(enum1.hasMoreElements()) {String strKey = (String) enum1.nextElement();String strValue = pps.getProperty(strKey);System.out.println(strKey + "=" + strValue);//封装到JavaBean。}}}

(3.1)@Component + @ConfigurationProperties
配置文件:

实体类:

测试:启动主启动类,访问控制层方法路径。

mycar.brand=YD
mycar.price=100000
---------------------------------------------
package com.atguigu.boot.bean;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** 只有在容器中的组件,才会拥有SpringBoot提供的强大功能*/
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {private String brand;private Integer price;public Car() {}public Car(String brand, Integer price) {this.brand = brand;this.price = price;}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Integer getPrice() {return price;}public void setPrice(Integer price) {this.price = price;}@Overridepublic String toString() {return "Car{" +"brand='" + brand + '\'' +", price=" + price +'}';}
}
-------------------------------------------
@RestController
public class HelloController {@AutowiredCar car;@RequestMapping("/car")public Car car(){return car;}}

(3.2)@EnableConfigurationProperties + @ConfigurationProperties
说明:因为实体类可能用的是第三方包里面的,可能没有加@Component,你也不能擅自加上,此时就可以使用这种方式。

修改实体类:

修改配置类:

仍然访问成功:

2.4.3、自动配置原理入门

(1)引导加载自动配置类

//主启动类的@SpringBootApplication注解,相当于以下3个注解的组合。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}======================

(1.1)@SpringBootConfiguration

此注解的底层是@Configuration。代表当前是一个配置类

(1.2)@ComponentScan

指定扫描哪些,Spring注解;具体查看spring注解驱动开发。

(1.3)@EnableAutoConfiguration
它也是个合成注解

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

(1.3.1)@AutoConfigurationPackage

自动配置包?指定了默认的包规则

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

(1.3.2)@Import(AutoConfigurationImportSelector.class)

1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# 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.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
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.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
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.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
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.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
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.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
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.r2dbc.R2dbcAutoConfiguration,\
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

(2)按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载。但是会根据注解xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。

(3)修改默认配置

        @Bean@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件public MultipartResolver multipartResolver(MultipartResolver resolver) {//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范// Detect if the user has created a MultipartResolver but named it incorrectlyreturn resolver;}
给容器中加入了文件上传解析器;

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

@Bean@ConditionalOnMissingBeanpublic CharacterEncodingFilter characterEncodingFilter() {}

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    ○ 用户直接自己@Bean替换底层的组件
    ○ 用户去看这个组件是获取的配置文件什么值就去修改。
    xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

(4)最佳实践

  • 引入场景依赖
    ○ https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

  • 查看自动配置了哪些(选做)
    ○ 自己分析,引入场景对应的自动配置一般都生效了
    ○ 配置文件中:debug=true开启自动配置报告。Negative(不生效)\Positive(生效)

  • 是否需要修改
    ○ 参照文档修改配置项

    • https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
    • 自己分析。xxxxProperties绑定了配置文件的哪些。如:修改banner图。

    ○ 自定义加入或者替换组件

    • @Bean、@Component。。。
      ○ 自定义器 XXXXXCustomizer;
      ○ …

2.4.4、开发小技巧(从这开始一步不落下)

(1)Lombok

说明:简化JavaBean开发,可以简写get、set、构造方法等等,程序在编译的时候会自动生成对应的方法。

步骤1:首先引入依赖:

  <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>

步骤2:idea中搜索安装lombok插件

===============================简化JavaBean开发===================================
package com.atguigu.boot.bean;import lombok.*;//@AllArgsConstructor //全参构造器
@NoArgsConstructor  //无参构造器
@Data //生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@ToString //用于为描述的类添加toString方法。
@EqualsAndHashCode //用于为描述的类,生成hashCode和equals方法。
public class User {private String name;private Integer age;private Pet pet;public User(String name, Integer age) {this.name = name;this.age = age;}
}================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {@RequestMapping("/hello")public String handle01(@RequestParam("name") String name){log.info("请求进来了....");return "Hello, Spring Boot 2!"+"你好:"+name;}
}



(2)dev-tools
说明:配置热部署。
步骤1:引入依赖。

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

步骤2:
项目或者页面修改以后:Ctrl+F9;

(3)Spring Initailizr(项目初始化向导)
说明:开发springboot应用,开发什么场景需要引入对应的依赖,还要参照官方文档查看需要支持那些场景,之后创建Maven工程把依赖引入进来。现在indea或者eclipse提供了Spring Initailizr。可以帮助我们快速的创建springboot项目。

(3.1)选择我们需要的开发场景


(3.2)自动依赖引入

(3.3)自动创建项目结构

(3.4)自动编写好主配置类

3、SpringBoot2核心技术-核心功能

3.1、配置文件

3.1.1、文件类型

(1)properties

同以前的properties用法

优先级高于yml的方式。
(2)yaml

(2.1)简介

YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

(2.2)基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
  • 后缀可以是yaml或者yml

(2.3)数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
  • 对象:键值对的集合。map、hash、set、object
行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: k1: v1k2: v2k3: v3
  • 数组:一组按次序排列的值。array、list、queue
行内写法:  k: [v1,v2,v3]
#或者
k:- v1- v2- v3

(2.4)示例


@ConfigurationProperties(prefix ="person")
@Component
@Data
public class Person {private String userName;private Boolean boss;private Date birth;private Integer age;private Pet pet;private String[] interests;private List<String> animal;private Map<String, Object> score;private Set<Double> salarys;private Map<String, List<Pet>> allPets;
}
-----------------------------------
@Data
public class Pet {private String name;private Double weight;
}

person:#  单引号会将 \n作为字符串输出   双引号会将\n 作为换行输出#  双引号不会转义,单引号会转义boss: truebirth: 2019/12/9age: 18#  interests: [篮球,足球]interests:- 篮球- 足球- 18animal: [阿猫,阿狗]#  score:#    english: 80#    math: 90score: {english:80,math:90}salarys:- 9999.98- 9999.99pet:name: 阿狗weight: 99.99allPets:sick:- {name: 阿狗,weight: 99.99}- name: 阿猫weight: 88.88- name: 阿虫weight: 77.77health:- {name: 阿花,weight: 199.99}- {name: 阿明,weight: 199.99}user-name: zhangsan#spring:
#  banner:
#    image:
#      bitdepth: 4
#  cache:
#    type: redis
#    redis:
#      time-to-live: 11000

控制层代码:

package com.atguigu.boot.controller;import com.atguigu.boot.bean.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@AutowiredPerson person;@RequestMapping("/person")public Person person(){String userName = person.getUserName();System.out.println(userName);return person;}}


启动主启动类进行访问测试:

3.1.2、配置提示

说明:在yml文件中,配置springboot提供的属性有提示,而自己写的属性没有提示,不好。如何让自己写的属性也有提示呢???

自定义的类和配置文件绑定一般没有提示。
解决:

  1. 导入依赖。
  2. 因为这个配置处理器只是为了开发方便,与业务无关。所以建议在打包的插件中配置在项目打包时忽略掉配置处理器。

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></exclude></excludes></configuration></plugin></plugins></build>

3.2、Web开发

3.2.1、SpringMVC自动配置概览


Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    ○ 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document).
    ○ 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
    ○ 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).
    ○ 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).
    ○ 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.
    ○ 静态index.html 页支持
  • Custom Favicon support (covered later in this document).
    ○ 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
    ○ 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
声明 WebMvcRegistrations 改变默认底层组件

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

3.2.2、简单功能分析

创建项目:

添加依赖:

(1)静态资源访问

(1.1)静态资源目录

只要静态资源放在类路径下:static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名


原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

默认在以上这4个目录下,想要改变默认的静态资源路径需以下配置:这样现在的静态资源文件只能放在haha目录下,放在默认的4个目录下就访问不了了。

spring:web:resources:static-locations:[classpath:/haha/]#因为是数组,所以可以配置多个,用逗号隔开如: [classpath:/haha/,/aa/]

(1.2)静态资源访问前缀
说明:建议静态资源访问时加上前缀,比如:一个web应用有很多静态资源和动态资源,项目中有很多拦截器,只有登录后才能访问一些动态请求,拦截器拦截/**相当于是把静态资源也拦截了,为了拦截器配置方便,把静态资源访问都加上前缀,这样拦截器放行以指定前缀路径开始的所求请求,这样就非常方便过滤掉静态资源。

默认无前缀

spring:mvc:static-path-pattern: /res/**

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

(1.3)webjar

说明:它把js、css封装成了一个个的jar包,我们想用的时候只需要导入依赖即可。

自动映射 /webjars/**

官方地址:https://www.webjars.org/

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

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

(2)欢迎页支持

  • 方式一:静态资源路径下 index.html
    ○ 可以配置静态资源路径
    ○ 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效resources:static-locations: [classpath:/haha/]

index.html页面:

  • 方式二:controller能处理/index
    自己编写控制层方法,略。

(3)自定义Favicon

说明:访问网站时的小图标。
把一个图片放在静态资源目录下,名字为固定写法:favicon.ico

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效


(4)静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 给容器中配了什么。
 @Configuration(proxyBeanMethods = false)@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })@Order(0)public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和xxx进行了绑定。WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources

(4.1)配置类只有一个有参构造器

 //有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean   给应用注册Servlet、Filter....public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,ObjectProvider<DispatcherServletPath> dispatcherServletPath,ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {this.resourceProperties = resourceProperties;this.mvcProperties = mvcProperties;this.beanFactory = beanFactory;this.messageConvertersProvider = messageConvertersProvider;this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();this.dispatcherServletPath = dispatcherServletPath;this.servletRegistrations = servletRegistrations;}

(4.2)资源处理的默认规则

@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {if (!this.resourceProperties.isAddMappings()) {logger.debug("Default resource handling disabled");return;}Duration cachePeriod = this.resourceProperties.getCache().getPeriod();CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();//webjars的规则if (!registry.hasMappingForPattern("/webjars/**")) {customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));}//String staticPathPattern = this.mvcProperties.getStaticPathPattern();if (!registry.hasMappingForPattern(staticPathPattern)) {customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));}}

spring:web:resources:add-mappings: false  禁用所有静态资源规则,默认为true

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };/*** Locations of static resources. Defaults to classpath:[/META-INF/resources/,* /resources/, /static/, /public/].*/private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

(4.3)欢迎页的处理规则

 HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。   @Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());return welcomePageHandlerMapping;}WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {//要用欢迎页功能,必须是/**logger.info("Adding welcome page: " + welcomePage.get());setRootViewName("forward:index.html");}else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {// 调用Controller  /indexlogger.info("Adding welcome page template: index");setRootViewName("index");}}

(4.4)favicon

3.2.3、请求参数处理

(1)请求映射
(1.1)rest使用与原理

  • @xxxMapping;

  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
    ○ 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
    现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    ○ 核心Filter;HiddenHttpMethodFilter过滤器

    • 用法: 表单method=post,隐藏域 _method=put
    • 在SpringBoot中的yml配置文件中手动开启(springmvc需要在xml中配置HiddenHttpMethodFilter过滤器,而在SpringBoot中默认配置的有过滤器,但是要在yml/properties配置文件中手动开启。)

    ○ 扩展:如何把_method 这个名字换成我们自己喜欢的。

测试:
index.html页面:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>atguigu,欢迎您</h1>
测试REST风格;
<form action="/user" method="get"><input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post"><input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post"><input name="_method" type="hidden" value="delete"/><input name="_m" type="hidden" value="delete"/><input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post"><input name="_method" type="hidden" value="PUT"/><input value="REST-PUT 提交" type="submit"/>
</form>
<hr/>
测试基本注解:
<ul><a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a><li>@PathVariable(路径变量)</li><li>@RequestHeader(获取请求头)</li><li>@RequestParam(获取请求参数)</li><li>@CookieValue(获取cookie值)</li><li>@RequestBody(获取请求体[POST]</li><li>@RequestAttribute(获取request域属性)</li><li>@MatrixVariable(矩阵变量)</li>
</ul>/cars/{path}?xxx=xxx&aaa=ccc queryString 查询字符串。@RequestParam<br/>
/cars/sell;low=34;brand=byd,audi,yd  ;矩阵变量 <br/>
页面开发,cookie禁用了,session里面的内容怎么使用;
session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。
url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递./boss/1/2/boss/1;age=20/2;age=20<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
<br/>
<form action="/save" method="post">测试@RequestBody获取数据 <br/>用户名:<input name="userName"/> <br>邮箱:<input name="email"/><input type="submit" value="提交"/>
</form>
<ol><li>矩阵变量需要在SpringBoot中手动开启</li><li>根据RFC3986的规范,矩阵变量应当绑定在路径变量中!</li><li>若是有多个矩阵变量,应当使用英文符号;进行分隔。</li><li>若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li><li>如:/cars/sell;low=34;brand=byd,audi,yd</li>
</ol>
<hr/>
测试原生API:
<a href="/testapi">测试原生API</a>
<hr/>
测试复杂类型:<hr/>
测试封装POJO;
<form action="/saveuser" method="post">姓名: <input name="userName" value="zhangsan"/> <br/>年龄: <input name="age" value="18"/> <br/>生日: <input name="birth" value="2019/12/10"/> <br/><!--    宠物姓名:<input name="pet.name" value="阿猫"/><br/>--><!--    宠物年龄:<input name="pet.age" value="5"/>-->宠物: <input name="pet" value="啊猫,3"/><input type="submit" value="保存"/>
</form><br>
</body>
</html>

控制层方法:


package com.atguigu.boot.controller;import org.springframework.web.bind.annotation.*;@RestController
public class HelloController {//    @RequestMapping(value = "/user",method = RequestMethod.GET)@GetMapping("/user")//简化写法public String getUser(){return "GET-张三";}//    @RequestMapping(value = "/user",method = RequestMethod.POST)@PostMapping("/user")public String saveUser(){return "POST-张三";}//    @RequestMapping(value = "/user",method = RequestMethod.PUT)@PutMapping("/user")public String putUser(){return "PUT-张三";}@DeleteMapping("/user")
//    @RequestMapping(value = "/user",method = RequestMethod.DELETE)public String deleteUser(){return "DELETE-张三";}//扩展点:如何把 _method 这个名字换成我们自己喜欢的
}----------------------------底层源码------------@Bean@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();}

Rest原理(表单提交要使用REST的时候

  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
    ○ 请求是否正常,并且是POST

    • 获取到_method的值。
    • 兼容以下请求;PUT.DELETE.PATCH
    • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
    • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

Rest使用客户端工具,

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。
# 说明:这一项配置是选择性开启,因为如果是前后端分离,我们不做前端开发,只需要返回json数据就行了,也就不需要配置。
spring:mvc:hiddenmethod:filter:enabled: true   #开启页面表单的Rest功能

访问页面:


此外还可以修改这个参数_method为其它自定义的参数。
添加配置类:

package com.atguigu.boot.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;@Configuration(proxyBeanMethods = false)//组件没有依赖关系,快速放
public class WebConfig /*implements WebMvcConfigurer*/ {@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();methodFilter.setMethodParam("_m");return methodFilter;}}

测试index.html页面必须换为_m才能访问成功。


(1.2)请求映射原理

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 找到当前请求使用哪个Handler(Controller的方法)处理mappedHandler = getHandler(processedRequest);//HandlerMapping:处理器映射。/xxx->>xxxx


RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html(只不过在浏览器输入地址时:http://localhost:8080/,这个/会被省略掉);
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
    ○ 如果有就找到这个请求对应的handler
    ○ 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}

(2)普通参数与基本注解
(2.1)注解:
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

测试:
index.html页面:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>atguigu,欢迎您</h1>
测试REST风格;
<form action="/user" method="get"><input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post"><input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post"><input name="_method" type="hidden" value="delete"/><input name="_m" type="hidden" value="delete"/><input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post"><input name="_method" type="hidden" value="PUT"/><input value="REST-PUT 提交" type="submit"/>
</form>
<hr/>
测试基本注解:
<ul><a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a><li>@PathVariable(路径变量)</li><li>@RequestHeader(获取请求头)</li><li>@RequestParam(获取请求参数)</li><li>@CookieValue(获取cookie值)</li><li>@RequestBody(获取请求体[POST]</li><li>@RequestAttribute(获取request域属性)</li><li>@MatrixVariable(矩阵变量)</li>
</ul>/cars/{path}?xxx=xxx&aaa=ccc queryString 查询字符串。@RequestParam<br/>
/cars/sell;low=34;brand=byd,audi,yd  ;矩阵变量 <br/>
页面开发,cookie禁用了,session里面的内容怎么使用;
session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。
url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递./boss/1/2/boss/1;age=20/2;age=20<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
<br/>
<form action="/save" method="post">测试@RequestBody获取数据 <br/>用户名:<input name="userName"/> <br>邮箱:<input name="email"/><input type="submit" value="提交"/>
</form>
<ol><li>矩阵变量需要在SpringBoot中手动开启</li><li>根据RFC3986的规范,矩阵变量应当绑定在路径变量中!</li><li>若是有多个矩阵变量,应当使用英文符号;进行分隔。</li><li>若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li><li>如:/cars/sell;low=34;brand=byd,audi,yd</li>
</ol>
<hr/>
测试原生API:
<a href="/testapi">测试原生API</a>
<hr/>
测试复杂类型:<hr/>
测试封装POJO;
<form action="/saveuser" method="post">姓名: <input name="userName" value="zhangsan"/> <br/>年龄: <input name="age" value="18"/> <br/>生日: <input name="birth" value="2019/12/10"/> <br/><!--    宠物姓名:<input name="pet.name" value="阿猫"/><br/>--><!--    宠物年龄:<input name="pet.age" value="5"/>-->宠物: <input name="pet" value="啊猫,3"/><input type="submit" value="保存"/>
</form><br>
</body>
</html>

测试1@PathVariable、@RequestHeader、@RequestParam、@CookieValue、@RequestBody

ParameterTestController类:

package com.atguigu.boot.controller;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
public class ParameterTestController {//  car/2/owner/zhangsan@GetMapping("/car/{id}/owner/{username}")//@PathVariable:获取restful风格的请求参数://     1.可以单个的获取//     2.也可以直接获取一个Map集合//@RequestHeader:获取请求头信息//     1.可以单个的获取//     2.也可以一次性获取所有的请求头信息//@RequestParam:获取普通的请求参数,如果请求参数名和方法的形参名相同时可以省略。//     1.获取一个参数//     2.获取的同名的参数有多个值。通过List集合或可变形参或数组//     3.获取所有的请求参数,包括参数只有一个值和参数有多个值//@CookieValue:获取Cookie里面的值://     1.单独获取一个Cookie里面Idea-9ccb666a的value值//     2.单独获取一个Cookie里面Idea-9ccb666a的整个信息public Map<String,Object> getCar(@PathVariable("id") Integer id,@PathVariable("username") String name,@PathVariable Map<String,String> pv,@RequestHeader("User-Agent") String userAgent,@RequestHeader Map<String,String> header,@RequestParam("age") Integer age,@RequestParam("inters") List<String> inters,@RequestParam Map<String,String> params,@CookieValue("Idea-9ccb666a") String idea,@CookieValue("Idea-9ccb666a") Cookie cookie){Map<String,Object> map = new HashMap<>();//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);map.put("age",age);map.put("inters",inters);map.put("params",params);System.out.println(cookie.getName()+"===>"+cookie.getValue());return map;}//@RequestBody:获取请求体的信息@PostMapping("/save")public Map postMethod(@RequestBody String content){Map<String,Object> map = new HashMap<>();map.put("content",content);return map;}}





测试2@RequestAttribute
RequestController类:

package com.atguigu.boot.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;@Controller
public class RequestController {@GetMapping("/goto")public String goToPage(HttpServletRequest request){request.setAttribute("msg","成功了...");request.setAttribute("code",200);//现在还没有学习模板引擎,不能进行前端开发,所以模拟一个跳转页面return "forward:/success";  //转发到  /success请求}@ResponseBody@GetMapping("/success")//@RequestAttribute:获取request域属性的值://    1.可以在方法的参数上,使用@RequestAttribute注解//    2.也可以直接通过原生的servletApi的方式获取。因为是请求转发用的是同一个域对象public Map success(@RequestAttribute(value = "msg",required = false) String msg,@RequestAttribute(value = "code",required = false)Integer code,HttpServletRequest request){Object msg1 = request.getAttribute("msg");Map<String,Object> map = new HashMap<>();map.put("reqMethod_msg",msg1);map.put("annotation_msg",msg);return map;}}

测试3@MatrixVariable:获取矩阵变量的值

package com.atguigu.boot.controller;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
public class ParameterTestController {//@MatrixVariable:获取矩阵变量的值//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd//2、SpringBoot默认是禁用了矩阵变量的功能//      手动开启(2种):原理。对于路径的处理。UrlPathHelper进行解析。//              removeSemicolonContent(移除分号内容)支持矩阵变量的//3、矩阵变量必须有url路径变量才能被解析@GetMapping("/cars/{path}")public Map carsSell(@MatrixVariable("low") Integer low,@MatrixVariable("brand") List<String> brand,@PathVariable("path") String path){Map<String,Object> map = new HashMap<>();map.put("low",low);map.put("brand",brand);map.put("path",path);return map;}// 矩阵变量获取相同名字的参数:/boss/1;age=20/2;age=10@GetMapping("/boss/{bossId}/{empId}")public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){Map<String,Object> map = new HashMap<>();map.put("bossAge",bossAge);map.put("empAge",empAge);return map;}
}

配置类:

package com.atguigu.boot.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;@Configuration(proxyBeanMethods = false)//组件没有依赖关系,快速放
public class WebConfig /*implements WebMvcConfigurer*/ {//方式一:使用@Bean 的方式返回一个 WebMvcConfigurer类型的组件public WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();// 不移除;后面的内容。矩阵变量功能就可以生效urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}};}//方式二:实现WebMvcConfigurer接口,重写对应的方法。
//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//
//        UrlPathHelper urlPathHelper = new UrlPathHelper();
//        // 不移除;后面的内容。矩阵变量功能就可以生效
//        urlPathHelper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(urlPathHelper);
//    }}

(2.2)Servlet API:

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

@Overridepublic boolean supportsParameter(MethodParameter parameter) {Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||MultipartRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||Principal.class.isAssignableFrom(paramType) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);}

(2.3)复杂参数:
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map
mavContainer.getModel(); 获取到值的


(2.4)自定义对象参数:
可以自动类型转换与格式化,可以级联封装。

/***     姓名: <input name="userName"/> <br/>*     年龄: <input name="age"/> <br/>*     生日: <input name="birth"/> <br/>*     宠物姓名:<input name="pet.name"/><br/>*     宠物年龄:<input name="pet.age"/>*/
@Data
public class Person {private String userName;private Integer age;private Date birth;private Pet pet;}@Data
public class Pet {private String name;private String age;}result

(3)POJO封装过程

  • ServletModelAttributeMethodProcessor

(4)参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值

(4.1)HandlerAdapter

0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx
(4.2)执行目标方法

// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

(4.3)参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

  • 当前解析器是否支持解析这种参数
  • 支持就调用 resolveArgument

(5)返回值处理器

(6)如何确定目标方法每一个参数的值

============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}

(6.1)挨个判断所有参数解析器那个支持解析这个参数

 @Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}

(6.2)解析这个参数的值

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可

(6.3)自定义类型参数 封装POJO
ServletModelAttributeMethodProcessor 这个参数处理器支持是否为简单类型。

public static boolean isSimpleValueType(Class<?> type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||CharSequence.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) ||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type ||Locale.class == type ||Class.class == type));}
@Override@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");String name = ModelFactory.getNameForParameter(parameter);ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);if (ann != null) {mavContainer.setBinding(name, ann.binding());}Object attribute = null;BindingResult bindingResult = null;if (mavContainer.containsAttribute(name)) {attribute = mavContainer.getModel().get(name);}else {// Create attribute instancetry {attribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {if (isBindExceptionRequired(parameter)) {// No BindingResult parameter -> fail with BindExceptionthrow ex;}// Otherwise, expose null/empty value and associated BindingResultif (parameter.getParameterType() == Optional.class) {attribute = Optional.empty();}bindingResult = ex.getBindingResult();}}if (bindingResult == null) {// Bean property binding and validation;// skipped in case of binding failure on construction.WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {bindRequestParameters(binder, webRequest);}validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}// Value type adaptation, also covering java.util.Optionalif (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}// Add resolved attribute and BindingResult at the end of the modelMap<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file

@FunctionalInterfacepublic interface Converter<S, T>


未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber<T extends Number> implements Converter<String, T>

自定义 Converter

    //1、WebMvcConfigurer定制化SpringMVC的功能@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();// 不移除;后面的内容。矩阵变量功能就可以生效urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Pet>() {@Overridepublic Pet convert(String source) {// 啊猫,3if(!StringUtils.isEmpty(source)){Pet pet = new Pet();String[] split = source.split(",");pet.setName(split[0]);pet.setAge(Integer.parseInt(split[1]));return pet;}return null;}});}};}

(7)目标方法执行完成
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。

(8)处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

InternalResourceView@Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}rd.forward(request, response);}}
暴露模型作为请求域属性
// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有数据遍历挨个放在请求域中model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}

3.2.4、数据响应与内容协商

(1)响应JSON
(1.1)jackson.jar+@ResponseBody
说明:想要返回json数据,必须引入json依赖+@ResponseBody注解,引入web依赖会自动关联json依赖。

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>web场景自动引入了json场景:<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope></dependency>


给前端自动返回json数据;

(1.1.1)返回值解析器

try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}
 @Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}
RequestResponseBodyMethodProcessor
@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.// 使用消息转换器进行写出操作writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}

(1.1.2)返回值解析器原理

  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 2、返回值处理器调用 handleReturnValue 进行处理
  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    ○ 1. 利用 MessageConverters 进行处理 将数据写为json

    • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
    • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
    • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
      • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
      • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

(1.2)SpringMVC到底支持哪些返回值

ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask@ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor

(1.3)HTTPMessageConverter原理
(1.3.1)MessageConverter规范

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person

(1.3.2)默认的MessageConverter

0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

(2)内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
(2.1)引入xml依赖

 <dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>

(2.2)postman分别测试返回json和xml
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

(2.3)开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。

spring:contentnegotiation:favor-parameter: true  #开启请求参数内容协商模式

发请求: http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml

确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

2、最终进行内容协商返回给客户端json即可。

(2.4)内容协商原理

  • 1、判断当前响应头中是否已经有确定的媒体类型。MediaType

  • 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
    contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略

    ○ HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型

  • 3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

  • 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

  • 5、客户端需要【application/xml】。服务端能力【10种、json、xml】

  • 6、进行内容协商的最佳匹配媒体类型

  • 7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

    导入了jackson处理xml的包,xml的converter就会自动进来

WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);if (jackson2XmlPresent) {Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));}

(2.5)自定义 MessageConverter

实现多协议数据兼容。json、xml、x-guigu
0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;

SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer

 @Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}}}



有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】

3.2.5、视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
官网:https://www.thymeleaf.org/

缺点:thymeleaf模板引擎并不是一个高并发的引擎,对于开发高并发的应用进行页面跳转,应该是做一个前后端分离,让职业的前端人员来做。或者做一个高并发的后台管理系统,我们应该选择其它的模板引擎。但是对于一个简单的单体应用就可以使用它了。
(1)视图解析

(1.1)视图解析原理流程




(2)模板引擎-Thymeleaf
(2.1)thymeleaf简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎

(2.2)基本语法

(2.2.1)表达式

表达式名字 语法 用途
变量取值 ${…} 获取请求域、session域、对象等值
选择变量 *{…} 获取上下文对象值
消息 #{…} 获取国际化等值
链接 @{…} 生成链接
片段表达式 ~{…} jsp:include 作用,引入公共页面片段

(2.2.2)字面量
文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,… 变量不能有空格

(2.2.3)文本操作
字符串拼接: +
变量替换: |The name is ${name}|

(2.2.4)数学运算
运算符: + , - , * , / , %

(2.2.5)布尔运算
运算符: and , or
一元运算: ! , not

(2.2.6)比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )

(2.2.7)条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)

(2.2.8)特殊操作
无操作: _

(2.3)设置属性值-th:attr
设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}"><fieldset><input type="text" name="email" /><input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/></fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5兼容的标签写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

(2.4)迭代

<tr th:each="prod : ${prods}"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

(2.5)条件运算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}"><p th:case="'admin'">User is an administrator</p><p th:case="#{roles.manager}">User is a manager</p><p th:case="*">User is some other thing</p>
</div>

(2.6)属性优先级

(3)thymeleaf使用
(3.1)引入Starter

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

(3.2)自动配置好了thymeleaf
底层源码:

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

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面

页面默认放的位置:必须放在templates目录下,资源名必须是以.html后缀为结尾。

 public static final String DEFAULT_PREFIX = "classpath:/templates/";public static final String DEFAULT_SUFFIX = ".html";  //xxx.html

(3.3)页面开发

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2><a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/><a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

配置文件:添加项目的前置访问路径

# 添加项目的前置访问路径
server:servlet:context-path: /world

控制层方法:

@Controller
public class ViewTestController {@GetMapping("/atguigu")public String atguigu(Model model){//model中的数据会被放在请求域中 request.setAttribute("a",aa)model.addAttribute("msg","你好 guigu");model.addAttribute("link","http://www.baidu.com");return "success";}
}

启动主启动类进行测试:

(4)构建后台管理系统
(4.1)项目创建
thymeleaf、web-starter、devtools、lombok

(4.2)静态资源处理
自动配置好,我们只需要把所有静态资源放到 static 文件夹下
(4.3)路径构建
th:action=“@{/login}”
(4.4)模板抽取
th:insert/replace/include
(4.5)页面跳转

@Controller
public class IndexController {/*** 登录页面* @return*/@GetMapping(value = {"/","/login"})public String loginPage(){return "login";}//登录成功后跳转到后端管理的首页://   但是这样用请求转发跳转会有表单重复提交问题,所以再写一个方法,通过重定向跳转来解决。@PostMapping("/login")public String main(User user, HttpSession session, Model model){ //RedirectAttributesif(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){//把登陆成功的用户保存起来session.setAttribute("loginUser",user);//登录成功重定向到main.html;  重定向防止表单重复提交return "redirect:/main.html";}else {model.addAttribute("msg","账号密码错误");//回到登录页面return "login";}}/*** 去main页面* @return*/@GetMapping("/main.html")public String mainPage(HttpSession session,Model model){//判断是否登录:// 方式一:每个方法写一个判断,但是这样太麻烦。// 方式二:可以直接使用拦截器,过滤器统一处理。//方式一写法:Object loginUser = session.getAttribute("loginUser");if(loginUser != null){return "main";}else {//回到登录页面model.addAttribute("msg","请重新登录");return "login";}}}


(4.6)数据渲染

    @GetMapping("/dynamic_table")public String dynamic_table(Model model){//表格内容的遍历List<User> users = Arrays.asList(new User("zhangsan", "123456"),new User("lisi", "123444"),new User("haha", "aaaaa"),new User("hehe ", "aaddd"));model.addAttribute("users",users);return "table/dynamic_table";//因为页面是放在templates目录下的table目录下}
        <table class="display table table-bordered" id="hidden-table-info"><thead><tr><th>#</th><th>用户名</th><th>密码</th></tr></thead><tbody><tr class="gradeX" th:each="user,stats:${users}"><td th:text="${stats.count}">Trident</td><td th:text="${user.userName}">Internet</td><td >[[${user.password}]]</td></tr></tbody></table>

3.2.6、拦截器

(1)HandlerInterceptor 接口

/*** 登录检查* 1、配置好拦截器要拦截哪些请求* 2、把这些配置放在容器中*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {/*** 目标方法执行之前* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();log.info("preHandle拦截的请求路径是{}",requestURI);//登录检查逻辑HttpSession session = request.getSession();Object loginUser = session.getAttribute("loginUser");if(loginUser != null){//放行return true;//true:代表放行}//拦截住。未登录。跳转到登录页request.setAttribute("msg","请先登录");
//        re.sendRedirect("/");request.getRequestDispatcher("/").forward(request,response);return false;//false代表拦截}/*** 目标方法执行完成以后* @param request* @param response* @param handler* @param modelAndView* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("postHandle执行{}",modelAndView);}/*** 页面渲染以后* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("afterCompletion执行异常{}",ex);}
}

(2)配置拦截器

/*** 1、编写一个拦截器实现HandlerInterceptor接口* 2、编写配置类,将拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")  //所有请求都被拦截包括静态资源.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求}
}

(3)拦截器原理

  1. 根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
  2. 先来顺序执行 所有拦截器的 preHandle方法
    2.1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
    2.2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
  3. 如果任何一个拦截器返回false。直接跳出不执行目标方法
  4. 所有拦截器都返回True。执行目标方法
  5. 倒序执行所有拦截器的postHandle方法。
  6. 前面的步骤有任何异常都会直接倒序触发 afterCompletion
  7. 页面成功渲染完成以后,也会倒序触发 afterCompletion


3.2.7、文件上传

(1)页面表单

<form method="post" action="/upload" enctype="multipart/form-data"><input type="file" name="file"><br><input type="submit" value="提交">
</form>

(2)文件上传代码

package com.atguigu.admin.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;/*** 文件上传测试*/
@Slf4j
@Controller
public class FormTestController {@GetMapping("/form_layouts")public String form_layouts(){return "form/form_layouts";}/*** MultipartFile 自动封装上传过来的文件:*   1.通过MultipartFile, 获取单文件上传*   2.通过MultipartFile[]数组, 获取多文件上传** @param email* @param username* @param headerImg* @param photos* @return*/@PostMapping("/upload")public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestPart("headerImg") MultipartFile headerImg,@RequestPart("photos") MultipartFile[] photos) throws IOException {log.info("上传的信息:email={},username={},headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);if(!headerImg.isEmpty()){//保存到文件服务器,OSS服务器String originalFilename = headerImg.getOriginalFilename();headerImg.transferTo(new File("D:\\cache\\"+originalFilename));}if(photos.length > 0){for (MultipartFile photo : photos) {if(!photo.isEmpty()){String originalFilename = photo.getOriginalFilename();photo.transferTo(new File("D:\\cache\\"+originalFilename));}}}return "main";}}

(3)配置文件:

#配置单个文件上传的最大值,默认为1MB
spring.servlet.multipart.max-file-size=10MB
#配置总请求上传的文件量的最大值,默认为10MB
spring.servlet.multipart.max-request-size=100MB

(4)自动配置原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤
    ○ 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    ○ 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    ○ 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>
    FileCopyUtils。实现文件流的拷贝
    @PostMapping("/upload")public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestPart("headerImg") MultipartFile headerImg,@RequestPart("photos") MultipartFile[] photos)

3.2.8、异常处理

(1)错误处理
(1.1)默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射

  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

  • 要对其进行自定义,添加View解析为error(即:可以自定义返回的错误页面)

  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。

  • error/下的4xx,5xx页面会被自动解析;

(1.2)定制错误处理逻辑

  • 自定义错误页
    ○ error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页

  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的

  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error

  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    ○ response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());

  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

  • ErrorViewResolver 实现自定义处理异常;
    ○ response.sendError 。error请求就会转给controller
    ○ 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    basicErrorController 要去的页面地址是 ErrorViewResolver

(1.3)异常处理自动配置原理

  • ErrorMvcAutoConfiguration 自动配置异常处理规则
    容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes

    • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
    • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。

    容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)

    • 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
    • 容器中有组件 View->id是error;(响应默认错误页)
    • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
      ○ 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
    • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
    • error/404、5xx.html

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

(1.4)异常处理步骤流程

  • 1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

  • 2、进入视图解析流程(页面渲染?)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

  • 3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

    • 3.1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】
    • 3.2、系统默认的 异常解析器;

    ○ 3.2.1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
    ○ 3.2.2、默认没有任何人能处理异常,所以异常会被抛出

    • 3.2.2.1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理

    • 3.2.2.2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。

    • 3.2.2.3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html

    • 4、模板引擎最终响应这个页面 error/500.html

3.2.9、Web原生组件注入(Servlet、Filter、Listener)

1、方式一:使用Servlet API(注解方式:推荐)

  1. @ServletComponentScan(basePackages = “com.atguigu.admin”) :指定原生Servlet组件都放在那里

  2. 注册servlet:@WebServlet(urlPatterns = “/my”):效果:直接响应,没有经过Spring的拦截器?

  3. 注册过滤器:@WebFilter(urlPatterns={“/css/“,”/images/”})

  4. 注册监听器:@WebListener

测试1:注册servlet:@WebServlet+@ServletComponentScan

package com.atguigu.admin.servlet;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;//servlet3.0提供的注解,配置能够处理的路径
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {private static final long serialVersionUID = -3250793476589229955L;@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("66666");}
}

package com.atguigu;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@ServletComponentScan(basePackages = "com.atguigu.admin") //spring提供的功能,可以把自己写的servlet扫描进来
public class Boot05WebAdminApplication {public static void main(String[] args) {SpringApplication.run(Boot05WebAdminApplication.class, args);}}

测试2:注册Filter:@WebFilter+@ServletComponentScan

package com.atguigu.admin.servlet;import lombok.extern.slf4j.Slf4j;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;@Slf4j
@WebFilter(urlPatterns={"/css/*","/images/*"})
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("MyFilter初始化完成");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {log.info("MyFilter工作");chain.doFilter(request,response);}@Overridepublic void destroy() {log.info("MyFilter销毁");}
}
package com.atguigu;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@ServletComponentScan(basePackages = "com.atguigu.admin") //spring提供的功能,可以把自己写的servlet扫描进来
public class Boot05WebAdminApplication {public static void main(String[] args) {SpringApplication.run(Boot05WebAdminApplication.class, args);}}

测试3:注册Listener:@WebListener+@ServletComponentScan

package com.atguigu.admin.servlet;import lombok.extern.slf4j.Slf4j;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;@Slf4j
@WebListener
public class MySwervletContextListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {log.info("MySwervletContextListener监听到项目初始化完成");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {log.info("MySwervletContextListener监听到项目销毁");}
}
package com.atguigu;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@ServletComponentScan(basePackages = "com.atguigu.admin") //spring提供的功能,可以把自己写的servlet扫描进来
public class Boot05WebAdminApplication {public static void main(String[] args) {SpringApplication.run(Boot05WebAdminApplication.class, args);}}

扩展:DispatchServlet 如何注册进来

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc
  • 通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet 配置进来。
  • 默认映射的是 / 路径,也可以在properties配置文件中修改spring.mvc.servlet.path=/mvc/,一般也不需要修改,没有啥意义。

Tomcat-Servlet;
多个Servlet都能处理到同一层路径,精确优选原则
A: /my/
B: /my/1

2、方式二:使用RegistrationBean (配置类的方式)
说明:使用配置类代替原来的:3个对应的注解+主启动类注解的方式。
步骤一:把原来的注解方式注释掉,但是这几个类还是要有的。



步骤二:编写配置类

package com.atguigu.admin.config;import com.atguigu.admin.servlet.MyFilter;
import com.atguigu.admin.servlet.MyServlet;
import com.atguigu.admin.servlet.MySwervletContextListener;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Arrays;/*** 1、MyServlet --> /my* 2、DispatcherServlet --> /*/
// (proxyBeanMethods = true):保证依赖的组件始终是单实例的,默认就是true单实例
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {@Beanpublic ServletRegistrationBean myServlet(){MyServlet myServlet = new MyServlet();//可以处理多个请求return new ServletRegistrationBean(myServlet,"/my","/my02");}@Beanpublic FilterRegistrationBean myFilter(){MyFilter myFilter = new MyFilter();//写法一:拦截和servlet请求相同的路径
//        return new FilterRegistrationBean(myFilter,myServlet());//方式二:自己设置拦截路径FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));return filterRegistrationBean;}@Beanpublic ServletListenerRegistrationBean myListener(){MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();return new ServletListenerRegistrationBean(mySwervletContextListener);}
}

3.2.10、嵌入式Servlet容器

说明:即springboot默认提供的tomact服务器,当然可以进行切换。

1、切换嵌入式Servlet容器

  • 默认支持的webServer
    ○ Tomcat, Jetty, or Undertow
    ○ ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器
   <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!--因为web依赖会自动关联tomact依赖,所以首先要排除tomact场景依赖--><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><!--之后导入其它的web服务器依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency>

  • 原理

    • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
    • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
    • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> Servlet 的web服务器)
    • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
    • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
    • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
    • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
    • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start();
    • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

2、定制Servlet容器
说明:可以修改服务器的一些规则,如:端口号,字符编码等。不但是tomact服务器,其它的web服务器同样可以修改。

  • 方式一:实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
    ○ 把配置文件的值和ServletWebServerFactory 进行绑定
  • 方式二(推荐):修改配置文件 server.xxx
  • 方式三:直接自定义 ConfigurableServletWebServerFactory

xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {@Overridepublic void customize(ConfigurableServletWebServerFactory server) {server.setPort(9000);}}

3.2.11、定制化原理

1、定制化的常见方式

  • 修改配置文件;
  • 定制化器xxxxxCustomizer;
  • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
  • @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
    ○ 原理
    ○ 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
    ○ 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
    ○ 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用

    • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
    • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
    • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
      ○ 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
      ○ 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
  • … …

2、原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项

3.3、数据访问

3.3.1 SQL (回来听)

(1)数据库连接池的自动配置-HikariDataSource
(1.1)导入JDBC场景

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


数据库驱动?
为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下要操作什么数据库。

注意:数据库版本和驱动版本对应,可以向下兼容

 <!--默认版本:<mysql.version>8.0.22</mysql.version>--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><!-- 修改版本,方式一: <version>5.1.49</version>--></dependency>想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)<properties><java.version>1.8</java.version><!--修改版本,方式二:--><!--<mysql.version>5.1.49</mysql.version>--></properties>

(1.2)分析自动配置
(1.2.1)自动配置的类

  • DataSourceAutoConfiguration : 数据源的自动配置
    ○ 修改数据源相关的配置:spring.datasource
    数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    底层配置好的连接池是:HikariDataSource
 @Configuration(proxyBeanMethods = false)@Conditional(PooledDataSourceCondition.class)@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })protected static class PooledDataSourceConfiguration
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud(因为此时好没有整合MyBatis等持久层框架,这是spring提供的一个小工具)
    ○ 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
    ○ @Bean@Primary JdbcTemplate;容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration: 分布式事务相关的

(1.3)修改配置项

# 数据库连接地址、用户名、密码、数据库驱动的名字、数据库的类型不用指定 默认就是HikariDataSource
spring:datasource:url: jdbc:mysql://localhost:3306/user_dbusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driver
#   type: com.zaxxer.hikari.HikariDataSource# 配置sprig下的jdbc下的template属性:查询的超时时间 单位:秒  表示3秒之内没有查询到数据则超时jdbc:template:query-timeout: 3

(1.4)测试

package com.atguigu;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;//使用spring提供的工具jdbcTemplate来进行crud操作。
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {@Autowired //这个组件spring容器注册好了,直接注入即可。JdbcTemplate jdbcTemplate;@Testvoid contextLoads() {//        jdbcTemplate.queryForObject("select * from account_tbl")
//        jdbcTemplate.queryForList("select * from account_tbl",)Long aLong = jdbcTemplate.queryForObject("select count(*) from t_account", Long.class);log.info("记录总数:{}",aLong);}}

(2)使用Druid数据库连接池
(2.1)druid官方github地址
https://github.com/alibaba/druid
整合第三方技术的两种方式

  • 自定义 (纯手工,手动的一个个的写。)
  • 找starter

(2.2)自定义方式
(2.2.1)创建数据源
添加依赖:

   <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.17</version></dependency>

spring原生方式:使用xml文件注册组件并进行配置属性。现在使用配置类进行代替。


<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /><property name="maxActive" value="20" /><property name="initialSize" value="1" /><property name="maxWait" value="60000" /><property name="minIdle" value="1" /><property name="timeBetweenEvictionRunsMillis" value="60000" /><property name="minEvictableIdleTimeMillis" value="300000" /><property name="testWhileIdle" value="true" /><property name="testOnBorrow" value="false" /><property name="testOnReturn" value="false" /><property name="poolPreparedStatements" value="true" /><property name="maxOpenPreparedStatements" value="20" />

配置文件:

# 数据库连接地址、用户名、密码、数据库驱动的名字、数据库的类型不用指定 默认就是HikariDataSource
spring:datasource:url: jdbc:mysql://localhost:3306/user_dbusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driver
#   type: com.zaxxer.hikari.HikariDataSource#配置sprig下的jdbc下的template属性:查询的超时时间 单位:秒  表示3秒之内没有查询到数据则超时jdbc:template:query-timeout: 3

配置类:

@Configuration
public class MyDataSourceConfig {// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)@ConfigurationProperties("spring.datasource")//方式二:设置连接数据库的信息,读取配置文件设置的连接信息@Beanpublic DataSource dataSource() throws SQLException {DruidDataSource druidDataSource = new DruidDataSource();
//设置连接数据库的信息,方式一:这是写死的写法
//        druidDataSource.setUrl();
//        druidDataSource.setUsername();
//        druidDataSource.setPassword();return druidDataSource;}
}

测试类:

package com.atguigu;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;import javax.sql.DataSource;//使用spring提供的工具jdbcTemplate来进行crud操作。
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {@AutowiredDataSource dataSource;@Testvoid contextLoads() {log.info("数据源类型:{}",dataSource.getClass());}}

(2.2.2)StatViewServlet
StatViewServlet的用途包括:

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API
  • 根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html

web.xml原生方式:配置监控功能、

  <servlet><servlet-name>DruidStatView</servlet-name><servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class></servlet><servlet-mapping><servlet-name>DruidStatView</servlet-name><url-pattern>/druid/*</url-pattern></servlet-mapping>

现在使用配置类:

package com.atguigu.admin.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.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;@Configuration
public class MyDataSourceConfig {// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)@ConfigurationProperties("spring.datasource")//方式二:设置连接数据库的信息,读取配置文件设置的连接信息@Beanpublic DataSource dataSource() throws SQLException {DruidDataSource druidDataSource = new DruidDataSource();
//设置连接数据库的信息,方式一:这是写死的写法
//        druidDataSource.setUrl();
//        druidDataSource.setUsername();
//        druidDataSource.setPassword();//        加入监控功能druidDataSource.setFilters("stat");return druidDataSource;}/*** 配置 druid的监控页功能* @return*/@Beanpublic ServletRegistrationBean statViewServlet(){StatViewServlet statViewServlet = new StatViewServlet();//配置拦截的路径ServletRegistrationBean<StatViewServlet> registrationBean =new ServletRegistrationBean<>(statViewServlet, "/druid/*");//配置监控页的用户名和密码registrationBean.addInitParameter("loginUsername","admin");registrationBean.addInitParameter("loginPassword","123456");return registrationBean;}}

控制层方法:

package com.atguigu.admin.controller;import com.atguigu.admin.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpSession;@Controller
public class IndexController {@AutowiredJdbcTemplate jdbcTemplate;@ResponseBody@GetMapping("/sql")public String queryFromDb(){Long aLong = jdbcTemplate.queryForObject("select count(*) from t_account", Long.class);return aLong.toString();}}

启动主启动类:
访问监控页:http://localhost:8080/druid/index.html


(2.2.3)StatFilter
用于统计监控信息;如SQL监控、URI监控

需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:<property name="filters" value="stat,slf4j" />

系统中所有filter:

慢SQL记录配置

<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter"><property name="slowSqlMillis" value="10000" /><property name="logSlowSql" value="true" />
</bean>使用 slowSqlMillis 定义慢SQL的时长

WebStatFilter用于采集web-jdbc关联监控的数据。

web.xml方式配置:

<filter><filter-name>DruidWebStatFilter</filter-name><filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class><!--经常需要排除一些不必要的url,比如*.js,/jslib/*等等。配置在init-param中。--><init-param><param-name>exclusions</param-name><param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value></init-param></filter><filter-mapping><filter-name>DruidWebStatFilter</filter-name><!--拦截所有路径--><url-pattern>/*</url-pattern></filter-mapping>

配置类方式:

  /*** WebStatFilter 用于采集web-jdbc关联监控的数据。*/@Beanpublic FilterRegistrationBean webStatFilter(){WebStatFilter webStatFilter = new WebStatFilter();FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));//拦截所有路径//排除不需要监控的资源filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return filterRegistrationBean;}

启动主启动类访问测试:

(2.2.4)防火墙
配置类:

(2.3)使用官方starter方式

把方式一的自定义方式注释掉。
注释掉依赖:


配置类标识为过时,并注释掉。

(2.3.1)引入druid-starter

        <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version></dependency>

(2.3.2)分析自动配置

  • 扩展配置项 spring.datasource.druid
  • DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
  • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
  • DruidFilterConfiguration.class}) 所有Druid自己filter的配置
    private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";

(2.3.3)配置示例

spring:datasource:url: jdbc:mysql://localhost:3306/db_accountusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverdruid:aop-patterns: com.atguigu.admin.*  # 监控SpringBean的范围filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)stat-view-servlet:   # 配置监控页功能enabled: true       # 默认false,需要开启login-username: admin # 配置监控页的用户名和密码login-password: adminresetEnable: false # 是否有重置按钮web-stat-filter:  # 监控webenabled: true # 开启urlPattern: /* # 需要匹配的exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'  # 需要排除的filter:stat:    # 对上面filters里面的stat的详细配置slow-sql-millis: 1000 # sql超过1000毫秒的查询都是慢查询logSlowSql: true # 日志是否要记录慢查询的sqlenabled: true # 监控的功能要不要开启wall:enabled: true # 防火墙的功能要不要开启config:drop-table-allow: false # 所有的删除操作都会被防火墙拦截

SpringBoot配置示例
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

配置项列表
https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

(3)整合MyBatis操作
https://github.com/mybatis
starter
SpringBoot官方的Starter:spring-boot-starter-*
第三方的: *-spring-boot-starter

<!--mybatis和mybatis-plus核心依赖,都包含了jdbc依赖,所以这里不用在引入jdbc依赖了。--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><!--注释掉--><!-- <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency>-->

(3.1)配置模式

说明:以前的配置方式需要在配置文件中自己一个个的写,现在底层自动配置好了。

  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class)MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties

可以修改配置文件中 mybatis 开始的所有:
方式一:使用核心配置文件的方式配置相关属性,如:开启驼峰规则映射。

yml配置文件:配置核心配置文件和映射文件的路径。

# 配置mybatis规则
mybatis:config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置

核心配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--所有的东西都不用配置,底层都配置好了--><!--设置MyBatis的全局配置--><settings><!--将_自动映射为驼峰,emp_name:empName--><setting name="mapUnderscoreToCamelCase" value="true"/></settings></configuration>

控制层,service层略。

接口:注意 加上@Mapper注解

package com.atguigu.admin.mapper;import com.atguigu.admin.bean.Account;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Mapper;/*** @Mapper 用于描述(做标记)数据层访问接口,用于告诉mybatis框架,*   使用此注解描述的接口要由底层为创建实现类.在实现类中基于mybatis*   API实现与数据库的交互.这个类的对象最后会交给spring管理.*/
@Mapper
public interface AccountMapper {public Account getAcct(Long id);
}

映射文件:

Mapper接口--->绑定Xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.atguigu.admin.mapper.AccountMapper">
<!--    public Account getAcct(Long id); --><select id="getAcct" resultType="com.atguigu.admin.bean.Account">select * from  account_tbl where  id=#{id}</select>
</mapper>

方式二:配置 private Configuration configuration; mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值
:之前用的mybatis-config.xml核心配置文件,现在可以使用yml或者properties文件下的 mybatis.configuration项,来配置各种属性以代替核心配置文件里面的一些列配置。

注意:一旦使用这种方式,那么必须要注释掉xml核心配置文件的方式。

# 配置mybatis规则
mybatis:# config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置configuration: # 指定mybatis全局配置文件中的相关配置项map-underscore-to-camel-case: true  #开启驼峰规则映射,一旦使用这种方式,那么必须要注释掉xml核心配置文件的方式。总结: 可以不写全局配置文件,所有全局配置文件的配置都放yml/properties中的configuration配置项中即可

总结开发步骤:

  • 导入mybatis官方starter
  • 编写mapper接口。标准@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议:配置在yml配置文件的mybatis.configuration项中

(3.2)注解模式
说明:使用注解方式代替映射文件方式写sql,适合简单地sqk语句。

注解方式获取插入数据的自增主键值:

@Mapper
public interface CityMapper {@Select("select * from city where id=#{id}")public City getById(Long id);@Insert("insert into  city(`name`,`state`,`country`) values(#{name},#{state},#{country})")@Options(useGeneratedKeys = true,keyProperty = "id")//注解方式获取插入数据的自增主键值。public void insert(City city);}

(3.3)混合模式
说明:注解方式和xml方式可以同时使用。
接口:

@Mapper
public interface CityMapper {@Select("select * from city where id=#{id}")public City getById(Long id);public void insert(City city);}

映射文件:xml方式获取插入数据的自增主键值。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.admin.mapper.CityMapper"><!--    public void insert(City city);-->
<!--    xml方式获取插入数据的自增主键值--><insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into  city(`name`,`state`,`country`) values(#{name},#{state},#{country})</insert></mapper>

最佳实战:

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • 在主启动类上添加@MapperScan(“com.atguigu.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解

(4)整合 MyBatis-Plus 完成CRUD
(4.1)什么是MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis plus 官网:https://baomidou.com/
建议安装 MybatisX 插件 :点击接口方法上的小鸟图标,会直接跳到对应的xml文件,开发时来回跳转比较方便,还有逆向生成代码,代码提示等功能。

(4.2)整合MyBatis-Plus

 <!--mybatis和mybatis-plus核心依赖,都包含了jdbc依赖,所以这里不用在引入jdbc依赖了。--><!--mybatis-plus:又包含了mybatis场景依赖,所以也不需要引入mybatis依赖了。--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><!--注释掉--><!-- <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency>--><!--注释掉--><!-- <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency>-->

自动配置

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下(当然也可以自定义修改)
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan(“com.atguigu.admin.mapper”) 批量扫描就行

优点:

  • 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力
package com.atguigu.admin.mapper;import com.atguigu.admin.bean.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;/*** 泛型:数据库对应的实体类类型。*/
public interface UserMapper extends BaseMapper<User> {}

(4.3)CRUD功能
控制层:

    @GetMapping("/user/delete/{id}")public String deleteUser(@PathVariable("id") Long id,@RequestParam(value = "pn",defaultValue = "1")Integer pn,RedirectAttributes ra){userService.removeById(id);ra.addAttribute("pn",pn);return "redirect:/dynamic_table";}@GetMapping("/dynamic_table")public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){//表格内容的遍历
//        response.sendError
//     List<User> users = Arrays.asList(new User("zhangsan", "123456"),
//                new User("lisi", "123444"),
//                new User("haha", "aaaaa"),
//                new User("hehe ", "aaddd"));
//        model.addAttribute("users",users);
//
//        if(users.size()>3){
//            throw new UserTooManyException();
//        }//从数据库中查出user表中的用户进行展示//构造分页参数Page<User> page = new Page<>(pn, 2);//调用page进行分页Page<User> userPage = userService.page(page, null);//        userPage.getRecords()
//        userPage.getCurrent()
//        userPage.getPages()model.addAttribute("users",userPage);return "table/dynamic_table";}

业务层:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {}public interface UserService extends IService<User> {}

3.3.2 NoSQL

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)
(1)Redis自动配置

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


自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis

redis环境搭建
1、阿里云按量付费redis。经典网络
2、申请redis的公网连接地址
3、修改白名单 允许0.0.0.0/0 访问

(2)RedisTemplate与Lettuce

    @Testvoid testRedis(){ValueOperations<String, String> operations = redisTemplate.opsForValue();operations.set("hello","world");String hello = operations.get("hello");System.out.println(hello);}

(3)切换至jedis

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--        导入jedis--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>
spring:redis:host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.comport: 6379password: lfy:Lfy123456client-type: jedisjedis:pool:max-active: 10

3.4、单元测试

3.4.1 JUnit5 的变化

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

添加juint5的依赖:

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

注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage

<dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.hamcrest</groupId><artifactId>hamcrest-core</artifactId></exclusion></exclusions>
</dependency>

以前:
@SpringBootTest + @RunWith(SpringTest.class)

现在版本:

@SpringBootTest
class Boot05WebAdminApplicationTests {@Testvoid contextLoads() {}
}

总结使用测试类的步骤:SpringBoot整合Junit以后。

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

3.4.2 JUnit5常用注解

JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

  • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
  • @DisplayName :为测试类或者测试方法设置展示名称
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith :为测试类或测试方法提供扩展类引用

package com.atguigu.admin;import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;import java.util.concurrent.TimeUnit;/*** @SpringBootTest是复合注解:*          @BootstrapWith(SpringBootTestContextBootstrapper.class)*          @ExtendWith(SpringExtension.class)*/
//@ExtendWith  为测试类或测试方法提供扩展类引用,@SpringBootTest本身就包含它。
@SpringBootTest //想要使用spring的容器注入功能就需要加上此注解,如果不需要使用注入功能可以不写。
@DisplayName("junit5功能测试类")
public class Junit5Test { //在类上运行单元测试,整个类上的测试方法都会执行一遍@AutowiredJdbcTemplate jdbcTemplate;/*** 测试前置条件*/@DisplayName("测试前置条件") //为测试类或者测试方法设置展示名称@Testvoid testassumptions(){System.out.println("111111");}@Disabled //表示测试类或测试方法不执行@DisplayName("测试方法2")@Testvoid test2() {System.out.println(2);}@RepeatedTest(5) //表示方法可重复执行,此测试方法可以执行5次@Testvoid test3() {System.out.println(5);}/*** 规定方法超时时间。超出时间测试出异常* 可以指定单位:MILLISECONDS毫秒** @throws InterruptedException*/@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)@Testvoid testTimeout() throws InterruptedException {Thread.sleep(600);}@BeforeEach //表示在每个单元测试之前执行void testBeforeEach() {System.out.println("测试就要开始了...");}@AfterEach //表示在每个单元测试之后执行void testAfterEach() {System.out.println("测试结束了...");}//必须是静态方法,或表示@TestInstance.因为这个方法只会被调用一次@BeforeAll //表示在所有单元测试之前执行static void testBeforeAll() {System.out.println("所有测试就要开始了...");}//必须是静态方法,或表示@TestInstance。因为这个方法只会被调用一次@AfterAll //表示在所有单元测试之后执行static void testAfterAll() {System.out.println("所有测试以及结束了...");}}

3.4.3 断言(assertions)

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;

断言:即断定某件事情一定会发生,如果这件事情没有发生表示出现了问题。用来代替system输出方法,相比输出方法会有一个详细的测试报告。
(1)简单断言
用来对单个值进行简单的验证。如:

package com.atguigu.admin;import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
public class Juint5Test01 {/*** 断言:前面断言失败,后面的代码都不会执行*/@DisplayName("测试简单断言")@Testvoid testSimpleAssertions() {int cal = cal(3, 2);//判断两个对象或两个原始类型是否相等,用的是equals方法,判断的是里面的值是否相等//参数:期望值、传入的真实值、自定义断言失败的提示信息      说明:传入的期望值和真实值是否相等assertEquals(5, cal, "业务逻辑计算失败");Object obj1 = new Object();Object obj2 = new Object();//判断两个对象引用是否指向同一个对象assertSame(obj1, obj2, "两个对象不一样");}int cal(int i, int j) {return i + j;}
}

(2)数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

 @Test@DisplayName("array assertion")public void array() {//比较的是2个数组里面的值assertArrayEquals(new int[]{1, 1}, new int[] {1, 2},"数组内容不相等");}

(3)组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

 @Test@DisplayName("组合断言")void all() {/*** 所有断言全部需要成功,才会向下执行。*///参数1:给组合断言是哪一个组起一个名//参数2:可变参数可以出传多个断言assertAll("test",() -> assertTrue(true && true, "结果不为true"),() -> assertEquals(1, 1, "结果不是1"));System.out.println("=====");}

(4)异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用 @Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

    @DisplayName("异常断言")@Testvoid testException() {//断定业务逻辑一定出现异常,没有出现异常所说明这个业务逻辑是有问题的。//参数:异常类型,可执行的方法,没有抛出异常的提示信息assertThrows(ArithmeticException.class, () -> {int i = 10 / 2;}, "业务逻辑居然正常运行?");}

(5)超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

@Test
@DisplayName("超时测试")
public void timeoutTest() {//如果测试方法时间超过1s将会异常Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

(6)快速失败
通过 fail 方法直接使得测试失败

@DisplayName("快速失败")@Testvoid testFail(){//xxxxx//在前面xxxx中单元测试了一大堆,之后想要直接结束,不想要在测试了。if(1 == 2){fail("测试失败");}}

(7)断言机制的好处
说明:在项目上线之前,跑一遍单元测试,会有一个完整的单元测试报告。它会执行每一个单元测试方法,那个方法执行成功,那个执行失败 失败的原因是什么,会有一个汇总报告,方便我们定位。


3.4.4 前置条件(assumptions)

JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

@DisplayName("前置条件")
public class AssumptionsTest {private final String environment = "DEV";@Test@DisplayName("simple")public void simpleAssume() {assumeTrue(Objects.equals(this.environment, "DEV"));assumeFalse(() -> Objects.equals(this.environment, "PROD"));}@Test@DisplayName("assume then do")public void assumeThenDo() {assumingThat(Objects.equals(this.environment, "DEV"),() -> System.out.println("In DEV"));}/*** 测试前置条件*/@DisplayName("测试前置条件")@Testvoid testassumptions(){//是true才会向下执行,如果不是true会有个提示信息。Assumptions.assumeTrue(false,"结果不是true");System.out.println("111111");}
}

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

3.4.5 嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

package com.atguigu.admin;import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;import java.util.EmptyStackException;
import java.util.Stack;
import java.util.stream.Stream;import static org.junit.jupiter.api.Assertions.*;@DisplayName("嵌套测试")
public class TestingAStackDemo {Stack<Object> stack;@Test@DisplayName("new Stack()")void isInstantiatedWithNew() {new Stack<>();//嵌套测试情况下,外层的Test不能驱动内层的Before(After)Each/All之类的方法提前/之后运行assertNull(stack);}@Nested@DisplayName("when new")class WhenNew {@BeforeEachvoid createNewStack() {stack = new Stack<>();}@Test@DisplayName("is empty")void isEmpty() {assertTrue(stack.isEmpty());}@Test@DisplayName("throws EmptyStackException when popped")void throwsExceptionWhenPopped() {assertThrows(EmptyStackException.class, stack::pop);}@Test@DisplayName("throws EmptyStackException when peeked")void throwsExceptionWhenPeeked() {assertThrows(EmptyStackException.class, stack::peek);}@Nested@DisplayName("after pushing an element")class AfterPushing {String anElement = "an element";@BeforeEachvoid pushAnElement() {stack.push(anElement);}/*** 内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行*/@Test@DisplayName("it is no longer empty")void isNotEmpty() {assertFalse(stack.isEmpty());}@Test@DisplayName("returns the element when popped and is empty")void returnElementWhenPopped() {assertEquals(anElement, stack.pop());assertTrue(stack.isEmpty());}@Test@DisplayName("returns the element when peeked but remains not empty")void returnElementWhenPeeked() {assertEquals(anElement, stack.peek());assertFalse(stack.isEmpty());}}}
}

3.4.6 参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {System.out.println(string);Assertions.assertTrue(StringUtils.isNotBlank(string));
}@ParameterizedTest
@MethodSource("method")    //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {System.out.println(name);Assertions.assertNotNull(name);
}static Stream<String> method() {return Stream.of("apple", "banana");
}

3.4.7 迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
  • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
  • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
  • 把@Ignore 替换成@Disabled。
  • 把@Category 替换成@Tag。
  • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

3.5、指标监控

3.5.1、SpringBoot Actuator

(1)简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

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

(2)1.x与2.x的不同

(3)如何使用

  • 引入场景
  • 访问 http://localhost:8080/actuator/**
  • 暴露所有监控信息为HTTP
management:endpoints:enabled-by-default: true #暴露所有端点信息web:exposure:include: '*'  #以web方式暴露
  • 测试

http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/endpointName/detailPath
。。。。。。

(4)可视化
https://github.com/codecentric/spring-boot-admin

3.5.2、Actuator Endpoint

(1)最常使用的端点

ID 描述
auditevents 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件。
beans 显示应用程序中所有Spring Bean的完整列表。
caches 暴露可用的缓存。
conditions 显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops 显示所有@ConfigurationProperties。
env 暴露Spring的属性ConfigurableEnvironment
flyway 显示已应用的所有Flyway数据库迁移。需要一个或多个Flyway组件。
health 显示应用程序运行状况信息。
httptrace 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info 显示应用程序信息。
integrationgraph 显示Spring integrationgraph 。需要依赖spring-integration-core。
loggers 显示和修改应用程序中日志的配置。
liquibase 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics 显示当前应用程序的“指标”信息。
mappings 显示所有@RequestMapping路径列表。
scheduledtasks 显示应用程序中的计划任务。
sessions 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown 使应用程序正常关闭。默认禁用。
startup 显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup。
threaddump 执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID 描述
heapdump 返回hprof堆转储文件。
jolokia 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core。
logfile 返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus。

最常用的Endpoint

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

(2)Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

(3)Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

(4)管理Endpoints
(4.1)开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.<endpointName>.enabled = true
management:endpoint:beans:enabled: true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint
management:endpoints:enabled-by-default: falseendpoint:beans:enabled: truehealth:enabled: true

(4.2)暴露Endpoints
支持的暴露方式

  • HTTP:默认只暴露health和info Endpoint
  • JMX:默认暴露所有Endpoint
  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID JMX Web
auditevents Yes No
beans Yes No
caches Yes No
conditions Yes No
configprops Yes No
env Yes No
flyway Yes No
health Yes Yes
heapdump N/A No
httptrace Yes No
info Yes Yes
integrationgraph Yes No
jolokia N/A No
logfile N/A No
loggers Yes No
liquibase Yes No
metrics Yes No
mappings Yes No
prometheus N/A No
scheduledtasks Yes No
sessions Yes No
shutdown Yes No
startup Yes No
threaddump Yes No

3.5.3、定制 Endpoint

(1)定制 Health 信息

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;@Component
public class MyHealthIndicator implements HealthIndicator {@Overridepublic Health health() {int errorCode = check(); // perform some specific health checkif (errorCode != 0) {return Health.down().withDetail("Error Code", errorCode).build();}return Health.up().build();}}构建Health
Health build = Health.down().withDetail("msg", "error service").withDetail("code", "500").withException(new RuntimeException()).build();
management:health:enabled: trueshow-details: always #总是显示详细信息。可显示每个模块的状态信息
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {/*** 真实的检查方法* @param builder* @throws Exception*/@Overrideprotected void doHealthCheck(Health.Builder builder) throws Exception {//mongodb。  获取连接进行测试Map<String,Object> map = new HashMap<>();// 检查完成if(1 == 2){
//            builder.up(); //健康builder.status(Status.UP);map.put("count",1);map.put("ms",100);}else {
//            builder.down();builder.status(Status.OUT_OF_SERVICE);map.put("err","连接超时");map.put("ms",3000);}builder.withDetail("code",100).withDetails(map);}
}

(2)定制info信息
常用两种方式
(2.1)编写配置文件

info:appName: boot-adminversion: 2.0.1mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom文件值mavenProjectVersion: @project.version@

(2.2)编写InfoContributor

import java.util.Collections;import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;@Component
public class ExampleInfoContributor implements InfoContributor {@Overridepublic void contribute(Info.Builder builder) {builder.withDetail("example",Collections.singletonMap("key", "value"));}}

http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
(3)定制Metrics信息
(3.1)SpringBoot支持自动适配的Metrics

  • JVM metrics, report utilization of:
    ○ Various memory and buffer pools
    ○ Statistics related to garbage collection
    ○ Threads utilization
    ○ Number of classes loaded/unloaded
  • CPU metrics
  • File descriptor metrics
  • Kafka consumer and producer metrics
  • Log4j2 metrics: record the number of events logged to Log4j2 at each level
  • Logback metrics: record the number of events logged to Logback at each level
  • Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
  • Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)
  • Spring Integration metrics

(3.2)增加定制Metrics

class MyService{Counter counter;public MyService(MeterRegistry meterRegistry){counter = meterRegistry.counter("myservice.method.running.counter");}public void hello() {counter.increment();}
}//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}

(4)定制Endpoint

@Component
@Endpoint(id = "container")
public class DockerEndpoint {@ReadOperationpublic Map getDockerInfo(){return Collections.singletonMap("info","docker started...");}@WriteOperationprivate void restartDocker(){System.out.println("docker restarted....");}}

场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;
当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes

3.6、原理解析

3.6.1 Profile功能

为了方便多环境适配,springboot简化了profile功能。
(1)application-profile功能
执行规则:

  • 默认配置文件 application.yaml,任何时候都会加载
  • 指定环境配置文件 application-{env}.yaml
  • 激活指定环境
    ○ 配置文件激活
    ○ 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha

    • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效
  • 同名配置项,profile指定环境的配置优先

测试:

application.propweties:

# 开发环境
person.name=张三
server.port=8080
#默认使用的是默认环境,即不加标识的环境。也可以指定环境。
#在默认环境的配置文件中,自动想要使用的环境
spring.profiles.active=test#执行流程:默认环境任何情况下都会执行,如果指定了其它环境之后执行其它环境,如果默认环境和其他环境有相同的配置,则指定的环境配置优先。

application-prod.yaml:

# 生产环境
#怎么告诉springboot这是一个生产环境,只需要在配置文件的名字后加上标识:application-xxx.yml,标识名任意。
person:name: prod-张三
server:port: 8000

application-test.yaml:

# 测试环境
#怎么告诉springboot这是一个测试环境,只需要在配置文件的名字后加上标识:application-xxx.yml,标识名任意。
person:name: test-张三
server:port: 7000

控制层方法:

package com.atguigu.boot.controller;import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HelloController {// @Value("${person.name}") //写法一@Value("${person.name:李四}") //写法二:即便拿不到配置文件的值,可以使用这个默认值private String name;@GetMapping("/")public String hello(){return "Hello"+name;}}

(2)@Profile条件装配功能
测试:

application.properties:

# 开发环境
person.name=张三
server.port=8080
#默认使用的是默认环境,即不加标识的环境。也可以指定环境。
#在默认环境的配置文件中,自动想要使用的环境
spring.profiles.active=prod#执行流程:默认环境任何情况下都会执行,如果指定了其它环境之后执行其它环境,如果默认环境和其他环境有相同的配置,则指定的环境配置优先。

application-prod.yaml:

# 生产环境
#怎么告诉springboot这是一个生产环境,只需要在配置文件的名字后加上标识:application-xxx.yml,标识名任意。
person:name: prod-张三age: 18
server:port: 8000

application-test.yaml:

# 测试环境
#怎么告诉springboot这是一个测试环境,只需要在配置文件的名字后加上标识:application-xxx.yml,标识名任意。
person:name: test-张三
server:port: 7000

person接口:

package com.atguigu.boot.bean;public interface Person {String getName();Integer getAge();}

Boss实现类:

package com.atguigu.boot.bean;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;@Profile("prod") //当前环境是prod时才生效,可以标在类上表示当前环境是prod时类才生效。标在方法上代表当前环境是prod时方法才生效。
@Component
@ConfigurationProperties("person") //读取配置文件里面的person值
@Data
public class Boss implements Person {private String name;private Integer age;}

Worker实现类:

package com.atguigu.boot.bean;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;@Profile("test") //当前环境是test环境下才生效
@Component
@ConfigurationProperties("person")
@Data
public class Worker implements Person {private String name;private Integer age;
}

类color:

package com.atguigu.boot.bean;public class Color {
}

配置类:

package com.atguigu.boot.config;import com.atguigu.boot.bean.Color;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;@Configuration
public class MyConfig {@Profile("prod")//标在方法上,标在方法上代表当前环境是prod时方法才生效。@Beanpublic Color red(){return new Color();}@Profile("test")@Beanpublic Color green(){return new Color();}
}

控制层方法:

package com.atguigu.boot.controller;import com.atguigu.boot.bean.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HelloController {@Autowiredprivate Person person;@GetMapping("/")public String hello(){return person.getClass().toString();}}

(3)profile分组
application.properties

# 开发环境
person.name=张三
server.port=8080
#默认使用的是默认环境,即不加标识的环境。也可以指定环境。
#在默认环境的配置文件中,自动想要使用的环境
#此时就就会同时加载2个环境
spring.profiles.active=myprod#执行流程:默认环境任何情况下都会执行,如果指定了其它环境之后执行其它环境,如果默认环境和其他环境有相同的配置,则指定的环境配置优先。#定义分组,分组名字随意,这个组加载第一种环境,加载第二种环境。
# 生产环境组下加载第一种环境条件,生产环境加载第二种环境条件,相当于同时加载2个配置文件
spring.profiles.group.myprod[0]=ppd
spring.profiles.group.myprod[1]=prod# 测试环境组下只有测试环境
spring.profiles.group.mytest[0]=test

application-prod.yaml

# 生产环境
#怎么告诉springboot这是一个生产环境,只需要在配置文件的名字后加上标识:application-xxx.yml,标识名任意。
person:name: prod-张三server:port: 8000

application-ppd.yaml

person:age: 20

application-test.yaml

# 测试环境
#怎么告诉springboot这是一个测试环境,只需要在配置文件的名字后加上标识:application-xxx.yml,标识名任意。
person:name: test-张三
server:port: 7000

测试类:

@GetMapping("/person")public Person person(){return person;}

3.6.2 外部化配置

说明:把常用的配置写到配置文件中,就叫作外部化配置。

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

(1)外部配置源
说明:外部配置源可以来自不同的地方。
常用:Java属性文件、YAML文件、环境变量、命令行参数(Dos窗口);

测试:配置信息来自环境变量

控制层代码:

@RestController
public class HelloController {@Value("${MAVEN_HOME}")//获取Maven的安装目录private String msg;@Value("${os.name}") //获取操作系统private String osName;@GetMapping("/msg")public String getMsg(){return msg+"==>"+osName;}
}

访问:

主启动类输出系统的环境变量所有信息:

package com.atguigu.boot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;import java.util.Map;@SpringBootApplication
public class Boot09FeaturesProfileApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(Boot09FeaturesProfileApplication.class, args);//获取当前的环境ConfigurableEnvironment environment = run.getEnvironment();//系统的环境变量Map<String, Object> systemEnvironment = environment.getSystemEnvironment();//系统的属性Map<String, Object> systemProperties = environment.getSystemProperties();System.out.println(systemEnvironment);System.out.println(systemProperties);}}

(2)配置文件查找位置

说明:配置文件的位置可以有以下5种种方式。仍然有优先级,如果有同名的配置,后面的会覆盖前面的配置。

  1. classpath 根路径(类路径下的根路径:即源码标识的路径)

  2. classpath 根路径下config目录

  3. jar包当前目录
    即:项目打成jar包发布到服务器的某一个位置,那么这个jar包所在的目录里面的配置文件也能拿到。

  4. jar包当前目录的config目录

  5. /config子目录的直接子目录(针对于linux系统)

(3)配置文件加载顺序:

  1. 当前jar包内部的application.properties和application.yml
  2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3. 引用的外部jar包的application.properties和application.yml
  4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

(4)指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

  1. Default properties (specified by setting SpringApplication.setDefaultProperties).(java代码主程序设置的值
  2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  3. Config data (such as application.properties files)application.properties问文件
  4. A RandomValuePropertySource that has properties only in random.*.
  5. OS environment variables.(操作系统的环境变量
  6. Java System properties (System.getProperties()).(java系统的属性
  7. JNDI attributes from java:comp/env.
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  11. Command line arguments.(命令行参数
  12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  13. @TestPropertySource annotations on your tests.
  14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.
  15. 说明:springboot可以通过以上14种方式获取配置信息的值,优先级从低到高排序,即:如果出现了同名配置,优先级高的会覆盖优先级低的。

3.6.3 自定义starter(重听)

说明:springboot为了简化开发,常用的场景都帮我们抽取出来了,即使这样也不能模拟所有的开发场景。比如我们现在有一个功能非常常用,我们把它抽取出来,其它的模块经常引用,而且引用过来一切场景都要配置好,此时我们就需要把这个功能抽取为自定义的starter。别人一引用这个自定义的stater,所有东西全配置好只需要改下配置文件就能用。
(1)starter启动原理

  • starter-pom引入 autoconfigurer 包
  • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
    @Configuration
    @Conditional
    @EnableConfigurationProperties
    @Bean
    ○ …

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

(2)自定义starter
atguigu-hello-spring-boot-starter(启动器)
atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

3.6.4 SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理

(1)SpringBoot启动过程

public interface Bootstrapper {/*** Initialize the given {@link BootstrapRegistry} with any required registrations.* @param registry the registry to initialize*/void intitialize(BootstrapRegistry registry);}



@FunctionalInterface
public interface ApplicationRunner {/*** Callback used to run the bean.* @param args incoming application arguments* @throws Exception on error*/void run(ApplicationArguments args) throws Exception;}
@FunctionalInterface
public interface CommandLineRunner {/*** Callback used to run the bean.* @param args incoming main method arguments* @throws Exception on error*/void run(String... args) throws Exception;}

(2)Application Events and Listeners
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners
ApplicationContextInitializer
ApplicationListener
SpringApplicationRunListener

(3)ApplicationRunner 与 CommandLineRunner

4、SpringBoot2核心技术-场景整合

001 第一季:SpringBoot2核心技术相关推荐

  1. 002 第一季SpringBoot2核心技术-核心功能:配置文件、Web开发(原生组件)、数据访问、单元测试、指标监控、原理解析:@Value、命令行参数、手动获取bean、自定义starter

    三.核心技术之- ->核心功能 1. 配置文件 1.1 文件类型 1.1.1 properties 同以前的properties用法 优先级高于yml的方式. 1.1.2 yaml 1) 简介 ...

  2. SpringBoot2核心技术最好的一篇文章——2. 核心技术

    SpringBoot2核心技术最好的一篇文章--2.核心技术 一.配置文件 1.1 properties 1.2 yaml 1. 简介 2. 基本语法 3. 数据类型 1.3 配置处理器 二. web ...

  3. 做自己——鬼脚七自媒体第一季

    <做自己--鬼脚七自媒体第一季> 基本信息 作者: 鬼脚七 出版社:人民邮电出版社 ISBN:9787121207105 上架时间:2013-6-25 出版日期:2013 年7月 开本:1 ...

  4. tp点一共有多少_致命女人更新时间 致命女人第一季一共多少集在线观看地址

    由主创Marc Cherry打造的全新黑色喜剧<致命女人>(Why Women Kill)确定了首播日期,现在第一季已经播出完毕了,但是致命女人更新时间是什么时候呢?致命女人第一季一共多少 ...

  5. 危机边缘第一季/全集Fringe迅雷下载

    本季Fringe Season 2 第二季(2008)看点:一架从汉堡飞往波士顿航班安全着陆,飞机上的机组成员和乘客却全部死亡.这起离奇案件揭开了一连串奇异.危险事件的序幕. 故事将主要讲述年轻的FB ...

  6. 暴君第一季/全集Tyrant迅雷下载

    本季第一季 Tyrant Season 1 (2014)看点:虽然李安退出了FX系列剧<暴君>(Tyrant),称不想耽误了剧集的制作,但显然FX对这部剧的重视程度非比寻常,因为他们找来的 ...

  7. 逍遥法外第一季/全集How To Get Away With Murder迅雷下载

    英文译名 How To Get Away With Murder (第1季) (2014-09-26首播)ABC. 本季看点: <逍遥法外又名:天才刑法班>由<实习医生格蕾>和 ...

  8. 2021阿里巴巴大数据技术公开课第一季:外部工具连接SaaS模式云数仓MaxCompute实战

    简介:MaxCompute 是面向分析的企业级 SaaS 模式云数据仓库,以 Serverless 架构提供快速.全托管的在线数据仓库服务,消除了传统数据平台在资源扩展性和弹性方面的限制,最小化用户运 ...

  9. 【第一季】CH07_FPGA_RunLED创建VIVADO工程实验

    [第一季]CH07_FPGA_RunLED创建VIVADO工程实验 7.1 硬件图片 先来熟悉一下开发板的硬件:LED部分及按钮部分 7.2 硬件原理图 PIN脚定义(讲解以MIZ702讲解,MIZ7 ...

  10. LiveVideoStackCon讲师热身分享第一季

    LiveVideoStackCon 2018音视频技术大会是每年的多媒体技术人的盛宴,为了让参会者与大会讲师更多互动交流,我们推出了LiveVideoStackCon讲师热身分享第一季,在每周四晚19 ...

最新文章

  1. 雷林鹏分享:MySQL 及 SQL 注入
  2. Tensorflow—Fetch and Feed
  3. 最严谨的计算机语言p,用于PLC的华P语言编译器设计及实现.pdf
  4. poj2912(种类并查集+枚举)
  5. blob html 预览_本地图片预览+blob使用
  6. 为什么Spark能成为最火的大数据计算引擎?它是怎样工作的?
  7. 【图像风格转换】项目参考资料总结
  8. H5脱离微信通过调起微信客户端支付
  9. 对方差,协方差,相关系数,协方差矩阵的理解与比较
  10. 基于RGB图像的机器人抓取算法汇总
  11. atitit.web原理 理论attilax总结
  12. 再见beycond comapre,适合程序员使用的免费文件对比工具——CCompare1.14
  13. Chromium OS Autotest 服务端测试
  14. ThreadLocal的介绍与使用
  15. mysql只读模式_mysql 只读模式详解
  16. D - Power Tower欧拉降幂公式
  17. Windows 10 已禁用输入法
  18. java 时分秒加减_JAVA日期加减运算
  19. 店盈通: 拼多多店运营怎么选择推广关键词?
  20. 算法设计与分析-----动态规划

热门文章

  1. ImportError: cannot import name ‘bbox_ious‘
  2. radix tree
  3. linux内核调度 宿主机,Qemu虚拟机与宿主机之间文件传输
  4. blog推荐 - 左岸读书
  5. 我看到左岸读书上的留言,感觉挺有新意
  6. 计算机仿真和vr的区别,扫盲科普:今天才知道VR和AR的区别
  7. vivo手机便签扫描功能怎么识别图片文字转为电子文字
  8. Alertmanager 告警详解
  9. 微信支付退款接口证书使用
  10. linux uwsgi 非root,只能以root身份运行uwsgi