avatar


23.SpringBoot [3/3]

Bean

加载方式

关于该部分的内容,很多都是我们在《15.Spring Framework [1/2]》讨论过的,这里相当于做一个小结。
所举的很多例子,也都来自《15.Spring Framework [1/2]》

配置文件和bean标签

1
2
<bean id="bookDao" class="com.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl"/>

配置文件和注解

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.stereotype.Component;

@Component("bookDao")
public class BookDaoImpl implements BookDao {

public void save() {
System.out.println("saving...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.stereotype.Component;

@Component
public class BookServiceImpl implements BookService {
private BookDao bookDao;

public void save() {
System.out.println(this.bookDao);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<?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
http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.kakawanyifan"/>

</beans>

​当然,由于我们无法在第三方提供包中添加注解,因此当我们需要加载第三方开发的Bean的时候,可以将@Bean定义在一个方法上方,方法的返回值就交给Spring管控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kakawanyifan;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/s");
ds.setUsername("root");
ds.setPassword("MySQL@2022");
return ds;
}
}

需要注意的是,这个包要能被Spring配置类加载到。而且在《15.Spring Framework [1/2]》我们说,如果在"JdbcConfig"加注解的话,一定是@Configuration注解,不能是@Component等其他注解。

因为@Configuration注解中有一个属性proxyBeanMethods,且该值默认为true

我们来看例子。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.kakawanyifan;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = true)
@ComponentScan("com.kakawanyifan")
public class SpringConfig {

@Bean
public Cat cat(){
return new Cat();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
SpringConfig springConfig = ctx.getBean("springConfig", SpringConfig.class);
System.out.println(springConfig.cat());
System.out.println(springConfig.cat());
System.out.println(springConfig.cat());
}
}

运行结果:

1
2
3
com.kakawanyifan.Cat@240237d2
com.kakawanyifan.Cat@240237d2
com.kakawanyifan.Cat@240237d2

我们看到,我们执行了三次springConfig.cat(),居然都是同一个对象。

  • 如果我们把proxyBeanMethods设置为false,即@Configuration(proxyBeanMethods = false),则三次是三个不同的对象。
  • 如果我们不采用@Configuration注解,三次是三个不同的对象。
  • 如果我们把proxyBeanMethods设置为true,但是我们注释掉cat()方法上的@Bean注解,三次也是三个不同的对象,因为此时没有方法的返回值Cat的Bean对象交给Spring管理。

如果是一个数据库连接的对象,因为在程序中不宜频繁的创建数据库连接的对象,所以一定要用@Configuration注解。

注解声明配置类

定义一个类并使用@ComponentScan替代原来XML配置中的包扫描这个动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
@ComponentScan("com.kakawanyifan")
public class SpringConfig {

}

如果需要多组,采取数组的形式:

1
@ComponentScan({"com.kakawanyifan.service","com.kakawanyifan.dao"})

由于早期开发的系统大部分都是采用XML的形式配置Bean,现在基本上都是使用注解。如果我们要对原系统进行改造,做二次开发。可以考虑@ImportResource这个注解,在配置类上直接写上要被融合的XML配置文件名即可。

1
2
3
4
5
@Configuration
@ImportResource("applicationContext.xml")
public class SpringConfig {

}

FactroyBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import com.kakawanyifan.service.impl.BookServiceImpl;
import org.springframework.beans.factory.FactoryBean;

public class BookServiceFactoryBean implements FactoryBean<BookService> {
//代替原始实例工厂中创建对象的方法
public BookService getObject() throws Exception {
System.out.println("public BookService getObject");
return new BookServiceImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return BookService.class;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.kakawanyifan")
public class SpringConfig {

@Bean
public BookServiceFactoryBean bookService(){
return new BookServiceFactoryBean();
}
}

或者XML方式:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="bookService" class="com.kakawanyifan.BookServiceFactoryBean"/>

</beans>

通过这种方法,虽然我们配置的是类是BookServiceFactoryBean,但所得到的对象,是BookServiceImpl类。
具体我们可以验证一下,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(ctx.getBean("bookService"));
}
}

运行结果:

1
com.kakawanyifan.service.impl.BookServiceImpl@24a35978

有什么用呢?可以在对象初始化前,即BookServiceFactoryBeangetObject()方法中,做一些事情。例如,我们创建一个数据库的连接对象,可以在其中检查数据库是否成功启动。

@Import注解注入Bean

如果我们要加载的Bean无法添加@Component注解,而且是第三方的Bean,我们可以使用@Import

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;


@Configuration
@ComponentScan("com.kakawanyifan")
@Import(JdbcConfig.class)
public class SpringConfig {

}

ImportSelector

可以有各种条件的判定,最后决定是否装载指定的Bean。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kakawanyifan;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//各种条件的判定,判定完毕后,决定是否装载指定的bean
boolean flag = annotationMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");
if(flag && annotationMetadata.getClassName().equals("com.kakawanyifan.SpringConfig")){
return new String[]{"com.kakawanyifan.Cat"};
}
return new String[0];
}
}
1
2
3
4
5
6
7
8
9
10
package com.kakawanyifan;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(MyImportSelector.class)
public class SpringConfig {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String iter:beanDefinitionNames) {
System.out.println(iter);
}
}
}

