IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> ApplicationContextInitializer的理解和使用 -> 正文阅读

[Java知识库]ApplicationContextInitializer的理解和使用

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

创建一个 SpringBootConfigurationClassPostProcessor 共用的 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);
	}
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-06 10:55:43  更:2022-05-06 10:56:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:56:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码