前言
在我新入职公司后熟悉公司项目时,想用dbeaver连接他的数据库看看,就得知道这个项目连接的数据库ip、账户密码,通常情况都是在本地配置,所以我索性就找,但是找了半天都什么都没有,不禁的就开始好奇,这玩意是怎么配置数据库信息的,最后实在找不到配置文件,所以想到在SpringBoot创建数据库连接时一定会创建一个DataSource
,所以只要在其实现类先打下断点,就可以通过传递的参数看到连接信息,果不其然,在一个DriverDataSource
的构造方法下传递了数据库的连接信息。
但出于好奇,所以一层层的追踪从什么地方取到的参数并传递给DriverDataSource
的构造方法,最后发现还是在DataSourceProperties
下获取的,这个类是SpringBoot用来表示在配置文件中spring.datasource
开头的配置,但整个工程都没有配置过这个属性,那从什么地方配置的呢?
最后顺藤摸瓜找到了关键点,即NacosPropertySourceLocator
,我们知道,在引入spring-boot-starter-jdbc
后,如果不在配置文件中设置spring.datasource
信息,那么Spring Boot将拒绝启动,很明显,Spring Boot表示你既然要引入我,那么肯定要使用数据库,既然要使用数据库,那么你就得要告诉我连接信息,这很合理吧。
通常情况我们都是在配置文件中写入,但这样就比较死了,最灵活的方式就是通过SpringBoot留下来的扩展方式,在代码中动态添加。
而NacosPropertySourceLocator
做的事就是从Nacos服务中拉取配置,并添加到系统的属性源集合中,他的实现核心是PropertySourceLocator
扩展,但这个扩展是Spring Cloud家族的,但在往上说,他还是使用了SpringBoot的扩展接口,只是在这基础上封装了一个专门针对加载属性列表的这样一个接口。
ApplicationContextInitializer
我们从最上层开始说,Spring Cloud中的PropertySourceLocator
实现核心是ApplicationContextInitializer
,这是Spring留下来为我们在应用程序上下文初始化前做的一些工作,比如拿到系统的PropertySources
,向其中添加自定义的配置,添加后即可通过@Value
这种方式取得。
代码如下。
@Configuration
class TestApplicationContextInitializer :
ApplicationContextInitializer<ConfigurableApplicationContext>, CommandLineRunner {
@Autowired
lateinit var dataSource: DataSource
override fun run(vararg args: String?) {
println(dataSource as HikariDataSource)
}
override fun initialize(applicationContext: ConfigurableApplicationContext) {
val mapOf = mapOf<String, String>(
"spring.datasource.url" to "jdbc:mysql://localhost:3306/db_blog",
"spring.datasource.username" to "hxl",
"spring.datasource.driver-class-name" to "com.mysql.cj.jdbc.Driver",
)
applicationContext.environment.propertySources.addFirst(MapPropertySource("datasource", mapOf))
}
}
注意的是,@Configuration
不能使ApplicationContextInitializer
生效,他是为了让CommandLineRunner
生效。
而我们所写的这个类会被实例化两次,一次是在应用程序上下文初始化前回调initialize
方法是所创建,另一次是被Spring扫描到@Configuration
注解后将其加入到Bean集合中最后实例化。
而要想ApplicationContextInitializer
生效,有三种办法。
- 系统属性配置中加入
context.initializer.classes
context.initializer.classes=com.example.springclouddemo.loader.TestApplicationContextInitializer
- 新建
META-INF/spring.factories
,内容如下
org.springframework.context.ApplicationContextInitializer=com.example.springclouddemo.loader.TestPropertySourceLocator
- 通过addInitializers 方法
@EnableDiscoveryClient
@SpringBootApplication
@EnableConfigurationProperties
class SpringCloudDemoApplication
fun main(args: Array<String>) {
val application = SpringApplication(SpringCloudDemoApplication::class.java)
application.addInitializers(TestApplicationContextInitializer())
application.run(*args)
}
运行后,将会看到DataSource
会被注入成功,也证实了在代码中加入的属性集合生效了。
PropertySourceLocator
这个扩展是Spring Cloud中的东西,使用起来也很简单,只需要实现locate方法返回一个PropertySource
即可。
class TestPropertySourceLocator : PropertySourceLocator {
override fun locate(environment: Environment?): PropertySource<*> {
val mapOf = mapOf(
"spring.datasource.url" to "jdbc:mysql://rm-bp1v2a7fy0qq145e5qo.mysql.rds.aliyuncs.com:3306/db_blog",
"spring.datasource.username" to "hxl",
"spring.datasource.driver-class-name" to "com.mysql.cj.jdbc.Driver",
"spring.datasource.url" to " jdbc:mysql://rm-bp1v2a7fy0qq145e5qo.mysql.rds.aliyuncs.com:3306/db_blog"
)
return MapPropertySource("test", mapOf)
}
}
让其生效同样需要在META-INF/spring.factories
下加入下面配置。
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.example.springclouddemo.loader.TestPropertySourceLocator
源码
源码一共分为两步。
-
BootstrapImportSelectorConfiguration
通过@Import
注解从某地提取一些class,最终将这些class实例化后放入系统Bean容器中。而这里的某地就是从
spring.factories
文件,属性名是org.springframework.cloud.bootstrap.BootstrapConfiguration
,到这里,我们所指定的类就可以通过@Autowired
注解注入。如下是提取类名的源码
BootstrapImportSelector#selectImports
List<String> names = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader));
-
获取所有
PropertySourceLocator
实现类,并收集属性源从下面这个类中可以看到,他通过
@Autowired
注入了所有实现了PropertySourceLocator
接口的类,而这个实现类就是上面我们编写的,如果使用了Nacos的话,这里有一个NacosPropertySourceLocator
,他负责从远程拉取配置。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
}
而在其initialize
方法下会遍历上面集合,调用locate
进行属性收集,最后同样调用addLast
等方法向系统的属性原集合中添加新属性。
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
}