运行结果:

1
2
3
4
5
6
7
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
com.kakawanyifan.Cat

因为我们是@Import(MyImportSelector.class),所以在selectImports(AnnotationMetadata annotationMetadata)方法中,annotationMetadata.getClassName()的值是com.kakawanyifan.SpringConfig

ImportBeanDefinitionRegistrar

在Spring中定义了BeanDefinition,这个控制Bean初始化加载的核心。BeanDefinition接口中给出了若干种方法,可以控制Bean的相关属性,例如创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个。

实现ImportBeanDefinitionRegistrar接口,我们可以根据条件,控制Bean的属性。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.kakawanyifan;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
if (metadata.getClassName().equals("com.kakawanyifan.SpringConfig-EEEEEEEE")){
registry.registerBeanDefinition("tom",beanDefinition);
}else {
registry.registerBeanDefinition("tomcat",beanDefinition);
beanDefinition.setScope(SCOPE_PROTOTYPE);
}
}
}
1
2
3
4
5
6
7
8
9
10
package com.kakawanyifan;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringConfig {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String iter:beanDefinitionNames) {
System.out.println(iter);
}
System.out.println(ctx.getBean("tomcat"));
System.out.println(ctx.getBean("tomcat"));
System.out.println(ctx.getBean("tomcat"));
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
tomcat
com.kakawanyifan.Cat@4e41089d
com.kakawanyifan.Cat@32a068d1
com.kakawanyifan.Cat@33cb5951

最终裁定

在上文,我们讨论了很多种Bean的加载方式,但是这里有一个BUG。这么多种方式,如果之间有冲突怎么办?谁能有最终裁定权?

BeanDefinitionRegistryPostProcessor,这个有最终裁定权。
其实看名字就知道,BeanDefinition是Bean定义,Registry是注册,Post是后置,Processor是处理器,全称bean定义后处理器。即在所有Bean注册都折腾完后,它进行最后的处理。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.kakawanyifan;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON;

public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.getBeanDefinition("tomcat").setScope(SCOPE_SINGLETON);
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

}
}
1
2
3
4
5
6
7
8
9
10
package com.kakawanyifan;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({MyImportBeanDefinitionRegistrar.class,MyBeanDefinitionRegistryPostProcessor.class})
public class SpringConfig {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String iter:beanDefinitionNames) {
System.out.println(iter);
}
System.out.println(ctx.getBean("tomcat"));
System.out.println(ctx.getBean("tomcat"));
System.out.println(ctx.getBean("tomcat"));
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
com.kakawanyifan.MyBeanDefinitionRegistryPostProcessor
tomcat
com.kakawanyifan.Cat@365c30cc
com.kakawanyifan.Cat@365c30cc
com.kakawanyifan.Cat@365c30cc

手工加载Bean

上文讨论的加载Bean的方式都是在容器启动阶段完成Bean的加载,这种方法可以在容器初始化完成后手动加载Bean。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.kakawanyifan;

public class Cat {

int age;

public Cat() {

}

public Cat(int age) {
this.age = age;
}

@Override
public String toString() {
return "Cat{" +
"age=" + age +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.registerBean("tom", Cat.class);
System.out.println(ctx.getBean("tom"));
}
}

运行结果:

1
Cat{age=0}

(为什么age的值是0呢,我们在《1.基础语法》讨论过,整数,默认值:0。)

如果容器中已经有了某种Bean,再加载会覆盖。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.kakawanyifan;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.registerBean("tom", Cat.class);
ctx.registerBean("tom", Cat.class,1);
Cat tom = ctx.getBean("tom", Cat.class);
tom.age = 100;
ctx.registerBean("tom", Cat.class,2);
System.out.println(ctx.getBean("tom"));
}
}

运行结果:

1
Cat{age=2}

那么,BeanDefinitionRegistryPostProcessor手工加载Bean,谁能最后定义Bean呢?
当然是手工加载Bean,因为这种方法是在容器初始化完成后进行的。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kakawanyifan;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.registerBean("tomcat", Cat.class,2);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String iter:beanDefinitionNames) {
System.out.println(iter);
}
System.out.println(ctx.getBean("tomcat"));
System.out.println(ctx.getBean("tomcat"));
System.out.println(ctx.getBean("tomcat"));
}
}
运行结果:
1
2
3
4
5
6
7
8
9
10
11
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
com.kakawanyifan.MyBeanDefinitionRegistryPostProcessor
tomcat
Cat{age=2}
Cat{age=2}
Cat{age=2}

