Spring 中初始化器的实现和自定义

自定义初始化器初始化器

在 Springboot 中使用自定义初始化器大致可以分为以下两个步骤:

  • 自定义初始化器,一般是实现 ApplicationContextInitializer 接口。
  • 注册初始化器。

为何要自定义初始化器

Spring 是一个扩展性很强的容器框架,为开发者提供了丰富的扩展入口,其中一个扩展点便是 ApplicationContextInitializer (应用上下文初始化器 )。

ApplicationContextInitializer 是 Spring 在执行 ConfigurableApplicationContext.refresh() 方法对应用上下文进行刷新之前调用的一个回调接口,用来完成对 Spring 应用上下文个性化的初始化工作,该接口定义在 org.springframework.context 包中,其内部仅包含一个 initialize() 方法,其定义代码如下。

package org.springframework.context;
 
/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
 
	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);
 
}

Springboot定义的 ApplicationContextInitializer 接口的实现类有下面几个

  • DelegatingApplicationContextInitializer

    DelegatingApplicationContextInitializer 初始化器负责读取核心配置文件 context.initializer.classes 配置项指定的初始化器,并调用它们的 initialize() 方法来完成对应用上下文的初始化工作。  
    
  • ContextIdApplicationContextInitializer

    ContextIdApplicationContextInitializer 初始化器的作用是给应用上下文设置一个ID,这个ID由 name 和 index 两个部分组成。
    
    其中 name 会依次从读取核心配置的以下三个属性,如果存在则立即使用,若不存在则继续查找下一个,都不存在则取默认值 “application”。 
    
    spring.application.name
    
    vcap.application.name
    
    spring.config.name
    
    index 会依次从核心配置的以下几个属性获取,如果存在则立即使用,若不存在则继续查找下一个,都不存在则为空。
    
    vcap.application.instance_index
    
    spring.application.index
    
    server.port
    
    PORT
    
    所以,ID的格式应为 [application.name][:application.index] ,例如,application:8080 。 
    
    
  • ConfigurationWarningsApplicationContextInitializer

ConfigurationWarningsApplicationContextInitializer 初始化器用来对常见的由于配置错误而引起的警告进行打印报告
  • ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer 初始化器通过监听 EmbeddedServletContainerInitializedEvent 事件,来对内部服务器实际要监听的端口号进行属性设置。 
  • SharedMetadataReaderFactoryContextInitializer

SharedMetadataReaderFactoryContextInitializer 初始化器用来创建一个可以在 ConfigurationClassPostProcessor 和Spring Boot 之间共享的CachingMetadataReaderFactory。 
  • AutoConfigurationReportLoggingInitializer

AutoConfigurationReportLoggingInitializer 初始化器用来将 ConditionEvaluationReport 记录的条件评估详情输出到日志,默认使用 DEBUG 级别,当有异常问题发生时会使用 INFO 级别。 

各个初始化器的执行顺序如下:
DelegatingApplicationContextInitializer -->
ContextIdApplicationContextInitializer --> ConfigurationWarningsApplicationContextInitializer -->ServerPortInfoApplicationContextInitializer–>SharedMetadataReaderFactoryContextInitializer–> AutoConfigurationReportLoggingInitializer

实战

第一种创建方式Initializer的方式

public class MyFirstApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
        Map<String,Object> mps = new HashMap<>();
        mps.put("key1","zhangsan");

        MapPropertySource mapPropertySource = new MapPropertySource("first",mps);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run firstInitializer");

    }

2.创建META-INF和spring.factories

org.springframework.context.ApplicationContextInitializer=com.zhang.initailizer.MyFirstApplicationContextInitializer

3.创建一个service文件夹

当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。

public class Testservice  implements ApplicationContextAware{

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         this.applicationContext = applicationContext;
    }

    public String test(){
        return  applicationContext.getEnvironment().getProperty("key1");
    }
}

4.创建一个controller文件夹

@RestController
public class TestController {
    @Autowired
    private Testservice testservice;

    @RequestMapping("/test")
    public String test(){
    return  testservice.test();
    }
}

http://localhost:8888/test

第一种启动方式

通过在CLASSPATH/META-INF/spring.factories中添加 org.springframework.context.ApplicationContextInitializer 配置项进行注册。

  • 实现ApplicationContextInitializer接口
  • spring.factories 添加接口实现
  • key值为org.springframework.context.ApplicationContextInitializer

第二种启动方式

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		//SpringApplication.run(Application.class, args);
		SpringApplication springApplication = new SpringApplication(Application.class);
		springApplication.addInitializers(new MySecondApplicationContextInitializer());
		ConfigurableApplicationContext context = springApplication.run(args);
	}
}
  • 实现ApplicationContextInitializer接口
  • springApplication类初始化设置进去

第三种启动方式

在 Springboot 核心配置文件 application.properties 中增加 context.initializer.classes = [ 初始化器全类名 ] 进行注册。

  • 实现ApplicationContextInitializer接口
  • 在application.properties填写接口实现
  • key值为context.initializer.classes

三种方式实现ApplicationContextInitializer接口 ,order值越小越先执行,不过 application.properties定义优先于其他方法。

Spring Boot 关于初始化器代码逻辑梳理:

	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.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
		// 设置将应用到Spring ApplicationContext的ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 设置将应用到SpringApplication并注册到ApplicationContext的applicationlistener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
进入getSpringFactoriesInstances(ApplicationContextInitializer.class)
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		// 获取类加载器
		ClassLoader classLoader = getClassLoader();
		// 使用Set集合确保唯一,以防止重复
		// SpringFactoriesLoader.loadFactoryNames(type, classLoader) 从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名
		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)
	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(classLoaderToUse)
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 {
			// FACTORIES_RESOURCE_LOCATION=META-INF/spring.factories
			// classLoader.getResources会将所有jar保重classpath下的META-INF/spring.factories中的路径都获取到
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				// 根据URL获取到对应资源的位置
				UrlResource resource = new UrlResource(url);
				// 加载并解析对应的spring.factories文件
				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());
					}
				}
			}

			// 将所有列表替换为包含唯一元素的不可修改列表
			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;
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值