springboot源码分析之环境属性构造过程1
使用springboot的目的就是在項目開發中,快速出東西,因此springboot對于配置文件的格式支持是非常豐富的,最常見的配置文件后綴有如下四種:properties、xml、yml、yaml,比如我們在springboot項目根目錄中配置了一個application.properties文件,則springboot項目啟動的時候就會自動將該文件的內容解析并設置到環境中,這樣后續需要使用該文件中配置的屬性的時候,只需要使用@value即可。同理application.xml、application.yml、application.yaml文件也會自動被加載并最終設置到環境中。
上面我們提到了環境,那么環境到底是個什么玩意呢?在這里提前說一下:我們這里關注的是源碼層面的事情。并非講解api如何使用。
大家首先思考一下,springboot項目如何啟動,這個到很簡單,無外乎引入springboot依賴包,設置項目啟動的main方法如下所示:
@EnableAutoConfiguration @ComponentScan(value = "cn.bainuoyoupin.web") public class Application {private static Logger logger = LoggerFactory.getLogger(Application.class);public static void main(String[] args) {long startTime = System.currentTimeMillis();SpringApplication.run(Application.class,args);logger.info("程序啟動花費時間為:" + (System.currentTimeMillis() - startTime) / 1000 + "秒");} }上述的代碼非常的簡單,但是springboot做了非常多的事情,因為springboot代碼體系非常龐大,所以后續的文章是我們講解那些源碼就直接看那些源碼,把不需要了解的暫時放到一邊。因此在這里暫時先關注環境的創建源碼,我們快速定位到SpringApplication類中的public ConfigurableApplicationContext run(String... args)方法,該方法關于環境的準備代碼如下所示:
,... ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); ...prepareEnvironment方法從名字就可以看出來是準備環境(Environment),prepareEnvironment代碼如下:
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {//獲取或者創建環境ConfigurableEnvironment environment = getOrCreateEnvironment();//配置環境的信息configureEnvironment(environment, applicationArguments.getSourceArgs());//通知所有的觀察者,環境已經準備好了。listeners.environmentPrepared(environment);//將第一步創建的環境進行轉換if (isWebEnvironment(environment) && !this.webEnvironment) {environment = convertToStandardEnvironment(environment);}return environment; }接下來,我們一步步的分析。
1.獲取或者創建環境。
getOrCreateEnvironment()方法如下所示:
private ConfigurableEnvironment getOrCreateEnvironment() {if (this.environment != null) {return this.environment;}if (this.webEnvironment) {return new StandardServletEnvironment();}return new StandardEnvironment(); }上述代碼邏輯如下:
1.如果environment不為空則直接返回。
2.如果是web環境則直接實例化StandardServletEnvironment類。
3.如果不是web環境則直接實例化StandardEnvironment類。
注意:environment 為ConfigurableEnvironment類型。我們不妨看一下該類的層次圖如下所示:
????Environment接口是Spring對當前程序運行期間的環境的封裝(spring)。主要提供了兩大功能:profile和property(頂級接口PropertyResolver提供)。目前主要有StandardEnvironment、StandardServletEnvironment和MockEnvironment3種實現,分別代表普通程序、Web程序以及測試程序的環境。通過上述的getOrCreateEnvironment方法處理邏輯也是可以總結出來的。
2.環境的裝載
在上面的代碼中實例化了StandardServletEnvironment類(我自己的環境是web),實例化該類的時候肯定會實例化其父類AbstractEnvironment,AbstractEnvironment類的構造函數如下:
public AbstractEnvironment() {customizePropertySources(this.propertySources);}需要注意一點,因為實例化的是StandardServletEnvironment類,jvm會自動觸發其父類中的構造函數,但是當前程序的this指針依然是StandardServletEnvironment。
this.propertySources屬性如下所示:
AbstractEnvironment.java
private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); 我們繼續跟蹤customizePropertySources方法,如下所示: AbstractEnvironment.java protected void customizePropertySources(MutablePropertySources propertySources) {}好吧,customizePropertySources方法竟然是個空的實現,但是注意一點,當前程序this是StandardServletEnvironment實例,我們不妨看一下StandardServletEnvironment類中是否重寫了該方法。果不其然,StandardServletEnvironment類重寫了customizePropertySources方法,詳細代碼如下所示:
StandardServletEnvironment.java
protected void customizePropertySources(MutablePropertySources propertySources) {//servletConfigInitParamspropertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));//servletContextInitParamspropertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));//jndiPropertiesif (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));}super.customizePropertySources(propertySources);}上述的代碼中,propertySources為AbstractEnvironment.java中的propertySources字段,因為他是個引用類型,所以可以拿到指針即可修改其值。
雖然我們暫時還不知道propertySources要干啥,但是我們還是先看明白PropertySources到底要干啥。PropertySources類圖如下所示:
MutablePropertySources類內部持有一個propertySourceList集合,該集合的詳細定義如下:
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
注意:propertySourceList為List<PropertySource<?>>類型。
MutablePropertySources類提供了一系列對propertySourceList操作的方法:
addFirst:在propertySourceList 頭部添加元素。
addLast:在propertySourceList 尾部添加元素。
addAtIndex:在propertySourceList 指定的位置添加元素。
等等。
不管是哪個api均是對propertySourceList集合進行操作的,這一點我們了解就可以了。
2.1PropertySource類
PropertySource類是一個抽象類,代碼如下:
public abstract class PropertySource<T> {protected final String name;protected final T source; ...省略其他的方法 }getSource()方法:,這個方法會返回得到屬性源的源頭。比如MapPropertySource的源頭就是一個Map,PropertiesPropertySource的源頭就是一個Properties。
name:我們可以理解為一個map中的key。
PropertySource類的子類結構如下:
RandomValuePropertySource:source是random。
ServletConfigPropertySource:source是ServletConfig。
ServletContextPropertySource:source是ServletContext。
JndiPropertySource:source是JndiLocatorDelegate。
StubPropertySource:source是Object。
MapPropertySource:source是Map<String, Object>。
了解了這些內容之后,我們再次看一下customizePropertySources方法的實現:
首先添加servletConfigInitParams,然后添加servletContextInitParams,其次判斷是否是jndi環境,如果是則添加jndiProperties,最后調用父類的customizePropertySources(propertySources)。
在跟進父類的customizePropertySources(propertySources)方法之前,我們總結一下MutablePropertySources類中propertySourceList已經存在的屬性為servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)。
StandardEnvironment類為StandardServletEnvironment類的父類,該類的customizePropertySources方法如下:
protected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}1、添加systemProperties
2、添加systemEnvironment。
上述的方法邏輯執行完畢之后,MutablePropertySources類中propertySourceList已經存在的屬性為servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment。
經過一系列的跟蹤getOrCreateEnvironment方法所做的事情已經分析完畢了。我們不妨繼往下看。
3.配置環境信息
configureEnvironment(environment, applicationArguments.getSourceArgs())方法詳細實現如下所示:
protected void configureEnvironment(ConfigurableEnvironment environment,String[] args) {configurePropertySources(environment, args);configureProfiles(environment, args);}3.1配置屬性源
configurePropertySources(environment, args)方法的核心實現如下:
protected void configurePropertySources(ConfigurableEnvironment environment,String[] args) {MutablePropertySources sources = environment.getPropertySources();if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));}if (this.addCommandLineProperties && args.length > 0) {String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;if (sources.contains(name)) {PropertySource<?> source = sources.get(name);CompositePropertySource composite = new CompositePropertySource(name);composite.addPropertySource(new SimpleCommandLinePropertySource(name + "-" + args.hashCode(), args));composite.addPropertySource(source);sources.replace(name, composite);}else {sources.addFirst(new SimpleCommandLinePropertySource(args));}}}1、如果defaultProperties不為空,則繼續添加defaultProperties。
2、如果addCommandLineProperties為true并且有命令參數,分兩步驟走:第一步存在commandLineArgs則繼續設置屬性;第二步commandLineArgs不存在則在頭部添加commandLineArgs。
上述的代碼執行完畢之后,MutablePropertySources類中propertySourceList已經存在的屬性為commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)。
3.2配置Profiles
這個后續我們用到了再來講解。
本文我們暫時講解到這里,后續的文章中,我們繼續跟蹤屬性文件的加載規則以及加載過程。提前曝光一點:
commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)中的屬性優先級從前到后依次降低。在最前面的使用優先級最高。
比如commandLineArgs中存在一個屬性a=1; systemProperties中存在一個屬性a=2,則我們程序使用的時候a=1,因為越靠前的優先級越高。通過上述的優先級我們可以發現一個規律,命令行的優先級最高、其次是程序中的、然后是系統的環境變量以及屬性、最后是默認的。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的springboot源码分析之环境属性构造过程1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 应用开发者必须了解的Kubernetes
- 下一篇: Flowable节点跳转