Spring Boot 学习笔记

159 阅读6分钟

Spring Boot 学习笔记

基础回顾

约定优于配置

框架应该提供合理的默认值

两大特性

Spring 的优点

  • IOC
  • AOP

Spring 的缺点

  • 配置是重量级的
  • 依赖管理耗时耗力

解决方案(两大特性)

  • 起步依赖:依赖管理,解决 jar 包版本问题
  • 自动配置:将配置 bean 注册进 IOC 容器

案例实现

使用 Spring Initializr (start.spring.io) 创建项目

引入 spring-boot-starter-web 依赖

单元测试与热部署

单元测试

引入 spring-boot-starter-test 依赖

package com.chen4393.demo.controller;

@RestController // @Controller + @ResponseBody
public class DemoController {

    @RequestMapping("/demo")
    public String demo() {
        return "你好, Spring Boot!";
    }
}
package com.chen4393.demo;

@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {

   @Autowired
   private DemoController demoController;

   @Test
   void contextLoads() {
      String demo = demoController.demo();
      Assertions.assertEquals("你好, Spring Boot!", demo);
   }
}

热部署

  1. 引入 spring-boot-devtools 依赖
  2. IDEA 中配置:
  • 【File】-【Settings】-【Compiler】,勾选【build project automatically】
  • 【shift】+【Alt】+【Ctrl】+【/】打开【Registry】菜单,勾选【compiler.automake.allow.when.app.runing】

全局配置文件

Properties 配置文件

application.properties

person.id=1
person.name=tom
person.hobby=吃饭,睡觉,看书
person.family=father,mother
person.map.k1=v1
person.map.k2=v2
person.pet.type=dog
person.pet.name=旺财

Yaml 配置文件

扩展名为 .yaml 或 .yml

application.yaml

person:
  id: 1
  hobby: [吃饭,睡觉,看书]
  name: tom
  family: [father, mother]
  map: {k1: v1, k2: v2}
  pet: {type: dog, name: 旺财}

application.properties 配置文件会覆盖 application.yaml 配置文件

利用配置文件注入属性值

使用 @ConfigurationProperties 注入属性值

@Component
@ConfigurationProperties(prefix = "person") // 将配置文件中以person开头的属性赋值
public class Person {

    private int id;
    private String name;
    private List<String> hobby;
    private String[] family;
    private Map<String, Object> map;
    private Pet pet;

    // getters and setters(required)

    // toString()
}

使用 @Value 注入属性值

@Component
public class Student {

    @Value("3")
    private int id;
    @Value("${person.name}")
    private String name;
}

自定义配置类

使用 @PropertySource 加载自定义配置文件

test.properties

test.id=101
test.name=chen4393

MyProperties.java

@Component
@PropertySource("classpath:test.properties")
@ConfigurationProperties(prefix = "test")
public class MyProperties {

    private int id;
    private String name;
    
    // getters and setters
    // toString()
}
package com.chen4393.demo;

@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {
   @Autowired
   private MyProperties myProperties;

   @Test
   void testMyProperties() {
      System.out.println(myProperties);
   }
}

使用 @Configuration 编写自定义配置类

package com.chen4393.demo.config;

public class MyService {
}
package com.chen4393.demo.config;

@Configuration
public class MyConfig {

    @Bean("iService")
    public MyService myService() {
        return new MyService();
    }
}
package com.chen4393.demo;

@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {

   @Autowired
   private ApplicationContext applicationContext;

   @Test
   void testIOC() {
      Assertions.assertTrue(applicationContext.containsBean("iService"));
   }
}

随机数设置及参数间引用

随机数设置

test.properties

test.id=${random.int[1,100]}
test.name=chen4393

参数间引用

application.properties

tom.age=${random.int[10,20]}
tom.description=tom的年龄可能是${tom.age}
package com.chen4393.demo;

@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {

   @Value("${tom.description}")
   private String description;

   @Test
   void placeHolderTest() {
      System.out.println(description);
   }
}

源码剖析

依赖管理

为什么不需要指定版本号?

从 pom.xml 文件出发,找到它的 parent 名为 spring-boot-starter-parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    ...
</dependencies>

进入 spring-boot-starter-parent 文件,发现还有一个 parent 名为 spring-boot-dependencies


<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.1.RELEASE</version>
</parent>

spring-boot-dependencies 文件定义了需要哪些依赖及其版本号,对依赖进行统一管理

