尚硅谷SpringBoot2核心技术-基础入门(一)

spring与springboot

spring能做什么?

spring的能力

  • 微服务开发
  • 响应式编程
  • 分布式云开发
  • web开发
  • 无服务开发(函数式服务)
  • 事件驱动
  • 批处理业务

spring能做什么图示

spring的生态

spring的生态覆盖了一下内容

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

spring的生态图示

spring5的重大升级

响应式编程

spring5的重大升级图示

内部源码设计

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

为什么使用SpringBoot

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
能快速创建出生产级别的Spring应用

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技术栈的快速开发脚手架

SpringBoot缺点

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

时代背景

微服务

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

分布式

分布式图示

分布式的困难

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

分布式的解决

  • SpringBoot + SpringCloud
    分布式解决图示

云原生

原生应用如何上云。 Cloud Native

上云的困难

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

如何学习SpringBoot

官网文档架构

springboot官网图示1

springboot官网图示2

Home · spring-projects/spring-boot Wiki · GitHub
springboot升级内容图示

SpringBoot2入门

创建maven工程

  • 使用idea创建maven工程

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<parent>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.13</version>
</parent>

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

创建主程序

1
2
3
4
5
6
@SpringBootApplication  
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}

编写业务

1
2
3
4
5
6
7
@RestController  
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "Hello Spring Boot 2";
}
}

测试

  • 直接运行main方法

简化配置

  • application.properties
1
server.port=8888

简化部署

  • 直接将项目打成可执行jar,放到对应服务器上运行即可
1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

了解自动配置原理

SpringBoot特点

依赖管理

父项目做依赖管理

  • 依赖管理
1
2
3
4
5
<parent>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.13</version>
</parent>
  • spring-boot-starter-parent 的父项目
1
2
3
4
5
<parent>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.13</version>
</parent>
  • dependencies中几乎声明了开发中所有常用的依赖的版本号,自动版本仲裁机制

开发导入starter场景启动器

  • 有很多 spring-boot-starter-**就是某种场景
  • 只要引入starter ,这个场景的所有常规需要的依赖就都会自动引入
  • SpringBoot所有支持的场景:Developing with Spring Boot
  • 见到的 *-spring-boot-starter:第三方为我们提供的简化开发的场景启动器
  • 所有场景启动器最底层的依赖
1
2
3
4
5
6
<dependency>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.13</version>
<scope>compile</scope>
</dependency>

无需关注版本号,自动版本仲裁

  • 引入的依赖默认都可以不写版本号
  • 引入非版本仲裁的jar,需要些版本号

可以修改默认版本号

  • 查看spring-boot-dependencies里面规定当前依赖的版本 用的key
  • 在当前项目里面重新配置
1
2
3
<properties>  
<mysql.version>8.0.21</mysql.version>
</properties>
  • 也可以在引入依赖的时候直接指定版本,指定后会覆盖父项目所指定的版本
1
2
3
4
5
<dependency>  
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>

自动配置

  • 自动配置Tomcat
    • 引入Tomcat依赖
    • 配置Tomcat
1
2
3
4
5
6
7
<!-- spring-boot-starter-web中自动引入了tomcat的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.7.13</version>
<scope>compile</scope>
</dependency>
  • 自动配置号SpringMVC

    • 引入SpringMVC全套组件
    • 自动配置好SpringMVC常用组件(功能)
  • 自动配置号web常见功能,如:字符编码问题

    • SpringBoot帮我们配置好了所有web开发的常见场景
  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 不需要再像以前一样在xml中进行包扫描路径配置
    • 想要改变默认扫描路径:@SpringBootApplication(scanBasePackages = “com.ys”)
      • 或者:@ComponentScan 指定扫描路径
1
2
3
4
5
@SpringBootApplication
// 等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.ys.demo")
  • 各种配置拥有的默认值

    • 默认配置最终都会映射到某一个配置类上
    • 配置文件的值最终会绑定到配置类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • springboot提供了非常多的starter
    • 引入了哪些场景,这个场景的自动配置才会开启(没有引入的配置不会开启)
    • springboot所有的自动配置功能都在 spring-boot-autoconfigure包里面

容器功能

组件添加

@Configuration

  • 基本使用
  • full模式和lite模式
  • 最佳实践
    • 配置类组件之间无依赖关系,用Lite模式加速容器启动过程,减少判断
    • 配置类组件之间有依赖关系,方法会被条用得到之前单实例组件,用Full模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**  