加载控制

@Conditional注解

上文,我们讨论了ImportSelectorImportBeanDefinitionRegistrar等,都可以控制Bean的加载。
Spring把这些操作给我们做了封装,提供了一个@Conditional注解,注解中需要我们填我们对于"Condition"接口的实现类。
不过,SpringBoot给我们提供了更多的封装好的注解,这样我们只需要填条件。

@Conditional注解的衍生注解

我们需要导入SpringBoot的依赖,在这里我们只导入spring-boot-starter

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>

(为了避免冲突,我们可以删去之前导入的Spring的依赖。)

然后,我们可以看到有各种各样的@Conditional注解的衍生注解。

衍生注解

例子

如果有类com.kakawanyifan.App,并且没有类com.kakawanyifan.AAAApp,并且有Beancom.kakawanyifan.Mouse,才加载Cat。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.kakawanyifan;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(Mouse.class)
public class SpringConfig {

@Bean
@ConditionalOnClass(name = "com.kakawanyifan.App")
@ConditionalOnMissingClass("com.kakawanyifan.AAAApp")
@ConditionalOnBean(name = "com.kakawanyifan.Mouse")
public Cat cat(){
return new Cat();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String iter:beanDefinitionNames) {
System.out.println(iter);
}
}
}

运行结果:

1
2
3
4
5
6
7
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
com.kakawanyifan.Mouse
cat

注意,通过@Import注解导入的Bean,名字是全限定名。

自动配置

分析

我们从@SpringBootApplication这个注解开始,其内容如下:

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@ComponentScan的参数是两个扫描排除类,其他的一些属于元注解,我们主要关注:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration

@SpringBootConfiguration

@SpringBootConfiguration的内容如下:

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

其中的@Configuration@Indexed,实际没有值得分析的内容:

1
2
3
4
5
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
1
2
3
4
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Indexed {

@EnableAutoConfiguration

@EnableAutoConfiguration的内容如下:

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

我们主要关注:

  • @AutoConfigurationPackage
  • @Import({AutoConfigurationImportSelector.class})的类AutoConfigurationImportSelector

@AutoConfigurationPackage的内容如下:

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

我们主要关注@Import({AutoConfigurationPackages.Registrar.class})AutoConfigurationPackages.Registrar

综上所述,我们主要关注:

  • AutoConfigurationImportSelector
  • AutoConfigurationPackages.Registrar

Registrar

我们点进AutoConfigurationPackages.RegistrarRegistrarAutoConfigurationPackages这个抽象类的一个静态内部类,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.springframework.boot.autoconfigure;

【部分代码略】

public abstract class AutoConfigurationPackages {

【部分代码略】

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

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

}

【部分代码略】

}

Registrar实现了ImportBeanDefinitionRegistrar接口,这个接口我们在上文讨论过,可以根据条件,控制Bean的属性。

我们在register方法这一行打断点,通过"执行表达式",查看第二个参数的内容,是com.kakawanyifan

new PackageImports(metadata).getPackageNames().toArray(new String[0])

@AutoConfigurationPackage注解的主要作用是获取项目主程序启动类所在根目录,从而指定后续组件扫描器要扫描的包位置。
因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描。

EnableAutoConfiguration

EnableAutoConfiguration的重点是AutoConfigurationImportSelector

selectImports

我们点开AutoConfigurationImportSelector,发现其实现了DeferredImportSelector,再点开DeferredImportSelector,发现其继承了ImportSelector。根据我们上文关于ImportSelector的讨论,我们主要关注其selectImports方法。内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.springframework.boot.autoconfigure;

【部分代码略】

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

【部分代码略】

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

【部分代码略】

}

点进!isEnabled(annotationMetadata),内容如下:

1
2
3
4
5
6
7
8
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}

isEnabled(annotationMetadata)的含义是,检查自动配置是否开启,默认是开启的。

getAutoConfigurationEntry

点进getAutoConfigurationEntry(annotationMetadata),内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

if之后的第一行代码,AnnotationAttributes attributes = getAttributes(annotationMetadata);的作用是获取@EnableAutoConfiguration注解的属性,有excludeexcludeName两个。

if之后的最后一行代码,点进new AutoConfigurationEntry(configurations, exclusions),内容如下:

1
2
3
4
AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
this.configurations = new ArrayList<>(configurations);
this.exclusions = new HashSet<>(exclusions);
}