<properties>
    <activemq.version>5.15.12</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.80</appengine-sdk.version>
    <artemis.version>2.12.0</artemis.version>
    <aspectj.version>1.9.5</aspectj.version>
    <assertj.version>3.16.1</assertj.version>
    ...
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-amqp</artifactId>
            <version>${activemq.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-blueprint</artifactId>
            <version>${activemq.version}</version>
        </dependency>
        ...
    </dependencies>
</dependencyManagement>

注意:如果 pom.xml 引入的依赖不是由 spring-boot-starter-parent 管理的,则需要指定版本号

项目运行依赖的 jar 包是在哪里来的?

进入 spring-boot-starter-web 依赖,发现其中包含 web 开发需要的所有底层依赖

...
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.3.1.RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <scope>compile</scope>
</dependency>
...

如何查找依赖启动器?

打开依赖启动器的官方文档,可以查看所有 starters

第三方依赖启动器:在 Spring Boot 官方没有整合的情况下,像 Mybatis 和 Druid 这样的第三方团队主动与其进行整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter 和 druid-spring-boot-starter 等依赖启动器

不过,导入第三方依赖启动器需要配置版本号!

自动配置(启动流程)

@SpringBootApplication 入口注解

​ @SpringBootConfiguration 表明该类为配置类

​ @EnableAutoConfiguration 开启自动配置

​ @AutoConfigurationPackage 扫描入口注解的类所在的包及其子包并添加到容器中

​ @Import(AutoConfigurationImportSelector.class) 添加 spring.factories 定义的 bean 到容器

​ @ComponentScan 包扫描

从启动类出发,进入 @SpringBootApplication 的定义,发现核心就是 @SpringBootConfiguration 和 @EnableAutoConfiguration 这两个注解

package org.springframework.boot.autoconfigure;

@Target(ElementType.TYPE)  // 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME)  // 表示注解的生命周期,Runtime运行时
@Documented  // 表示注解可以记录在javadoc中
@Inherited  // 表示可以被子类继承该注解
@SpringBootConfiguration  // 标明该类为配置类
@EnableAutoConfiguration  // 启动自动配置功能
@ComponentScan(excludeFilters = {  // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}

@SpringBootConfiguration

本质是配置类

package org.springframework.boot;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration //配置类
public @interface SpringBootConfiguration {

}

@EnableAutoConfiguration 开启自动配置

实现自动配置的核心注解

package org.springframework.boot.autoconfigure;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// 获取@springbootApplication标注类所在的包名,并且对该包及其子包进行扫描,将组件添加到容器中
@AutoConfigurationPackage
// 向容器添加一些由AutoConfigurationImportSelector指定的组件
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   // 排除某些配置类
   Class<?>[] exclude() default {};

   // 排除某些配置文件
   String[] excludeName() default {};

}

@AutoConfigurationPackage 实现扫描主配置类所在包及其子包

package org.springframework.boot.autoconfigure;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// 向容器中添加一些由Registrar指定的组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

Registrar 类的作用:注册 bean (@SpringBootApplication 标注的主配置类所在的包及其子包下所有组件),通过 debug 可以看到 new PackageImport(metadata).getPackageName() 返回的是包名 com.chen4393.demo

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

  // 获取的是项目主程序启动类所在的目录
  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // metadata: 注解标注的元数据信息
    // 默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
    register(registry, new PackageImport(metadata).getPackageName());
  }
...
}

因此我们之前在 com.chen4393.demo 下定义项目主程序启动类,并在这个包下定义子包 controller

@Import(AutoConfigurationImportSelector.class) 实现将配置类信息交给加载器

@Import 的作用:将多个配置类导入到主配置类中,避免将所有配置都写到主配置类中。因此这句话的意思是向容器添加一些组件,添加哪些组件由 AutoConfigurationImportSelector 来指定

进入 AutoConfigurationImportSelector 的定义,找到方法 selectImports

package org.springframework.boot.autoconfigure;

public class AutoConfigurationImportSelector {
  ...
  @Override
  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    }
    // 获取 AutoConfigurationEntry 对象,该对象保存配置信息
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  }
}

进入 getAutoConfigurationEntry 方法,该方法为实现自动配置的核心方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  }
  AnnotationAttributes attributes = getAttributes(annotationMetadata); 
  // 获取待添加到容器的配置信息
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  // 去重
  configurations = removeDuplicates(configurations);
  // 排除某些不希望自动配置的配置类
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  configurations.removeAll(exclusions);
  // 筛选出最终符合当前项目运行环境对应的自动配置类
  configurations = getConfigurationClassFilter().filter(configurations);
  // 触发AutoConfigurationImportEvents事件
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}

进入 getCandidateConfigurations 方法

... getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  // 获取需要加载的工厂类全名
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
  ...
}

进入 loadFactoryNames 方法

public final class SpringFactoriesLoader {

  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  ...
  public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  }

  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    ...
    Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    ...
  }
  ...
}

找到 spring-boot-autoconfigure-2.3.1.RELEASE.jar 下面的 META-INF/spring.factories 配置文件,其实每个 jar 下面都有一个这样的文件

...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
...

底层实现自动配置的步骤总结

  1. 应用启动
  2. @SpringBootApplication 标明该类为配置类
  3. @EnableAutoConfiguration 开启自动配置
  4. @AutoConfigurationPackage - @Import(AutoConfigurationPackages.Registrar.class) 将 Registrar 类导入到容器,而 Registrar 类的作用是获取项目主程序启动类所在的目录,并将相应组件导入到容器;
  5. @Import(AutoConfigurationImportSelector.class) 将 AutoConfigurationImportSelector 类导入到容器,而 AutoConfigurationImportSelector 类的作用是通过 selectImports 方法使用内部工具类 SpringFactoriesLoader 查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程;
  6. @ComponentScan 扫描主配置类所在的包及其子包

自定义 Starter

新建一个和之前 demo 项目同级的 module 模块,选择普通 maven 工程,命名为 my-spring-boot-starter

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

新建一个实体类 SimpleBean

package com.chen4393.pojo;

@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {

    private int id;
    private String name;
    // getters and setters
    // toString()
}

新建一个自定义的自动配置类 MyAutoConfiguration

package com.chen4393.config;

@Configuration
@ConditionalOnClass
public class MyAutoConfiguration {

    static {
        System.out.println("MyAutoConfiguration init...");
    }

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }
}

在 resources 下创建 META-INF/spring.factories 配置文件,配置自己定义的自动配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.chen4393.config.MyAutoConfiguration

在 demo 工程下的 application.properties 配置文件下添加属性值

simplebean.id=1
simplebean.name=自定义starter

测试

@Autowired
private SimpleBean simpleBean;

@Test
public void myStarterTest() {
    System.out.println(simpleBean);
}

执行原理

数据访问

视图技术

缓存管理