1.介绍下SpringFactoriesLoader
- 框架内部使用的通用加载机制
- 加载并实例化类路径下(包含jar包)
META-INF/spring.factories
指定类型的类型; - 可以自定义实例化方式,只使用加载功能,返回
List<String>
,值为类的全限定类名; -
META-INF/spring.factories
文件的格式为Properties
格式,即K/V
格式。其中key
一般为接口或抽象类的全限定类名,value
为实现类,多个实现逗号分隔;
2.SpringFactoriesLoader如何加载工厂类?
对外暴露了两个 public
方法,具体如下:
loadFactories
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
//实际的FactoryClass为 META-INF/spring.factories文件中的某个key
Assert.notNull(factoryClass, "‘factoryClass‘ must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//根据名字和参数就能知道,获取名字;这个方法比较核心,跟进去看一下;
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
//for 循环反射实例化对象;
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
//如果根据@Order注解中的值进行排序
AnnotationAwareOrderComparator.sort(result);
return result;
}
loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//loadSpringFactories的返回是一个map,然后调用getOrDefault方法,具体api可以参加guava.
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//老套路,尝试从缓存取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//通过classLoader加载对应的META-INF/spring.factories文件。
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
//迭代,放在map中。这里存在一k多v的情况,所以用的是LinkedMultiValueMap
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//借助工具类转换为properties文件,其实就是一个k/v都为string的map
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类型是LinkedMultiValueMap,允许一K多V。
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);
}
}
根据上面的调用流程可以知道,可以选择加载&实例化,对应<T> List<T> loadFactories
方法;也可以选择只加载,拿到全限定类名,自己想干什么都可以,对应 List<String> loadFactoryNames
。
3.系统初始化器的作用?
- 回调接口,用于初始化
Spring Context
,调用时机在reresh
方法之前; - 通过实现该接口,可以在手动给应用上下文注入一些内容,如给
Environment
注入自定义属性; - 可以根据实现类上的
@Order
注解进行排序;
4.系统初始化器调用时机?
org.springframework.boot.SpringApplication#run(java.lang.String...)#prepareContext
方法处调用:
5.如何自定义实现系统初始化器?
方式一
- 实现ApplicationContextInitializer接口
- resources/META-INF/spring.factories内填写接口实现
- key值为org.springframework.context.ApplicationContextlnitializer
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.setRequiredProperties("mooc");
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run firstInitializer");
}
}
#配置
org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.FirstInitializer
方式二
- 实现ApplicationContextInitializer接口
- SpringApplication类初始后设置进去
SpringApplication application = new SpringApplication(Sb2Application.class);
SecondInitializer initializer = new SecondInitializer();
application.addInitializers(initializer);
application.run(args);
方式三
- 实现ApplicationContextInitializer接口
- application.properties内填写接口实现
- key值为context.initializer.classes
#application.properties
context.listener.classes=com.mooc.sb2.listener.ThirdListener
6.自定义实现系统初始化器有哪些注意事项?
- 都要实现
ApplicationContextInitializer
接口 Order
值越小越先执行application.properties
中定义的优先于其它方式