* 1.配置类里面使用 @Bean 标注在方法上给容器注册组件,默认也是单实例的
* 2.配置类本身也是组件
* 3. proxyBeanMethods : 代理 bean 的方法(是否对配置类使用代理模式,如果使用代理模式,会在调用配置类里面的方法打的时候检查容器中是否已经存在组件,不存在才会真正调用方法创建组件,如果不使用代理模式,就不会进行检查,每次都创建一个新的组件)
* Full(proxyBeanMethods = true)、保证每个 @Bean 方法无论被调用多少此返回的组件都是单实例的
* Lite(proxyBeanMethods = false)、每个 @Bean 方法每次被调用返回的组件都是新创建出来的
* 组件存在依赖关系最好使用Full模式默认,如果具体业务需要每次新建组件,或者不存在依赖关系使用 Lite 模式
*/
@Configuration(proxyBeanMethods = true)//告诉Springboot这是一个配置类 == 配置文件
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多次获取的都是之前注册到容器中的单实例对象
*/
@Bean //给容器中添加组件,以方法名作为逐渐的id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
User user = new User("zhangshan", "18");
user.setCat(tomcat());
return user;
}
@Bean("cat")//也可以在注解中指定组件的id
public Cat tomcat(){
return new Cat("tomcat");
}
}

@Bean @Component @Controller @Service @Repository

  • @Bean:标记在方法上,代表方法返回的是在一个组件
  • @Component:标记在类上,表示这个类是一个组件
  • @Controller:标记在类上,表示这个类是一个控制器
  • @Service:表示这是一个业务逻辑组件
  • @Repository:表示这是一个数据库层组件

@ComponentScan @Import

  • @ComponentScan : 配置包扫描路径
  • @Import:给容器中创建某种类型的组件,默认组件的名称就是全类名
1
@Import({User.class, DBHelper.class})

@Conditional

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

条件装配相关注解图示

1
2
@ConditionalOnMissingBean(name = "tom")
//不存在名称为 tom 的组件时进行注入

原生配置文件引入

@ImportResource

1
2
@ImportResource("classpath:beans.xml")
//导入指定位置的xml配置文件

配置绑定

直接通过java的方式进行配置绑定:
需要去匹配配置文件中的key,很麻烦

1
2
3
4
5
6
7
8
9
10
11
12
13
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。
}
}
}

@ConfigurationProperties

1
2
mycar.brand=BYD  
mycar.price=1000

当我们在配置文件中写了如上面的两行配置,只需要进行下面的代码配置,springboot就会帮我们自动绑定相关配置(@ConfigurationProperties直接需要和 @Component 或者 @EnableConfigurationProperties 一起使用,只有在容器中的组件,才会拥有springboot提供的强大功能)

1
2
3
4
5
6
7
8
@Getter  
@Setter
@ConfigurationProperties(prefix = "mycar")
//前缀匹配,表示匹配哪些前缀开头的配置
public class Car {
private String brand;
private Integer price;
}

@EnableConfigurationProperties + @ConfigurationProperties

在配置类上标记@EnableConfigurationProperties注解,并在注解中指定启用哪一个类的配置,一定要在配置类上标记(标记了@Configuration注解的类)

1
2
3
@Configuration
@EnableConfigurationProperties(Car.class)
public class MyConfig {}

@Component + @ConfigurationProperties

在配置类上面标记@Component注解,表示将该类注册成一个组件

1
2
3
4
5
6
@Component  
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}

自动配置原理入门

引导加载自动配置类

查看SpringBootApplication注解的源码可以看到,这个注解是由三个注解组成的(已去除java注解定义必须的元注解)

1
2
3
4
5
@SpringBootConfiguration  
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@SpringBootConfiguration

@SpringBootConfiguration 注解的源码如下,它只是实现了一个 @Configuration 注解,表示被标记的类是一个配置类

1
2
@Configuration  
public @interface SpringBootConfiguration {}

@ComponentScan

@ComponentScan注解主要就是指定扫描包路径,springboot默认配置了一些过滤器,用来处理扫描路径

@EnableAutoConfiguration

EnableAutoConfiguration注解上面也是标注了两个注解

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

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

1
2
3
//使用 Import 注解给容器中导入一个组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

Registrar 类是一个静态内部类,里面一共有两个方法;
其中的第一个方法 registerBeanDefinitions 的第一个参数 AnnotationMetadata 元注解信息,这里的信息就是标注了 @SpringBootApplication 注解的类的信息,通过该注解获取到包路径信息,进行扫描。

1
2
3
4
5
6
7
8
9
10
11
12
13
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {  
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导进来,主启动类所在包
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
@Import({AutoConfigurationImportSelector.class})
  • 给容器中添加了 AutoConfigurationImportSelector 组件
  • 会调用 AutoConfigurationImportSelector 中的 selectImports 方法
  • selectImports 方法中会使用 getAutoConfigurationEntry(annotationMetadata);给容器中导入一些组件
  • getAutoConfigurationEntry 方法中,利用 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 获取到所有需要导入到容器中的配置类
  • Map<String, List<String>> loadSpringFactories(ClassLoader classLoader); 加载所有组件
  • 读取一个文件 META-INF/spring.factories
    • 默认扫描当前系统中所有的 META-INF/spring.factories 文件
    • spring-boot-autoconfigure-2.7.13.jar包里面也有 META-INF/spring.factories

注意:在springboot2.7开始,spring-boot-autoconfigure-2.7.13.jar中自动配置的配置类已经不在META-INF/spring.factories文件中了,而是转移到了/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,

factories文件加载配置类图示

目前截至到 2.7.13 版本 一共是是144个配置类

按需开启自动配置项

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

如下图可以做一个简单的解析
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
这一行配置表示要去判断spring.aop.auto这个配置项的值是否配置的true,matchIfMissing = true则表示如果没有进行配置也自动开启,意思就是默认开启,出除非配置了关闭才会关闭
AspectJAutoProxyingConfiguration内部静态类,上面的@Configuration表示这以上一个配置类,@ConditionalOnClass(Advice.class)注解表示当Advice这个类存在的时候才会生效,从图中可以看到该类为红色,所以目前是不生效的。

下面的ClassProxyingConfiguration内部静态类同样是一个配置类,@ConditionalOnMissingClass("org.aspectj.weaver.Advice")注解表示Advice这个类不存在的时候才会生效,而下面的@ConditionalOnProperty注解表示spring.aop.proxy-target-class这个类为true才会生效,同样,如果没有配置也会生效,所以该静态内部类里面的bean会生效。
AopAutoConfiguration类图示

修改默认配置

1
2
3
4
5
6
7
8
9
10
11
12
@Bean  
//如果容器包含MultipartResolver类型的bean这个配置才会生效
@ConditionalOnBean(MultipartResolver.class)
//并且这个类型的bean的名称不是 multipartResolver 这个配置才会生效
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
//给@Bean的方法传了入参,那么这个参数的值就会在容器中找
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
//这段代码的整体含义就是如果容器中有一个 MultipartResolver 类型的bean,但这个bean的名称并不是 multipartResolver ,就把这个bean重新进行注入,并且设置名称为 multipartResolver (bean名称和方法名相同)
//主要是因为文件解析器的bean名称必须是 multipartResolver ,但是有时候用户自定义的名称并不一定就正确,通过这种方式将bean的名称修改正确

springboot会在底层配置好所有必须的bean,但如果用户也进行了配置,那么以用户配置的优先

1
2
3
@Bean  
@ConditionalOnMissingBean //如果没有配置这个bean,那么该配置生效
public CharacterEncodingFilter characterEncodingFilter() { }

总结:

  • SpringBoot先加载所有的自动配置类,xxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件类,类中会设置一些默认值,也可以通过配置文件进行修改。xxxProperties
  • 生效的配置类就会给容器中装配很多bean
  • 只要容器中有这些bean,就相当于这些功能都有了
  • 定制化配置
    • 用户直接自己用@Bean自定义bean替换底层的bean
    • 用户去看这个bean获取的配置文件是什么值进行修改,或者从官网文档看。

Common Application Properties (spring.io)

xxxAutoConfiguration —> 组件 —> xxxProperties里面取值 —> application.properties

最佳实践

  • 引入场景依赖
  • 查看自动配置了哪些(选做)
    • 自己分析,引入场景的自动配置一般都会生效
    • 配置文件中添加 dubug=true 开启自动配置报告。
      • Positive matches:表示已经生效的自动配置
      • Negative matches:表示没有生效的自动配置
  • 是都需要修改
    • 参照文档修改配置项
    • 自定义加入还或者替换组件
      + @Bean@Component ….
    • 自定义器 xxxCustomizer

开发小技巧

Lombok

简化开发,使用注解的方式在编译代码的时候自动生成getter、setter、有参构造、无参构造等方法

1
2
3
4
5
<dependency>  
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

dev-tools

热部署,修改了代码之后不需要再重启项目,可以自动重新加载(自动重启项目,并不是真正的热加载)。

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

Spring Initailizr(项目初始化)

  • 选择我们需要的开发场景
  • 自动依赖引入
  • 自动创建项目结构
  • 自动编写好主配置类

也可以使用在线方式:
spring:Spring Initializr
阿里云:Cloud Native App Initializer (aliyun.com)