只是组装成一个实体类进行返回。

其他代码,可以分为四个部分:

  1. 加载自动配置组件:
    1
    2
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
  2. 排除指定组件:
    1
    2
    3
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
  3. 过滤自动配置组件:
    1
    configurations = getConfigurationClassFilter().filter(configurations);
  4. 事件注册:
    1
    fireAutoConfigurationImportEvents(configurations, exclusions);

加载自动配置组件

1
2
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);

getCandidateConfigurations

点进getCandidateConfigurations方法:

1
2
3
4
5
6
7
8
9
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

SpringFactoriesLoader.loadFactoryNames方法,有两个参数

  • 第一个是自动装配类:
    1
    2
    3
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
    }
  • 第二个是类加载器:
    1
    2
    3
    protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
    }

也就是说,loadFactoryNames只会读取针对自动配置的注册类。

点进SpringFactoriesLoader.loadFactoryNames方法:

1
2
3
4
5
6
7
8
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

点进loadSpringFactories方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}

result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}

// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}

这一段代码很长,大致的含义就是加载指定的ClassLoader下面的所有FACTORIES_RESOURCE_LOCATION位置的文件,并将文件解析内容保存在一个Map<String, List<String>>的对象中,最后返回该对象。

我们点进FACTORIES_RESOURCE_LOCATION

1
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

找一个Spring相关的Jar包看看META-INF/spring.factories文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver

【部分代码略】

还有一行代码

1
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);

作用是,从需要自动装配的Jar包的META-INF/spring/%s.imports下找到对应的文件信息,把他加入到configurations集合中。

removeDuplicates

1
configurations = removeDuplicates(configurations);

因为程序默认加载的是ClassLoader下面的所有META-INF/spring.factories文件中的配置,难免在不同的Jar包中出现重复的配置。
所以,还有一个方法,removeDuplicates,去重:

1
2
3
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}

去重,在我们的开发工作中,是一个很常见的现象。而SpringBoot的去重方法,非常值得我们参考。

排除指定组件

1
2
3
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);

getExclusions

通过"加载自动配置组件",我们获取了spring.factories文件中注册的自动加载组件,但如果在实际应用的过程中并不需要某些组件,可以排除。

  • 可以通过配置@EnableAutoConfiguration的注解属性excludeexcludeName进行有针对性的排除。
  • 可以通过配置文件进行排除。

我们点进getExclusions

1
2
3
4
5
6
7
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(asList(attributes, "excludeName"));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}

其中excluded.addAll(asList(attributes, "exclude"));excluded.addAll(asList(attributes, "excludeName"));实现了:

  • 通过@EnableAutoConfiguration的注解属性excludeexcludeName进行有针对性的排除

再点进getExcludeAutoConfigurationsProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
protected List<String> getExcludeAutoConfigurationsProperty() {
Environment environment = getEnvironment();
if (environment == null) {
return Collections.emptyList();
}
if (environment instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(environment);
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
.orElse(Collections.emptyList());
}
String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

该段代码实现了:

  • 通过配置文件进行排除。

checkExcludedClasses

点进checkExcludedClasses方法:

1
2
3
4
5
6
7
8
9
10
11
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
for (String exclusion : exclusions) {
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}

点进handleInvalidExcludes方法:

1
2
3
4
5
6
7
8
9
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
message));
}

checkExcludedClasses方法用来确保被排除的类存在于当前的ClassLoader中,并且包含在spring.factories注册的集合中。如果不满足以上条件,调用handlefnvalidExcludes方法拋出异常。

configurations.removeAll

1
configurations.removeAll(exclusions);

该方法从自动配置集合中移除被排除集合的类。

  • configurations的类型是List<String>
  • exclusions的类型是Set<String>

从某个集合中剔除一些内容,在我们的开发工作中,是一个很常见的现象。而SpringBoot的剔除方法,非常值得我们参考。

过滤自动配置组件

1
configurations = getConfigurationClassFilter().filter(configurations);

getConfigurationClassFilter

点进getConfigurationClassFilter方法:

1
2
3
4
5
6
7
8
9
10
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}

再点进List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();getAutoConfigurationImportFilters()

1
2
3
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

即,获取spring.factories中Key为AutoConfigurationImportFilter的Filters列表。

AutoConfigurationImportFilter的相关内容:

1
2
3
4
5
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

默认配置了三个筛选条件:OnBeanConditionOnClassCondition、和OnWebApplicationCondition

再点进this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);,这是一个构造方法:

1
2
3
4
5
6
7
8
9
10
11
private static class ConfigurationClassFilter {

private final AutoConfigurationMetadata autoConfigurationMetadata;

private final List<AutoConfigurationImportFilter> filters;

ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
this.filters = filters;
}
}

