ApplicationContextInitializer的理解和使用


一、ApplicationContextInitializer 介紹

1.1 作用

ApplicationContextInitializer 接口用於在 Spring 容器刷新之前執行的一個回調函數,通常用於向 SpringBoot 容器中注入屬性。

1.2 內置實現類

DelegatingApplicationContextInitializer

使用環境屬性 context.initializer.classes 指定的初始化器(initializers)進行初始化工作,如果沒有指定則什么都不做。

通過它使得我們可以把自定義實現類配置在 application.properties 里成為了可能。

ContextIdApplicationContextInitializer

設置Spring應用上下文的ID,會參照環境屬性。至於Id設置為什么值,將會參考環境屬性:
* spring.application.name
* vcap.application.name
* spring.config.name
* spring.application.index
* vcap.application.instance_index
    
如果這些屬性都沒有,ID 使用 application。

ConfigurationWarningsApplicationContextInitializer

對於一般配置錯誤在日志中作出警告

ServerPortInfoApplicationContextInitializer

 將內置 servlet容器實際使用的監聽端口寫入到 Environment 環境屬性中。這樣屬性 local.server.port 就可以直接通過 @Value 注入到測試中,或者通過環境屬性 Environment 獲取。

SharedMetadataReaderFactoryContextInitializer

創建一個 SpringBoot和ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory對象。實現類為:ConcurrentReferenceCachingMetadataReaderFactory

ConditionEvaluationReportLoggingListener

將 ConditionEvaluationReport寫入日志。

二、實現方式

首先新建三個自定義類,實現 ApplicationContextInitializer 接口

public class FirstInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key1", "First");

        MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run firstInitializer");
    }

}

public class SecondInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key1", "Second");

        MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run secondInitializer");
    }

}

public class ThirdInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key1", "Third");

        MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run thirdInitializer");
    }

}

2.1 在 resources/META-INF/spring.factories 中配置

org.springframework.context.ApplicationContextInitializer=com.learn.springboot.initializer.FirstInitializer

2.2 在 mian 函數中添加

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
//        SpringApplication.run(SpringbootApplication.class, args);
        SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run();
    }

}

2.3 在配置文件中配置

context.initializer.classes=com.learn.springboot.initializer.ThirdInitializer

運行項目,查看控制台:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

run thirdInitializer
run firstInitializer
run secondInitializer

可以看到配置生效了,並且三種配置優先級不一樣,配置文件優先級最高,spring.factories 其次,代碼最后。

三、獲取屬性值

@RestController
public class HelloController {
    
    private ApplicationContext applicationContext;
    
    public HelloController(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @RequestMapping("/getAttributes")
    public String getAttributes() {
        String value = applicationContext.getEnvironment().getProperty("key1");
        System.out.println(value);
        return value;
    }
    
}

啟動項目,訪問http://localhost:8080/getAttributes 查看控制台輸出:

Third

發現同名的 key,只會存在一個,並且只存第一次設置的值。

四、通過 @Order 注解修改執行順序

注:@order 值越小,執行優先級越高

4.1 不同配置方式下,執行順序

@Order(1)
public class SecondInitializer implements ApplicationContextInitializer {
    ......
}

@Order(2)
public class FirstInitializer implements ApplicationContextInitializer {
    ......
}

@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer {
    ......
}

運行項目,查看控制台:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

run thirdInitializer
run secondInitializer
run firstInitializer

可以看到通過 @Order ** 注解是可以改變spring.factories** 和代碼形式的執行順序的,但是application.properties 配置文件的優先級還是最高的。

4.2 同一配置下,執行順序

新建實現類

@Order(1)
public class FourthInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key1", "Fourth");

        MapPropertySource mapPropertySource = new MapPropertySource("FourthInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run fourthInitializer");
    }

}

application.properties 文件中配置

context.initializer.classes=com.learn.springboot.initializer.ThirdInitializer,com.learn.springboot.initializer.FourthInitializer

