- 介绍
- 参考
介绍
SPI扩展
JDK的SPI机制
spring的SPI机制
跟JDK原生的SPI机制类似,但是使用的是SpringFactoriesLoader来解析META-INF/spring.factories
-
spring.factories
-
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
//...
}
查看@EnableAutoConfiguration
源码可以发现,它的核心在于AutoConfigurationImportSelector
,关于@Import在spring-ioc容器中的加载时机,参考这里。
它实现的是DeferredImportSelector
,并且order=LOWEST_PRECEDENCE,也就是说它的加载优先级在所有配置类之后,详细分析一下它的核心方法selectImports
AutoConfigurationImportSelector.selectImports
AutoConfigurationImportSelector.getCandidateConfigurations
//********************************************************
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//【这里使用了SpringFactoriesLoader来加载META-INF/spring.factories】
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;
}
//********************************************************
自动配置的类在META-INF/spring.factories里面的key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
,可以参考spring-boot-autoconfigure包下面的META-INF/spring.factories文件配置。
当然这个是自动配置类的配置key,如果需要加载其它的自定义扩展类,key=接口名称,value=实现。
自定义自动配置类
自定义的类上添加一些注解就可以,加载的顺序、条件同样可以参考spring-boot-autoconfigure包下面的META-INF/spring.factories文件配置里面内置的配置类
@Configuration
@AutoConfigureAfter(JmxAutoConfiguration.class)
@ConditionalOnProperty(prefix = "spring.application.admin", value = "enabled", havingValue = "true", matchIfMissing = false)
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
spring xml扩展
-
spring.handlers
-
spring.schemas定义了xml中namespace对应的xsd文件的本地位置
-
BeanDefinitionParser
-
NamespaceHandler/NamespaceHandlerSupport
-
原理
从spring容器的入口方法AbstractApplicationContext.refresh开始
AbstractApplicationContext#obtainFreshBeanFactory#refreshBeanFactory
AbstractRefreshableApplicationContext#refreshBeanFactory
AbstractXmlApplicationContext#loadBeanDefinitions
AbstractBeanDefinitionReader#loadBeanDefinitions
XmlBeanDefinitionReader#loadBeanDefinitions#doLoadBeanDefinitions
//【核心方法】加载XML文档
XmlBeanDefinitionReader#doLoadDocument
//【核心方法】从XML文档中解析BeanDefinition
XmlBeanDefinitionReader#registerBeanDefinitions
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions#doRegisterBeanDefinitions
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
//*************************************************************
//这个方法比较关键,判断XML里面的namespace是不是默认的
//spring的XML配置里面所谓默认的namespace就是指 xmlns="http://www.springframework.org/schema/beans"
//自定义的namespace就是很多框架的扩展点,例如dubbo的spring配置就自定义了 “xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"”
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
//自定义扩展namespace在这里被解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
//自定义扩展namespace在这里被解析
delegate.parseCustomElement(root);
}
}
//*************************************************************
//对默认的namespace节点的处理逻辑,比如最常用的<beans xxx><bean xxx></bean></beans>节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
//*************************************************************
//【我们把关注点聚焦在自定义namespace】
BeanDefinitionParserDelegate#parseCustomElement
//获取到所有的HandlerMappings,就是扫描所有依赖jar包META-INF/spring.handlers中定义的类,key=namespaceUrl,value=NamespaceHandler实现类
//获取META-INF/spring.handlers中内容的方法就是PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
DefaultNamespaceHandlerResolver#resolve
DefaultNamespaceHandlerResolver#getHandlerMappings
//获取到META-INF/spring.handlers中定义的NamespaceHandler的map之后,根据节点的namespace字符串获取指定的NamespaceHandler来处理当前节点
//在NamespaceHandler中使用BeanDefinitionParser来解析XML节点
总结一下如何扩展spring的xml配置文件,添加自定义的xml标签及解析器,例如实现类似dubbo配置文件中的<dubbo:xxx />
的解析
- 1、定义 xsd 文件
- 2、编写自定义NamespaceHandler,一般通过写一个类继承自NamespaceHandlerSupport
- 3、编写自定义BeanDefinitionParser,在自定义的NamespaceHandlerSupport子类中引用自定义的BeanDefinitionParser来解析XML节点
- 4、配置spring.handlers和spring.schmas,让spring能扫描到我们自定义的NamespaceHandler,在spring容器加载bean的过程中如果遇到自定义的XML节点就使用自定义的NamespaceHandler来处理这个节点
springboot扩展-ApplicationContextInitializer
在ConfigurableApplicationContext初始化容器之前对容器进行操作
//值越小越早执行
@Order(100)
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("bean的数量:" + applicationContext.getBeanDefinitionCount());
}
}
三种使用方法
- SpringApplication.addInitializers()
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(App.class);
springApplication.addInitializers(new MyApplicationContextInitializer());
springApplication.run(args);
}
}
- application.properties
context.initializer.classes=com.qigang.sb.ext.aci.MyApplicationContextInitializer
这个配置能生效主要靠DelegatingApplicationContextInitializer
这个springboot内置的初始化器
- META-INF\spring.factories
org.springframework.context.ApplicationContextInitializer=com.qigang.sb.ext.aci.MyApplicationContextInitializer
这个是依赖spring的SPI机制
spring内置的初始化器
- DelegatingApplicationContextInitializer
加载application.properties或者环境变量中context.initializer.classes
指定的自定义初始化器
- ContextIdApplicationContextInitializer
- ConfigurationWarningsApplicationContextInitializer
-
ServerPortInfoApplicationContextInitializer
既是一个ApplicationContextInitializer也是一个ApplicationListener,监听WebServerInitializedEvent事件,web服务器初始化后将端口写入到环境变量
local.server.port
中,然后程序中就可以通过@Value("${local.server.port}")
或者environment.getProperty("local.server.port")
来获取端口信息。 - SharedMetadataReaderFactoryContextInitializer
- ConditionEvaluationReportLoggingListener
springboot扩展-SpringApplicationRunListener
springboot扩展-PropertySource
自定义PropertySourceLoader读取本地自定义配置文件
原理
在spring-boot包的META-INF/spring.factories文件中配置了一系列org.springframework.context.ApplicationListener
,其中有一个是ConfigFileApplicationListener
,这个类里面包含了对PropertySourceLoader
的操作,它在springboot中的默认实现也是在META-INF/spring.factories中的。
## ConfigFileApplicationListener配置
org.springframework.context.ApplicationListener=\
...
org.springframework.boot.context.config.ConfigFileApplicationListener,\
...
## PropertySourceLoader配置
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
//SpringApplication构造函数中加载了/META-INF/spring.factories里面的初始化器和监听器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//加载初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//【加载监听器,这里加载了ConfigFileApplicationListener】
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在SpringApplication.run方法中环境准备好时首先触发了
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//【这里通过EventPublishingRunListener触发了ApplicationEnvironmentPreparedEvent事件,而ConfigFileApplicationListener正好监听了这个事件】
//因此ConfigFileApplicationListener.onApplicationEvent会被首先调用
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//...
}
//ConfigFileApplicationListener源码
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
}
@Override
public boolean supportsSourceType(Class<?> aClass) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
}
在onApplicationEvent->onApplicationEnvironmentPreparedEvent方法中从/META-INF/spring.factories中加载了EnvironmentPostProcessor列表,并把自身也加入到列表中(ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口),然后调用postProcessEnvironment方法,这个时候ConfigFileApplicationListener.postProcessEnvironment方法就被调用了。
这里有一个扩展点,可以通过自定义的EnvironmentPostProcessor来动态加载我们自己的配置源,这样加载进来的配置顺序早于springboot的默认配置
/** * 使用EnvironmentPostProcessor加载外部配置资源 * 1.实现EnvironmentPostProcessor接口,重写postProcessEnvironment方法 * 2.在项目资源目录下创建/META-INF/spring.factories文件,添加内容如下: * org.springframework.boot.env.EnvironmentPostProcessor=com.xxx.xxx.MyEnvironmentPostProcessor */ @Component public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor{ @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { try(InputStream input = new FileInputStream("E:\\ds.properties")) { Properties properties = new Properties(); properties.load(input); PropertiesPropertySource propertySource = new PropertiesPropertySource("ve", properties); environment.getPropertySources().addLast(propertySource); System.out.println("====加载外部配置文件完毕===="); } catch (Exception e) { e.printStackTrace(); } } }
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
在ConfigFileApplicationListener.postProcessEnvironment方法中调用了以下关键的load方法,load方法中初始化了profile,并根据profile加载配置文件
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
//Loader子类构造函数里面加载了所有的PropertySourceLoader
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
}
如果系统环境或者配置文件中没有指定spring.profiles.active
属性,那会有两个profile(null 以及 default)被ConfigFileApplicationListener加载进来。如果没有自定义配置文件名称,那默认的文件名称在ConfigFileApplicationListener
里面定义的是application
,遍历PropertySourceLoader来加载配置文件的时候会把这个名称传递给PropertySourceLoader进行配置文件加载。
关键的加载配置的逻辑都在ConfigFileApplicationListener.Loader.load()
这个子类方法中,它会搜索指定路径下面的name-profile.ext配置文件,根据ext判断使用哪个PropertySourceLoader来处理这个配置类并加载到内存。具体方法可以跟踪进入ConfigFileApplicationListener.Loader.loadForFileExtension()
springboot中相关的配置项:
- spring.profiles.active
激活的profile,默认为”null+default”,也就是会加载类似”application.properties,application-default.properties”的配置文件
- spring.config.name
替代配置文件名称,默认为”application”
- spring.config.location
替代配置文件位置,默认的搜索位置为”classpath:/,classpath:/config/,file:./,file:./config/”
自定义PropertySourceLoader示例
通过自定义的PropertySourceLoader可以读取自定义的文件格式中的配置内容,例如json配置,代码配置参考这里。
自定义配置源读取远程配置
有几种方式来自定义远程配置源,参考这里。
自定义EnvironmentPostProcessor
自定义PropertySourceLoader的方式只能读取本地配置,如果想更灵活一点,读取数据库或者是配置中心的配置就需要其它途径,例如上面提到的自定义EnvironmentPostProcessor
的方式,在springboot环境准备好之后把自定义的配置加入到系统内存中。但是这种方式有个缺点就是,它不能刷新配置,远程配置发生变更之后只能通过重启应用让springboot重新拉取配置。如果想实现定时刷新配置信息,可以通过springcloud提供的ContextRefresher.refresh方法,这个方法会把springboot中之前加载好的EnvironmentPostProcessor中的postProcessEnvironment方法重新执行一遍,详情见示例。
自定义PropertySourceLocator
第二种方式是自定义PropertySourceLocator,这也是springcloud中提供的扩展,也要结合ContextRefresher.refresh来刷新配置,详见示例。
三方组件实现刷新远程配置
例如Archaius PolledConfigurationSource,这个组件会启动一个schedule定时从你指定的配置源拉取配置数据,但是它读取配置需要用它组件提供的方法,而不是通过@Value结合@RefreshScope的方式。参考示例。