即,上文的三个筛选条件,最后作为了ConfigurationClassFilter对象的成员变量filters

filter

点进filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}

遍历成员变量filters,并且参与到boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
filter.match方法有两个参数,candidates就来自入参configurations,还有一个入参是this.autoConfigurationMetadata

autoConfigurationMetadata来自ConfigurationClassFilter的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
private static class ConfigurationClassFilter {
private final AutoConfigurationMetadata autoConfigurationMetadata;
private final List<AutoConfigurationImportFilter> filters;

ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
this.filters = filters;
}

【部分代码略】

}

再点进this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);

1
2
3
4
5
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}

即,autoConfigurationMetadata,来自spring-autoconfigure-metadata.propertics中配置对应实体类。

我们找一个spring-autoconfigure-metadata.propertics看看:

1
2
3
4
5
6
7
8
9
10
11
12
org.springframework.boot.autoconfigure.AutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitMessagingTemplate
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.autoconfigure.amqp.RabbitStreamConfiguration=

【部分代码略】

过滤条件为该列表中自动配置类的注解得包含在OnBeanConditionOnClassConditionOnWebApplicationCondition中指定的注解,即,得包含@ConditionalOnBean@ConditionalOnClass@ConditionalOnWebApplication

事件注册

1
fireAutoConfigurationImportEvents(configurations, exclusions);

点进fireAutoConfigurationImportEvents方法:

1
2
3
4
5
6
7
8
9
10
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}

点进getAutoConfigurationImportListeners方法:

1
2
3
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

过程如下:

  1. 通过SpringFactoriesLoader类提供的loadFactories方法将spring.factories中配置的接口AutoConfigurationImportListener的实现类加载出来。
  2. 将筛选出的自动配置类集合和被排除的自动配置类集合封装成AutoConfigurationimportEvent事件对象,并传人该事件对象通过监听器提供的onAutoConfigurationImportEvent方法。
  3. 进行事件广播。

构造流程

分析

我们从一个引导类的main方法开始,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

}

点进其中的run方法:

1
2
3
4
5
6
7
8
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}


public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

这里有两个run方法,我们可以打上断点,会先在第一个上暂停,然后在第二个上暂停。

点进第二个的new SpringApplication(primarySources)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}


@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

也有两个,也是会先在第一个上暂停,然后在第二个上暂停。
重点关注第二个方法的如下几行:

  • 赋值成员变量resourceLoader
    1
    this.resourceLoader = resourceLoader;
  • 赋值成员变量primarySources
    1
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  • 推断Web应用类型:
    1
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
  • 加载并初始化ApplicationContextInitializer及其相关实现类:
    1
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  • 加载并初始化ApplicationListener及其相关实现类:
    1
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  • 推断main方法:
    1
    this.mainApplicationClass = deduceMainApplicationClass();

推断Web应用类型

点进WebApplicationType.deduceFromClasspath()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };

private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

deduceFromClasspath的推断逻辑如下:

  • 如果DispatcherHandler存在,并且DispatcherServletServleContainer都不存在:
    • 返回类型为REACTIVE
  • 否则:
    • 如果SERVLETConfigurableWebApplicationContext任何一个不存在:
      • 说明当前应用为非Web应用,返回NONE
    • 否则:
      • 说明当前应用为SERVLET应用,返回SERVLET

ApplicationContextInitializer

1
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

该部分其实有两个方法:setInitializersgetSpringFactoriesInstances

getSpringFactoriesInstances

点进getSpringFactoriesInstances方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

其中SpringFactoriesLoader.loadFactoryNames(type, classLoader)获取的是spring.factories中注册的对应配置。
我们可以打断点看一下,type的值:

1
org.springframework.context.ApplicationContextInitializer

找一个spring.factories看一下,内容如下:

1
2
3
4
5
6
7
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

点进createSpringFactoriesInstances方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}

重点关注如下几行:

  • 获取Class:
    1
    Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  • 获取构造方法:
    1
    Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
  • 生成对象:
    1
    T instance = (T) BeanUtils.instantiateClass(constructor, args);

setInitializers

在完成获取配置类集合和实例化操作之后,调用setinitializers方法将实例化的集合添加到SpringApplication的成员变量initializers中。

1
2
3
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}

ApplicationListener

1
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

ApplicationListener的整个配置和加载流程与ApplicationContextlnitializer完全一致,也是先通过SpringFactoriesLoaderloadFactoryNames方法获取spring.factories中对应配置,然后再进行实例化,最后将获得的结果集合添加到SpringApplication的成员
变量listeners中。

我们可以点击方法看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

推断main方法

1
this.mainApplicationClass = deduceMainApplicationClass();