運行項目,查看控制台:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

run fourthInitializer
run thirdInitializer
run secondInitializer
run firstInitializer

可以看到同一配置方式, @Order 注解也可以起作用。

五、系統初始化器原理解析

5.1在 resources/META-INF/spring.factories 中配置實現原理

SpringApplication 初始化時通過 SpringFactoriesLoader 獲取到配置在 META-INF/spring.factories 文件中的 ApplicationContextInitializer 的所有實現類.

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	......
    // 設置系統初始化器
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	......
}
// 獲取工廠實例對象
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();
	// 使用名稱並確保唯一以防止重復
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 創建工廠實例對象
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 對工廠實例對象列表進行排序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

// 創建工廠實例對象
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;
}

在 run 方法中回調 ApplicationContextInitializer 接口函數

public ConfigurableApplicationContext run(String... args) {
	......
	// 准備上下文環境注入系統初始化信息 
	prepareContext(context, environment, listeners, applicationArguments, printedBanner);
	......
}
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	......
    // 應用初始化器   
	applyInitializers(context);
    ......
}
protected void applyInitializers(ConfigurableApplicationContext context) {
	for (ApplicationContextInitializer initializer : getInitializers()) {
        // 判斷子類是否是 ConfigurableApplicationContext 類型
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
				ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        // 回調 ApplicationContextInitializer接口的 initialize 方法
		initializer.initialize(context);
	}
}

獲取初始化器列表

// 獲取在 SpringApplication 構造函數中設置的初始化器列表
public Set<ApplicationContextInitializer<?>> getInitializers() {
	return asUnmodifiableOrderedSet(this.initializers);
}
// 對初始化器列表進行排序
private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
	List<E> list = new ArrayList<>(elements);
	list.sort(AnnotationAwareOrderComparator.INSTANCE);
	return new LinkedHashSet<>(list);
}

5.2 在 main 函數配置實現原理

在之前我們知道 SpringApplication 初始化之后,就已經把 META-INF/spring.factories 中配置的初始化實現類添加到 initializers 列表中了,然后通過 addInitializers 方法,添加自定義的實現類:

public static void main(String[] args) {
    // SpringApplication.run(SpringbootApplication.class, args);
    SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
    springApplication.addInitializers(new ThirdInitializer());
    springApplication.run();
}
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
	this.initializers.addAll(Arrays.asList(initializers));
}

5.3 在配置文件中配置實現原理

在配置文件中配置方式,主要通過內置的 DelegatingApplicationContextInitializer 實現的,它實現了 Order 方法,所以優先級最高。:

private int order = 0;	

@Override
public int getOrder() {
	return this.order;
}

然后我們看下它的 initialize方法實現:

@Override
public void initialize(ConfigurableApplicationContext context) {
    // 獲取上下文環境變量
	ConfigurableEnvironment environment = context.getEnvironment();
    // 從上下文環境變量中獲取指定初始化類列表
	List<Class<?>> initializerClasses = getInitializerClasses(environment);
	if (!initializerClasses.isEmpty()) {
        // 應用初始化器
		applyInitializerClasses(context, initializerClasses);
	}
}

從上下文環境變量獲取指定的屬性名,並實例化對象

private static final String PROPERTY_NAME = "context.initializer.classes";

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
    // 從上下文環境變量獲取指定的屬性名
	String classNames = env.getProperty(PROPERTY_NAME);
	List<Class<?>> classes = new ArrayList<>();
	if (StringUtils.hasLength(classNames)) {
        // 將逗號分割的屬性值逐個取出
		for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
			// 實例化對象並添加到列表中
            classes.add(getInitializerClass(className));
		}
	}
	return classes;
}
private Class<?> getInitializerClass(String className) throws LinkageError {
	try {
		Class<?> initializerClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
		Assert.isAssignable(ApplicationContextInitializer.class, initializerClass);
		return initializerClass;
	}
	catch (ClassNotFoundException ex) {
		throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM