SpringApplication 初始化

# SpringApplication初始化

基于springboot3.2和springframework6.1.4

## 初始化代码解释

```java

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();

}```

这是来自Spring Boot框架的`SpringApplication`类的代码片段。该构造函数用于创建`SpringApplication`

的新实例,这是一个关键的类,负责引导和启动Spring应用程序。

让我们分解一下这个构造函数的关键方面:

1. **资源加载器(Resource Loader)**:`resourceLoader`参数表示要由应用程序上下文使用的资源加载器。它有助于从不同来源加载资源。

2. **主要来源(Primary Sources)**:`primarySources`参数表示主要的bean来源。这些类被视为配置Spring应用程序上下文的主要来源。

3. **Web应用程序类型(Web Application Type)**:`webApplicationType`是从类路径中推断出来的。它表示Web应用程序的类型(例如,Servlet、Reactive、None)。

4. **引导注册表初始化器(Bootstrap Registry Initializers)**:`bootstrapRegistryInitializers`

  是`BootstrapRegistryInitializer`实例的列表。它们负责在应用程序引导阶段自定义`BootstrapRegistry`。

5. **初始化器和监听器(Initializers and Listeners)**:构造函数通过使用`getSpringFactoriesInstances`

  从Spring工厂获取实例来设置初始化器和监听器。

```properties

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

org.springframework.context.ApplicationListener=\

org.springframework.boot.ClearCachesApplicationListener,\

org.springframework.boot.builder.ParentContextCloserApplicationListener,\

org.springframework.boot.context.FileEncodingApplicationListener,\

org.springframework.boot.context.config.AnsiOutputApplicationListener,\

org.springframework.boot.context.config.DelegatingApplicationListener,\

org.springframework.boot.context.logging.LoggingApplicationListener,\

    org.springframework.boot.env.EnvironmentPostProcessorApplicationListener```

6. **主应用程序类(Main Application Class)**:`mainApplicationClass`是通过推断得出的,表示应用程序的主类。

这个构造函数设置了`SpringApplication`实例的初始配置。在创建实例后,开发人员可以在调用`run`方法启动Spring应用程序之前进一步定制它。

## WebApplicationType

```java

private static final String[] SERVLET_INDICATOR_CLASSES = {"jakarta.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;

}```

## getSpringFactoriesInstances

loadFactoriesResource读取public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"

资源文件内容,以Map>返回。

static final Map> cache = new ConcurrentReferenceHashMap<>();

classLoader -> resourceLocation -> SpringFactoriesLoader。

然后通过SpringFactoriesLoader里面的Map> factories获取接口的实现类。接口的className —>

接口实现类的className。

```java

private List getSpringFactoriesInstances(Class type, ArgumentResolver argumentResolver) {

return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver);

}

public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) {

return forResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);

}

public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {

Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");

ClassLoader resourceClassLoader = (classLoader != null ? classLoader : SpringFactoriesLoader.class.getClassLoader());

Map loaders = cache.computeIfAbsent(resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());

return loaders.computeIfAbsent(resourceLocation, key -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));

}```

`loaders.computeIfAbsent(resourceLocation, key -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)))`

这段代码使用了Java 8的`computeIfAbsent`方法,它是`ConcurrentMap`接口的一个方法,用于根据给定的键计算值并将其插入到Map中(如果缺少该键的映射)。

具体来说,这段代码的作用是在`loaders`这个`ConcurrentMap`中,根据`resourceLocation`

这个键,计算并插入一个值。这个值是通过使用lambda表达式 `key -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation))`

计算得到的。

解释一下lambda表达式的部分:

- `key` 是 `resourceLocation`,即在`loaders`中要查找或插入的键。

- `new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation))`

  创建了一个`SpringFactoriesLoader`对象。这个对象的构造函数接受两个参数:`classLoader`

  和`loadFactoriesResource(resourceClassLoader, resourceLocation)`。其中,`loadFactoriesResource`方法用于加载资源,返回一个表示资源的对象。

总体来说,这段代码的目的是利用`computeIfAbsent`方法,根据`resourceLocation`计算出一个`SpringFactoriesLoader`