点进deduceMainApplicationClass()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}

main方法的推断逻辑如下:

  • 创建一个运行时异常,然后获得栈数组
  • 遍历栈数组:
    • 判断类的方法中是否包含main方法,第一个被匹配的类会通过Class.forName方法创建对象,并将其被返回

最后,会通过this.mainApplicationClass = deduceMainApplicationClass();,将对象赋值给SpringApplication的成员变量mainApplicationClass

运行流程

分析

同样,我们从一个引导类的main方法开始,其中很多内容,我们在上文已经讨论过了。

1
2
3
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

这次重点关注其中的run(args),点进run(args)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}

重点关注如下方法:

  • 获得监听器数组:
    1
    SpringApplicationRunListeners listeners = getRunListeners(args);
  • 初始化ApplicationArguments
    1
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  • 初始化ConfigurableEnvironment
    1
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  • 应用上下文的创建:
    1
    context = createApplicationContext();
  • 应用上下文的准备:
    1
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  • 应用上下文的刷新:
    1
    refreshContext(context);
  • 执行所有运行器:
    1
    callRunners(context, applicationArguments);

获得监听器数组

1
SpringApplicationRunListeners listeners = getRunListeners(args);

为什么说是数组?
我们查看SpringApplicationRunListeners的构造方法:

1
2
3
4
5
6
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,
ApplicationStartup applicationStartup) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
this.applicationStartup = applicationStartup;
}

配置与加载

点进getRunListeners(args)方法:

1
2
3
4
5
6
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}

关于SpringApplicationRunListeners构造方法,我们不再讨论。主要关注上文的getSpringFactoriesInstances方法:

1
2
3
4
5
6
7
8
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

其中的SpringFactoriesLoader.loadFactoryNames方法,我们在上文已经讨论了很多遍。这里打个断点,看看type的值:

1
org.springframework.boot.SpringApplicationRunListener

找一个spring.factories看一下,内容如下:

1
2
3
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

关于createSpringFactoriesInstances方法,与之前讨论的几乎一致,这里不赘述。

SpringApplicationRunListener接口

我们再来简单的分析一下SpringApplicationRunListener接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

package org.springframework.boot;

import java.time.Duration;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;

/**
* Listener for the {@link SpringApplication} {@code run} method.
* {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
* and should declare a public constructor that accepts a {@link SpringApplication}
* instance and a {@code String[]} of arguments. A new
* {@link SpringApplicationRunListener} instance will be created for each run.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @author Chris Bono
* @since 1.0.0
*/
public interface SpringApplicationRunListener {

/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
* @param bootstrapContext the bootstrap context
*/
default void starting(ConfigurableBootstrapContext bootstrapContext) {
}

/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param bootstrapContext the bootstrap context
* @param environment the environment
*/
default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
}

/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
* @param context the application context
*/
default void contextPrepared(ConfigurableApplicationContext context) {
}

/**
* Called once the application context has been loaded but before it has been
* refreshed.
* @param context the application context
*/
default void contextLoaded(ConfigurableApplicationContext context) {
}

/**
* The context has been refreshed and the application has started but
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
* ApplicationRunners} have not been called.
* @param context the application context.
* @param timeTaken the time taken to start the application or {@code null} if unknown
* @since 2.6.0
*/
default void started(ConfigurableApplicationContext context, Duration timeTaken) {
started(context);
}

/**
* The context has been refreshed and the application has started but
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
* ApplicationRunners} have not been called.
* @param context the application context.
* @since 2.0.0
* @deprecated since 2.6.0 for removal in 3.0.0 in favor of
* {@link #started(ConfigurableApplicationContext, Duration)}
*/
@Deprecated
default void started(ConfigurableApplicationContext context) {
}

/**
* Called immediately before the run method finishes, when the application context has
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
* {@link ApplicationRunner ApplicationRunners} have been called.
* @param context the application context.
* @param timeTaken the time taken for the application to be ready or {@code null} if
* unknown
* @since 2.6.0
*/
default void ready(ConfigurableApplicationContext context, Duration timeTaken) {
running(context);
}

/**
* Called immediately before the run method finishes, when the application context has
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
* {@link ApplicationRunner ApplicationRunners} have been called.
* @param context the application context.
* @since 2.0.0
* @deprecated since 2.6.0 for removal in 3.0.0 in favor of
* {@link #ready(ConfigurableApplicationContext, Duration)}
*/
@Deprecated
default void running(ConfigurableApplicationContext context) {
}

/**
* Called when a failure occurs when running the application.
* @param context the application context or {@code null} if a failure occurred before
* the context was created
* @param exception the failure
* @since 2.0.0
*/
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}

}

