首先说说原理,我们知道使用一个公用的starter的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且SpringBoot会自动进行类的自动配置。那么 SpringBoot 是如何知道要实例化哪些类,并进行自动配置的呢? 下面简单说一下。
首先,SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories
文件,然后根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。
第二步,根据 spring.factories
配置加载AutoConfigure
类。
最后,根据 @Conditional
注解的条件,进行自动配置并将Bean注入Spring Context 上下文当中。
我们也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class})
指定自动配置哪些类。
官方提供的starter,大多包含两个jar包: 一个starter——没有任何实现,只用来管理依赖(spring.providers文件声明);一个autoconfigure包——包含所有具体实现,包括自动配置类,及META-INF/spring.factories文件。自定义starter的时候,可以合并写到一个。
官方提供的starter,命名遵循spring-boot-starter-xx; 自定义starter,命名遵循xx-spring-boot-starter。
从项目的截图可以看出,包的拆分上采取了类似官方的2个jar的形式;命名上采取了 自定义starter,遵循xx-spring-boot-starter的命名方式。
如上图所示,这是一个starter——没有任何实现,只用来管理依赖(spring.providers文件声明), 该声明指明了依赖由
provides: nacos-config-spring-boot-autoconfigure 提供。
其pom.xml 内容如期望的一样:仅有业务依赖和基础starter依赖,而没有基础配置依赖,因为包含在了nacos-config-spring-boot-autoconfigure包中。
org.springframework.boot
spring-boot-starter
true
com.alibaba.nacos
nacos-spring-context
com.alibaba.boot
nacos-spring-boot-base
com.alibaba.boot
nacos-config-spring-boot-autoconfigure
如上图所示,在resources/META-INF/
下spring.factories
文件中如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.boot.nacos.config.autoconfigure.NacosConfigAutoConfiguration
是整个配置的关键部分,其指明了AutoConfigure实现
类是 NacosConfigAutoConfiguration类。而该类的核心作用就是实现其注解中的相关bean在某些条件下的自动装配。
@ConditionalOnProperty(name = NacosConfigConstants.ENABLED, matchIfMissing = true)
@ConditionalOnMissingBean(name = CONFIG_GLOBAL_NACOS_PROPERTIES_BEAN_NAME)
@EnableNacosConfig
@EnableConfigurationProperties(value = NacosConfigProperties.class)
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
public class NacosConfigAutoConfiguration {
}
该类通过注解进行实现,其注解的含义如下:
@ConditionalOnClass
,当classpath
下发现该类的情况下进行自动配置。@ConditionalOnMissingBean
,当Spring Context
中不存在该Bean
时进行自动配置。@ConditionalOnProperty(name = NacosConfigConstants.ENABLED, matchIfMissing = true)
时进行自动配置。其中NacosConfigConstants类的如下:
public interface NacosConfigConstants {
String ENDPOINT_PREFIX = "nacos-config";
String ENABLED = EnableNacosConfig.CONFIG_PREFIX + "enabled";
String PREFIX = "nacos.config";
}
有引用到了另一个注解
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({NacosConfigBeanDefinitionRegistrar.class})
public @interface EnableNacosConfig {
String CONFIG_PREFIX = "nacos.config.";
String ENDPOINT_PLACEHOLDER = "${nacos.config.endpoint:${nacos.endpoint:}}";
String NAMESPACE_PLACEHOLDER = "${nacos.config.namespace:${nacos.namespace:}}";
String ACCESS_KEY_PLACEHOLDER = "${nacos.config.access-key:${nacos.access-key:}}";
String SECRET_KEY_PLACEHOLDER = "${nacos.config.secret-key:${nacos.secret-key:}}";
String SERVER_ADDR_PLACEHOLDER = "${nacos.config.server-addr:${nacos.server-addr:}}";
String CONTEXT_PATH_PLACEHOLDER = "${nacos.config.context-path:${nacos.context-path:}}";
String CLUSTER_NAME_PLACEHOLDER = "${nacos.config.cluster-name:${nacos.cluster-name:}}";
String ENCODE_PLACEHOLDER = "${nacos.config.encode:${nacos.encode:UTF-8}}";
NacosProperties globalProperties() default @NacosProperties(
endpoint = "${nacos.config.endpoint:${nacos.endpoint:}}",
namespace = "${nacos.config.namespace:${nacos.namespace:}}",
accessKey = "${nacos.config.access-key:${nacos.access-key:}}",
secretKey = "${nacos.config.secret-key:${nacos.secret-key:}}",
serverAddr = "${nacos.config.server-addr:${nacos.server-addr:}}",
contextPath = "${nacos.config.context-path:${nacos.context-path:}}",
clusterName = "${nacos.config.cluster-name:${nacos.cluster-name:}}",
encode = "${nacos.config.encode:${nacos.encode:UTF-8}}"
);
}
从上面的类和注解可以看出,其实,就相当于更为常见的 @ConditionalOnProperty(prefix = "nacos.config",value = "enabled",havingValue = "true")。
从上面可以看出,其毫无例外的都用都了基于前缀的自动配置进行bean的装配,那么最终依赖如下
spring-boot-configuration-processor 也是必然的了。
org.springframework.boot
spring-boot-configuration-processor
true
在最上面的红框中我们看到了 spring-configuration-metadata.json 核心作用是使用时给出自动给出IDE提示,需要料及而更多请参照:Springboot实现基于前缀的自定义配置和自动提示功能。
进一步,看下pom.xml中依赖了哪些包?
org.springframework.boot
spring-boot-autoconfigure
true
org.springframework.boot
spring-boot-starter-logging
true
org.springframework.boot
spring-boot-configuration-processor
true
com.alibaba.nacos
nacos-spring-context
true
com.alibaba.boot
nacos-spring-boot-base
true
org.springframework.boot
spring-boot-starter-test
test
最终,确实依赖到了如下2个包:
spring-boot-autoconfigure
spring-boot-configuration-processor
附加额外注解解释:
@ConditionalOnBean:当容器中有指定的Bean的条件下
@ConditionalOnClass:当类路径下有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean:当容器中没有指定Bean的情况下
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径下是否有指定的资源
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean @ConditionalOnWebApplication:当前项目是Web项目的条件下
参考: 1. 编写自己的SpringBoot-starter
2.实现自定义Spring Boot Starter
3.nacos-spring-boot-project