spring boot AutoConfiguration.replacements 文件的作用
自动配置类可能会在 @AutoConfigureBefore、@AutoConfigureAfter 排序注解中被引用,也可能在 @SpringBootApplication(exclude =, excludeName = )、@EnableAutoConfiguration(exclude = , excludeName = ) 或 spring.autoconfigure.exclude 属性值中被引用用于排除某些自动配置。
如果我们想要重命名自动配置类,或者将其移动到其他的包中,就会导致自动配置排序和排除失效。spring boot 提供了 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements 文件用于解决这个问题,在文件中写入旧类和新类的映射关系之后,spring boot 会先将注解和属性中的旧类名替换为新类名,再处理自动配置类的排序、排除。
例如:
旧类=新类
properties
com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration
文件的读取由 AutoConfigurationReplacements 的 load 方法实现:
java
package org.springframework.boot.autoconfigure;
final class AutoConfigurationReplacements {
private static final String LOCATION = "META-INF/spring/%s.replacements";
private final Map<String, String> replacements;
private AutoConfigurationReplacements(Map<String, String> replacements) {
this.replacements = Map.copyOf(replacements);
}
Set<String> replaceAll(Set<String> classNames) {
Set<String> replaced = new LinkedHashSet<>(classNames.size());
for (String className : classNames) {
replaced.add(replace(className));
}
return replaced;
}
String replace(String className) {
return this.replacements.getOrDefault(className, className);
}
static AutoConfigurationReplacements load(Class<?> annotation, @Nullable ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// META-INF/spring/%s.replacements
// annotation 是 AutoConfiguration,全限定名是 org.springframework.boot.autoconfigure.AutoConfiguration
// 格式化之后就是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements
String location = String.format(LOCATION, annotation.getName());
// 获取多个 jar 中的文件路径
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
Map<String, String> replacements = new HashMap<>();
// 循环读取 AutoConfiguration.replacements 文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// readReplacements 使用 Properties.load 加载文件
replacements.putAll(readReplacements(url));
}
return new AutoConfigurationReplacements(replacements);
}
private static ClassLoader decideClassloader(@Nullable ClassLoader classLoader) {
if (classLoader == null) {
return ImportCandidates.class.getClassLoader();
}
return classLoader;
}
private static Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
try {
return classLoader.getResources(location);
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to load configurations from location [" + location + "]", ex);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Map<String, String> readReplacements(URL url) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
Properties properties = new Properties();
// 加载 .replacements 文件
properties.load(reader);
return (Map) properties;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load replacements from location [" + url + "]", ex);
}
}
}
参考
Creating Your Own Auto-configuration :: Deprecating and Replacing Auto-configuration Classes