根据注释,run方法与SpringApplicationRunListener的方法之间的关系如下:

SpringApplicationRunListener

EventPublishingRunListener

在上文,我们还提到了位于spring.factories的如下内容:

1
2
3
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener是对于SpringApplicationRunListener的唯一实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package org.springframework.boot.context.event;

【部分代码略】

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

private final SpringApplication application;

private final String[] args;

private final SimpleApplicationEventMulticaster initialMulticaster;

public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}

@Override
public int getOrder() {
return 0;
}

@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}

@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
ailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}

【部分代码略】

}

我们观察其构造方法:

  • application:类型为SpringApplication,是当前运行的SpringApplication实例。
  • args:启动程序时的命令参数。
  • initialMulticaster:类型为SimpleApplicationEventMulticaster,事件广播器。

最后,还有一段代码,作用是:遍历ApplicationListener并关联SimpleApplicationEventMulticaster

再观察起监听作用的代码,startingstarted,其逻辑是:

  1. 程序启动到某个步骤后,调用EventPublishingRunListener的某个方法(例如,starting)。
  2. EventPublishingRunListener的具体方法将application参数和args参数封装到对应的事件中。这里的事件均为SpringApplicationEvent的实现类。
  3. 通过成员变量initialMulticastermulticastEvent方法对事件进行广播,或通过该方法的ConfigurableApplicationContext的参数的publishEvent方法来对事件进行发布
  4. 对应的ApplicationListener被触发,执行相应的业务逻辑。

ApplicationArguments

1
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

ApplicationArguments的初始化过程非常简单,只是调用了其实现类DefaultApplicationArguments的构造方法,并传人了main方法中的args参数。

ConfigurableEnvironment

1
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

prepareEnvironment

点进prepareEnvironment方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}

我们重点关注:

  • 获取或创建环境:
    1
    ConfigurableEnvironment environment = getOrCreateEnvironment();
  • 配置环境:
    1
    configureEnvironment(environment, applicationArguments.getSourceArgs());

我们还会看到有这么一行

1
listeners.environmentPrepared(bootstrapContext, environment);

点进去的话,就是SpringApplicationRunListener接口的实现类的environmentPrepared方法。这也印证了我们上文:说的run方法与SpringApplicationRunListener的方法之间的关系。

获取或创建环境

点进getOrCreateEnvironment();的方法:

1
2
3
4
5
6
7
8
9
10
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
}
return (environment != null) ? environment : new ApplicationEnvironment();
}

逻辑:

  • 如果environment存在:
    • 直接返回。
  • 否者:
    • 针对不同的应用类型,创建不同的环境。

配置环境

点进configureEnvironment的方法:

1
2
3
4
5
6
7
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}

转换服务

if (this.addConversionService),判断是否需要添加转换服务。

我们点进ApplicationConversionService(),看看到底什么是转换服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public ApplicationConversionService() {
this(null);
}


public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
this(embeddedValueResolver, false);
}


private ApplicationConversionService(StringValueResolver embeddedValueResolver, boolean unmodifiable) {
if (embeddedValueResolver != null) {
setEmbeddedValueResolver(embeddedValueResolver);
}
configure(this);
this.unmodifiable = unmodifiable;
}

点进configure(this)方法:

1
2
3
4
5
6
public static void configure(FormatterRegistry registry) {
DefaultConversionService.addDefaultConverters(registry);
DefaultFormattingConversionService.addDefaultFormatters(registry);
addApplicationFormatters(registry);
addApplicationConverters(registry);
}

例如,点进其中的DefaultConversionService.addDefaultConverters(registry)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);

converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

converterRegistry.addConverter(new ObjectToObjectConverter());
converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new FallbackObjectToStringConverter());
converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}

即,是各种数据类型的转换。

configurePropertySources

1
configurePropertySources(environment, args);

点进configurePropertySources方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (!CollectionUtils.isEmpty(this.defaultProperties)) {
DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}

这段代码需要重点看一下参数的优先级处理和默认参数与命令参数之间的关系:

  • 如果存在默认属性配置,则将默认属性配置放置在最后,也就是说优先级最低。
  • 如果存在命令参数:
    • 如果命令的参数已经存在于属性配置中
      • 则用命令行参数替换已有的。
    • 如果命令的参数并不存在于属性配置中
      • 直接将其设置为优先级最高。

(这也印证了我们在《21.SpringBoot [1/3]》说的,命令行的优先级最高。)

configureProfiles

1
configureProfiles(environment, args);

在2.7.7版本的SpringBoot中,这个方法是空的。

1
2
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
}

但在更早的版本中,其中应该是有内容的。

有内容

应用上下文的创建

1
context = createApplicationContext();

点进createApplicationContext()方法:

