个人学习SpringBoot 之基于spring的工厂加载机制实现自定义ApplicationContextInitializer和ApplicationListener
在前几篇笔记中我学些了springapplication在准备阶段要做的两件事,今天来学习要做的另外两件事
其一是通过spring的工厂加载机制初始化ApplicationContextInitializer,另外一个是通过spring的工厂加载机制初始化ApplicationListener,这个讲个事两个接口,他们有很多实现类,下面我们就来看一下他们是怎么去做的。
在SpringApplication这个类初始化的时候会初始化很多东西在其构造函数中有这么几段代码我们一起来看下
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断application类型
this.webApplicationType = deduceWebApplicationType();
//初始化一些ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//初始化一些ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断第一个main方法所在的主类
this.mainApplicationClass = deduceMainApplicationClass();
}
我们来看下他是怎么去初始化ApplicationContextInitializer的,首先是去执行getSpringFactoriesInstances(ApplicationListener.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 classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//通过spring的工厂加载机器去各位jar包下的MTEA-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;
}
从上面的代码我们可以大概整个流程,其中最核心的是loadfactoryNames方法
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()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
我们通过其实现发现其实是通过classloader去拿这些配置在spring.factories里的类名的
,看完了这个我们现在也来些一个自己的demo
我们首先创建一个自己的HelloWorldApplicationContextInitializer继承自ApplicationContextInitializer并重新其方法,在这个方法里我们可以做很多事,我这只是做一个简单的demo
package com.demo.springboot.context;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.stream.Stream;
@Order(Ordered.HIGHEST_PRECEDENCE)//顺序,HIGHEST_PRECEDENCE最高,首先加载
public class HelloWorldApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String[] defaultProfiles = environment.getDefaultProfiles();
Stream.of(defaultProfiles).forEach(item->{
System.out.println("item====="+item);
});
}
}
然后在resource下面添加META-INF然后再在下面添加spring.factories文件
并在文件里配上自己的ApplicationContextInitializer,然后编写主类做测试
package com.demo.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.HashSet;
public class DemoSpringbootApplcaition {
public static void main(String[] args) {
SpringApplication application =new SpringApplication();
HashSet<String> sources = new HashSet<>();
sources.add(SpringBootcnfig.class.getName());
application.setSources(sources);
application.setWebApplicationType(WebApplicationType.NONE);
ConfigurableApplicationContext context = application.run(args);
}
@SpringBootApplication
public static class SpringBootcnfig{
}
}
然后我们启动测试,我们debug可以清楚的看到程序执行过来了
根据测试事ok的,另外自定义ApplicationListener的实现大同小异,有兴趣的小伙伴可以镐一哈。不对的地方望大佬传教,
参考书籍: springboot编程思想,spring源码深度解析第2版