对象,并将其插入到`loaders`中,如果`loaders`中已存在该键对应的值,则直接返回已有的值而不重新计算。

从`SpringFactoriesLoader`中 `Map> factories`获取接口的实现类名,并实例化以后返回。

```java

public List load(Class factoryType, @Nullable ArgumentResolver argumentResolver, @Nullable FailureHandler failureHandler) {

Assert.notNull(factoryType, "'factoryType' must not be null");

List implementationNames = loadFactoryNames(factoryType);

logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));

List result = new ArrayList<>(implementationNames.size());

FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;

for (String implementationName : implementationNames) {

T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);

if (factory != null) {

result.add(factory);

}

}

AnnotationAwareOrderComparator.sort(result);

return result;

}```

## 补充知识

### Lambda表达式

从Java 8的角度解释Lambda表达式,Lambda表达式是一种轻量级的语法,用于在需要函数接口(Functional

Interface)的地方提供一个简洁的方式来定义匿名函数。

以下是Lambda表达式的一般语法:

```java

(parameters)->expression```

或者

```java

(parameters)->{statements; }```

其中:

- `parameters` 指的是Lambda表达式的参数列表,类似于方法的参数列表。

- `->` 是Lambda运算符,分隔参数列表和Lambda表达式的主体。

- `expression` 或 `{ statements; }` 是Lambda表达式的主体,可以是单个表达式或一个代码块。

举例来说,考虑一个简单的Lambda表达式,用于对两个数进行相加:

```java

(int a, int b)->a +b```

在这里:

- `(int a, int b)` 是参数列表,表明Lambda表达式接受两个整数参数。

- `->` 是Lambda运算符。

- `a + b` 是表达式,表示对两个整数进行相加的操作。

Lambda表达式的主要用途是在函数接口的上下文中提供一种简洁的方式来创建匿名函数。函数接口是只包含一个抽象方法的接口。例如,`Runnable`、`Callable`

等都是函数接口。在Lambda表达式中,可以直接传递行为(函数)而不是实例化一个匿名类。

下面是一个例子,使用Lambda表达式创建一个线程:

```java

// 使用匿名内部类

Runnable runnable1 = new Runnable() {

@Override

public void run() {

System.out.println("Hello from anonymous class!");

}

};

// 使用Lambda表达式

Runnable runnable2 = () -> {

System.out.println("Hello from Lambda!");

};```

在这个例子中,`runnable2`使用Lambda表达式实现了`Runnable`接口,取代了传统的匿名内部类的写法。Lambda表达式的引入使得代码更为简洁和易读。

### computeIfAbsent

`computeIfAbsent` 是Java 8中 `Map` 接口引入的一个方法,用于在映射中根据指定的键计算值并插入到映射中,如果该键尚未与值关联或与null关联。

```java

V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)```

- `key` 是要计算值的键。

- `mappingFunction` 是一个用于计算值的函数接口,接收键作为参数,返回与键相关联的值。

该方法的行为如下:

- 如果指定的键在映射中不存在(或与null关联),则计算值并将其与键关联。

- 如果计算的值不为null,则将其与键关联。

- 如果计算的值为null,则不会将其与键关联,并且不会更改映射。

以下是一个简单的示例,演示了如何使用 `computeIfAbsent` 方法:

```java

import java.util.HashMap;

import java.util.Map;

public class ComputeIfAbsentExample {

public static void main(String[] args) {

Map map = new HashMap<>();

        // 使用 computeIfAbsent 插入键值对

map.computeIfAbsent("one", key -> 1);

map.computeIfAbsent("two", key -> 2);

System.out.println(map); // Output: {one=1, two=2}

        // 如果键已存在,computeIfAbsent 不会改变映射

map.computeIfAbsent("one", key -> 10);

System.out.println(map); // Output: {one=1, two=2}

}

}```

在上述示例中,`computeIfAbsent` 方法被用于向映射中插入键值对。如果键已经存在,该方法不会改变映射。这是一种方便的方式,以便在映射中插入新的键值对,而无需手动检查键是否存在。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容