1
2
3
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}

点进create方法,我们会发现这是一个接口类中的方法。

具体我们不讨论,总之就是根据我们的应用类型,创建上下文。

(在《未分类【计算机】:IDEA中Debug的方法和技巧》中,我们讨论过方法断点,这里我们直接在方法上打断点,然后一步一步,看其逻辑。)

应用上下文的准备

1
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

prepareContext

点进prepareContext方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}

我们主要关注:

  • context设置environment
    1
    context.setEnvironment(environment);
  • 应用上下文后置处理:
    1
    postProcessApplicationContext(context);
  • 初始化context
    1
    applyInitializers(context);
  • 获得ConfigurableListableBeanFactory,并注册单例对象
    1
    2
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
  • 获取全部配置源:
    1
    Set<Object> sources = getAllSources();
  • 将配置源加载到context
    1
    load(context, sources.toArray(new Object[0]));

其中,有些方法很简单,甚至就一行代码。我们挑选部分逻辑复杂一些的进行讨论。

应用上下文后置处理

1
postProcessApplicationContext(context);

点进postProcessApplicationContext方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
}
}
if (this.addConversionService) {
context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
}
}

postProcessApplicationContext方法,完成操作包括beanNameGeneratorresourceLoaderClassLoaderConversionService的设置。

获取全部配置源

在进入该逻辑之前,获得了ConfigurableListableBeanFactory,并注册单例对象,并根据逻辑,设置了一些属性。

1
Set<Object> sources = getAllSources();
1
2
3
4
5
6
7
8
9
10
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}

以上操作逻辑很简单,如果Set集合中不存在primarySources配置源或sources配置源,则将其添加人Set中,最后将Set设置为不可修改。

  • primarySources来自SpringApplication的构造参数
  • sources来自setResources方法。

将配置源加载到context

load

1
load(context, sources.toArray(new Object[0]));

点进load方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}

createBeanDefinitionLoader

点进createBeanDefinitionLoader方法:

1
2
3
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}

我们发现,该方法主要通过BeanDefinitionLoader来完成配置资源的加载操作。

再点进BeanDefinitionLoader方法:

1
2
3
4
5
6
7
8
9
10
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

通过BeanDefinitionLoader的构造方法我们可以看到BeanDefinitionLoader支持基于AnnotatedBeanDefinitionReaderXmlBeanDefinitionReaderGroovyBeanDefinitionReader等多种类型的加载操作。

最后返回loader

loader.load()

再点进loader.load()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void load() {
for (Object source : this.sources) {
load(source);
}
}


private void load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
load((Class<?>) source);
return;
}
if (source instanceof Resource) {
load((Resource) source);
return;
}
if (source instanceof Package) {
load((Package) source);
return;
}
if (source instanceof CharSequence) {
load((CharSequence) source);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

从以上代码可以看出,BeanDefinitionLoader加载支持的范围包括:ClassResourcePackageCharSequence四种。

应用上下文的刷新

1
refreshContext(context);

点进refreshContext方法:

1
2
3
4
5
6
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}

再点进refresh方法:

1
2
3
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}

其中refresh方法调用的是AbstractApplicationContext的refresh方法,该类属于spring-context包,方法内部具体逻辑,我们不作讨论。

执行所有运行器

1
callRunners(context, applicationArguments);

点进callRunners方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}


private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}


private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}

过程:

  1. context中获得类型为ApplicationRunnerCommandLineRunmerBean,将它们放入List列表中并进行排序。
  2. 去重,同时遍历,依次执行。

最后,我们讨论一下,什么是Runner运行器。

Runner运行器分为两种:

  • 应用程序运行器(Runner)
  • 命令行运行器

其作用都是在SpringBoot应用程序启动后执行代码,可以使用这些接口在应用程序启动后立即执行一些操作。
两种除了参数不同,其它没区别。

应用程序运行器,实现ApplicationRunner接口,重写其run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.kakawanyifan;


import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements ApplicationRunner {

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

@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("Hello World from Application Runner");
}
}
运行结果:
1
2
2023-01-31 18:03:38.748  INFO 24269 --- [  restartedMain] com.kakawanyifan.Application             : Started Application in 2.139 seconds (JVM running for 2.584)
Hello World from Application Runner

命令行运行器,实现CommandLineRunner接口,重写其run方法:

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kakawanyifan;


import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

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

@Override
public void run(String... args) throws Exception {
System.out.println("Hello world from Command Line Runner");
}
}
运行结果:
1
2
2023-01-31 18:05:38.244  INFO 24300 --- [  restartedMain] com.kakawanyifan.Application             : Started Application in 2.064 seconds (JVM running for 2.521)
Hello world from Command Line Runner
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/10823
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区