深入理解SpringBoot之系统初始化器

深入理解SpringBoot之系统初始化器


1. 什么是系统初始化器

系统初始化器是一种回调机制,可以让我们扩展自定义的属性。

2. 如何自定义系统初始化器

SpringBoot提供了三种自定义系统初始化器的方案,接下来我们一一进行实现。

ApplicationContextInitializer<ConfigurableApplicationContext>

要实现自定义系统的初始化器必须实现ApplicationContextInitializer接口,并且可以使用@Order注解进行排序,下面我来创建三个初始化器。

@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String,Object> map = new HashMap<>();
        map.put("key1","value1");
        MapPropertySource propertySource = new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addFirst(propertySource);
        System.out.println("run firstInitializer");
    }
}
@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String,Object> map = new HashMap<>();
        map.put("key2","value2");
        MapPropertySource propertySource = new MapPropertySource("secondInitializer", map);
        environment.getPropertySources().addFirst(propertySource);
        System.out.println("run secondInitializer");
    }
}
@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String,Object> map = new HashMap<>();
        map.put("key3","value3");
        MapPropertySource propertySource = new MapPropertySource("thirdInitializer", map);
        environment.getPropertySources().addFirst(propertySource);
        System.out.println("run thirdInitializer");
    }
}

启动类实现:

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

第一种实现方式:在 classpath 下创建 META-INF 文件,并在下面创建 spring.factories 文件
在这里插入图片描述
配置文件内容如下

org.springframework.context.ApplicationContextInitializer=com.wys.initializer.FirstInitializer

这样我们就配置好了第一个系统初始化器,接下来运行,可以看到第一个系统初始化器已经生效。
在这里插入图片描述
配置第二个系统初始化器,修改启动类并运行

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

在这里插入图片描述
可以看到第二个系统初始化器也成功的运行了,接下来配置第三个初始化器,在 application.properties 中添加如下配置并运行

# 系统初始化器配置
context.initializer.classes=com.wys.initializer.ThirdInitializer

在这里插入图片描述
可以看到三个系统初始化器都已经成功加载了,但是可以看到 ThirdInitializer 这个初始化器的 order 是最小的,在这里是第一个进行初始化的,后面我们讲原理时会给出解释。

接下来我们可以调用系统初始化器中初始化的变量

@Service
public class DemoService implements ApplicationContextAware {

    private ApplicationContext applicationContext;

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

    public String test(){
        return this.applicationContext.getEnvironment().getProperty("key1");
    }
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class DemoTest {

    @Autowired
    private DemoService demoService;

    @Test
    public void test1(){
        System.out.println(this.demoService.test());
    }
}

运行上面测试程序可以看到输出结果为 value1 ,就是我们之前系统初始化器中设置的变量。

注:如果使用 junit 进行单元测试,使用 SpringApplication 的 addInitializer 方法添加的系统初始化器时不生效的。

3. SpringBoot系统初始化器实现原理

3.1 对类初始化器进行初始化并放入到 cache 中

在 SpringApplication 的构造器中会去初始化系统初始化器,通过 SpringFactoriesLoader.loadFactoryNames 来进行初始化

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

在这里可以看到使用 classLoader.getResource 来获取资源,FACTORIES_RESOURCE_LOCATION 值为 META-INF/spring.factories。
会将所有jar包下面的这个配置转换为 properties 并放入 cache 中,第二次获取就直接读 cache 中的数据。

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

3.2 执行系统初始化器

在 run 方法中该代码中 prepareContext 方法中会调用 applyInitializers 方法来执行类的初始化器,applyInitializer会便利系统初始化器并执行 initializer 方法。

prepareContext(context, environment, listeners, applicationArguments, printedBanner);
applyInitializers(context);
protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

3.3 为什么 ThirdInitializer 的 @Order注解会失效?

SpringBoot 会对初始化器进行排序,可以看到在我们自定义的初始化器前面存在两个初始化器,他们的 Order 都为 0,具备更高的优先级。
在这里插入图片描述
在 DelegatingApplicationContextInitializer 这个类中的 initializer 方法会寻找配置文件中 context.initializer.classes 配置的初始化器并执行,我把代码贴过来大家一看便知。

public class DelegatingApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	// NOTE: Similar to org.springframework.web.context.ContextLoader

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

	private int order = 0;

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			applyInitializerClasses(context, initializerClasses);
		}
	}

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

	private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
		Class<?> contextClass = context.getClass();
		List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
		for (Class<?> initializerClass : initializerClasses) {
			initializers.add(instantiateInitializer(contextClass, initializerClass));
		}
		applyInitializers(context, initializers);
	}

	private ApplicationContextInitializer<?> instantiateInitializer(Class<?> contextClass, Class<?> initializerClass) {
		Class<?> requireContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
				ApplicationContextInitializer.class);
		Assert.isAssignable(requireContextClass, contextClass,
				String.format(
						"Could not add context initializer [%s]" + " as its generic parameter [%s] is not assignable "
								+ "from the type of application context used by this " + "context loader [%s]: ",
						initializerClass.getName(), requireContextClass.getName(), contextClass.getName()));
		return (ApplicationContextInitializer<?>) BeanUtils.instantiateClass(initializerClass);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
		initializers.sort(new AnnotationAwareOrderComparator());
		for (ApplicationContextInitializer initializer : initializers) {
			initializer.initialize(context);
		}
	}

	public void setOrder(int order) {
		this.order = order;
	}

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


以上就是类初始化器的使用和原理,大家有什么问题欢迎在评论区进行讨论。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值