本文轉載自spring boot插件開發實戰和原理
實戰:編寫spring boot插件
為什么要編寫boot插件
因為我們在開發的時候需要提供一些共同的功能,所以我們編寫個共同的jar包。開發人員在使用jar包的時候不用考慮jar包的內容,直接使用具體的功能即可,但是可能由於包路徑的不同,你所編寫的bean沒有被初始化到spring容器中。不應該讓開發人員去掃描你的包路徑去初始化bean。所以我們要自己動手去把bean初始化到bean容器中,這也是spring擴展能力的由來(spriing.factories)
實戰
編寫插件代碼,編寫配置類(例如:DemoAutoConfig),在其中定義你需要的bean 在resources下創建META-INF/spring.factories 編寫spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.demo.DemoAutoConfig
spring.factories 常用配置接口
org.springframework.boot.SpringApplicationRunListener
SpringApplicationRunListener來監聽Spring Boot的啟動流程,並且在各個流程中處理自己的邏輯。在應用啟動時,在Spring容器初始化的各個階段回調對應的方法。
org.springframework.context.ApplicationContextInitializer
ApplicationContextInitializer是在springboot啟動過程上下文 ConfigurableApplicationContext刷新方法前(refresh)調用,對ConfigurableApplicationContext的實例做進一步的設置或者處理。
org.springframework.boot.autoconfigure.EnableAutoConfiguration
定義系統自動裝配的類。
org.springframework.boot.env.EnvironmentPostProcessor
配置環境的集中管理。比如擴展去做排除加載系統默認的哪些配置類,方便自定義擴展。
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
自動裝配類排除
spring factories 原理
獲取配置流程
在啟動類注解@SpringBootApplication中可以看到引用了@EnableAutoConfiguration。 其中@Import(AutoConfigurationImportSelector.class)
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
其中getAutoConfigurationEntry方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
其中getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
調用了SpringFactoriesLoader.loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
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 factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
加載配置流程
在main方法啟動的時候我們會調用SpringApplication.run方法 run方法中調用了getSpringFactoriesInstances 調用createSpringFactoriesInstances
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
總結
這是一種類似插件的設計方式,只要引入對應的jar包,就會掃描到jar里的spring.factories,對應的實現類也就會被實例化。