(Spring學習07)Spring之啟動刷新過程源碼解析

概述

通常,我們說的Spring啟動,就是構造ApplicationContext對象以及調用refresh()方法的過程。
首先,Spring啟動過程主要做了這么幾件事情:

  1. 構造一個BeanFactory對象
  2. 解析配置類,得到BeanDefinition,并注冊到BeanFactory中
    i. 解析@ComponentScan,此時就會完成掃描
    ii. 解析@Import
    iii. 解析@Bean
    iv. …
  3. 因為ApplicationContext還支持國際化,所以還需要初始化MessageSource對象
  4. 因為ApplicationContext還支持事件機制,所以還需要初始化ApplicationEventMulticaster對象
  5. 把用戶定義的ApplicationListener對象添加到ApplicationContext中,等Spring啟動完了就要發布事件了
  6. 創建非懶加載的單例Bean對象,并存在BeanFactory的單例池中。
  7. 調用Lifecycle Bean的start()方法
  8. 發布ContextRefreshedEvent事件

由于Spring啟動過程中要創建非懶加載的單例Bean對象,那么就需要用到BeanPostProcessor,所以Spring在啟動過程中就需要做兩件事:

  1. 生成默認的BeanPostProcessor對象,并添加到BeanFactory中
    i. AutowiredAnnotationBeanPostProcessor:處理@Autowired、@Value
    ii. CommonAnnotationBeanPostProcessor:處理@Resource、@PostConstruct、@PreDestroy
    iii. ApplicationContextAwareProcessor:處理ApplicationContextAware等回調
  2. 找到外部用戶所定義的BeanPostProcessor對象(類型為BeanPostProcessor的Bean對象),并添加到BeanFactory中

BeanFactoryPostProcessor

BeanPostProcessor表示Bean的后置處理器,是用來對Bean進行加工的,類似的,BeanFactoryPostProcessor理解為BeanFactory的后置處理器,用來用對BeanFactory進行加工的。

Spring支持用戶定義BeanFactoryPostProcessor的實現類Bean,來對BeanFactory進行加工,比如:

@Component
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");beanDefinition.setAutowireCandidate(false);}
}

以上代碼,就利用了BeanFactoryPostProcessor來拿到BeanFactory,然后獲取BeanFactory內的某個BeanDefinition對象并進行修改,注意這一步是發生在Spring啟動時,創建單例Bean之前的,所以此時對BeanDefinition進行修改是會生效的。
?

注意:在ApplicationContext內部有一個核心的DefaultListableBeanFactory,它實現了ConfigurableListableBeanFactory和BeanDefinitionRegistry接口,所以ApplicationContext和DefaultListableBeanFactory是可以注冊BeanDefinition的,但是ConfigurableListableBeanFactory是不能注冊BeanDefinition的,只能獲取BeanDefinition,然后做修改。

所以Spring還提供了一個BeanFactoryPostProcessor的子接口:BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;}

我們可以看到BeanDefinitionRegistryPostProcessor繼承了BeanFactoryPostProcessor接口,并新增了一個方法,注意方法的參數為BeanDefinitionRegistry,所以如果我們提供一個類來實現BeanDefinitionRegistryPostProcessor,那么在postProcessBeanDefinitionRegistry()方法中就可以注冊BeanDefinition了。比如:

@Component
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(User.class);registry.registerBeanDefinition("user", beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");beanDefinition.setAutowireCandidate(false);}
}

如何理解refresh()?

/*** Load or refresh the persistent representation of the configuration,* which might an XML file, properties file, or relational database schema.* <p>As this is a startup method, it should destroy already created singletons* if it fails, to avoid dangling resources. In other words, after invocation* of that method, either all or no singletons at all should be instantiated.* @throws BeansException if the bean factory could not be initialized* @throws IllegalStateException if already initialized and multiple refresh* attempts are not supported*/void refresh() throws BeansException, IllegalStateException;

這是ConfigurableApplicationContext接口上refresh()方法的注釋,意思是:加載或刷新持久化的配置,可能是XML文件、屬性文件或關系數據庫中存儲的。由于這是一個啟動方法,如果失敗,它應該銷毀已經創建的單例,以避免暫用資源。換句話說,在調用該方法之后,應該實例化所有的單例,或者根本不實例化單例 。

有個理念需要注意:ApplicationContext關閉之后不代表JVM也關閉了,ApplicationContext是屬于JVM的,說白了ApplicationContext也是JVM中的一個對象。

在Spring的設計中,也提供可以刷新的ApplicationContext和不可以刷新的ApplicationContext。比如:

AbstractRefreshableApplicationContext extends AbstractApplicationContext

就是可以刷新的

GenericApplicationContext extends AbstractApplicationContext

就是不可以刷新的。

AnnotationConfigApplicationContext繼承的是GenericApplicationContext,所以它是不能刷新的。
AnnotationConfigWebApplicationContext繼承的是AbstractRefreshableWebApplicationContext,所以它是可以刷的。

上面說的不能刷新是指不能重復刷新,只能調用一次refresh方法,第二次時會報錯。

refresh()底層原理流程

底層原理流程圖:https://www.processon.com/view/link/5f60a7d71e08531edf26a919
下面以AnnotationConfigApplicationContext為例子,來介紹refresh的底層原理。

  1. 在調用AnnotationConfigApplicationContext的構造方法之前,會調用父類GenericApplicationContext的無參構造方法,會構造一個BeanFactory,為DefaultListableBeanFactory。

  2. 構造AnnotatedBeanDefinitionReader(主要作用添加一些基礎的PostProcessor,同時可以通過reader進行BeanDefinition的注冊),同時對BeanFactory進行設置和添加PostProcessor(后置處理器)
    i. 設置dependencyComparator:AnnotationAwareOrderComparator,它是一個Comparator,是用來進行排序的,會獲取某個對象上的Order注解或者通過實現Ordered接口所定義的值進行排序,在日常開發中可以利用這個類來進行排序。
    ii. 設置autowireCandidateResolver:ContextAnnotationAutowireCandidateResolver,用來解析某個Bean能不能進行自動注入,比如某個Bean的autowireCandidate屬性是否等于true
    iii. 向BeanFactory中添加ConfigurationClassPostProcessor對應的BeanDefinition,它是一個BeanDefinitionRegistryPostProcessor,并且實現了PriorityOrdered接口
    iv. 向BeanFactory中添加AutowiredAnnotationBeanPostProcessor對應的BeanDefinition,它是一個InstantiationAwareBeanPostProcessorAdapter,MergedBeanDefinitionPostProcessor
    v. 向BeanFactory中添加CommonAnnotationBeanPostProcessor對應的BeanDefinition,它是一個InstantiationAwareBeanPostProcessor,InitDestroyAnnotationBeanPostProcessor
    vi. 向BeanFactory中添加EventListenerMethodProcessor對應的BeanDefinition,它是一個BeanFactoryPostProcessor,SmartInitializingSingleton
    vii. 向BeanFactory中添加DefaultEventListenerFactory對應的BeanDefinition,它是一個EventListenerFactory

  3. 構造ClassPathBeanDefinitionScanner(主要作用可以用來掃描得到并注冊BeanDefinition),同時進行設置:
    i. 設置this.includeFilters = AnnotationTypeFilter(Component.class)
    ii. 設置environment
    iii. 設置resourceLoader

  4. 利用reader注冊AppConfig為BeanDefinition,類型為AnnotatedGenericBeanDefinition

  5. 接下來就是調用refresh方法

  6. prepareRefresh():
    i. 記錄啟動時間
    ii. 可以允許子容器設置一些內容到Environment中
    iii. 驗證Environment中是否包括了必須要有的屬性

  7. obtainFreshBeanFactory():進行BeanFactory的refresh,在這里會去調用子類的refreshBeanFactory方法,具體子類是怎么刷新的得看子類,然后再調用子類的getBeanFactory方法,重新得到一個BeanFactory

  8. prepareBeanFactory(beanFactory):
    i. 設置beanFactory的類加載器
    ii. 設置表達式解析器:StandardBeanExpressionResolver,用來解析Spring中的表達式
    iii. 添加PropertyEditorRegistrar:ResourceEditorRegistrar,PropertyEditor類型轉化器注冊器,用來注冊一些默認的PropertyEditor
    iv. 添加一個Bean的后置處理器:ApplicationContextAwareProcessor,是一個BeanPostProcessor,用來執行EnvironmentAware、ApplicationEventPublisherAware等回調方法
    v. 添加ignoredDependencyInterface:可以向這個屬性中添加一些接口,如果某個類實現了這個接口,并且這個類中的某些set方法在接口中也存在,那么這個set方法在自動注入的時候是不會執行的,比如EnvironmentAware這個接口,如果某個類實現了這個接口,那么就必須實現它的setEnvironment方法,而這是一個set方法,和Spring中的autowire是沖突的,那么Spring在自動注入時是不會調用setEnvironment方法的,而是等到回調Aware接口時再來調用(注意,這個功能僅限于xml的autowire,@Autowired注解是忽略這個屬性的)

     a. EnvironmentAwareb. EmbeddedValueResolverAwarec. ResourceLoaderAwared. ApplicationEventPublisherAwaree. MessageSourceAwaref. ApplicationContextAwareg. 另外其實在構造BeanFactory的時候就已經提前添加了另外三個:h. BeanNameAwarei. BeanClassLoaderAwarej. BeanFactoryAware
    

    vi. 添加resolvableDependencies:在byType進行依賴注入時,會先從這個屬性中根據類型找bean

     a. BeanFactory.class:當前BeanFactory對象b. ResourceLoader.class:當前ApplicationContext對象c. ApplicationEventPublisher.class:當前ApplicationContext對象d. ApplicationContext.class:當前ApplicationContext對象
    

    vii. 添加一個Bean的后置處理器:ApplicationListenerDetector,是一個BeanPostProcessor,用來判斷某個Bean是不是ApplicationListener,如果是則把這個Bean添加到ApplicationContext中去,注意一個ApplicationListener只能是單例的
    viii. 添加一個Bean的后置處理器:LoadTimeWeaverAwareProcessor,是一個BeanPostProcessor,用來判斷某個Bean是不是實現了LoadTimeWeaverAware接口,如果實現了則把ApplicationContext中的loadTimeWeaver回調setLoadTimeWeaver方法設置給該Bean。
    ix. 添加一些單例bean到單例池:

     a. "environment":Environment對象b. "systemProperties":System.getProperties()返回的Map對象c. "systemEnvironment":System.getenv()返回的Map對象
    
  9. postProcessBeanFactory(beanFactory) : 提供給AbstractApplicationContext的子類進行擴展,具體的子類,可以繼續向BeanFactory中再添加一些東西

  10. invokeBeanFactoryPostProcessors(beanFactory):執行BeanFactoryPostProcessor
    i. 此時在BeanFactory中會存在一個BeanFactoryPostProcessor:ConfigurationClassPostProcessor,它也是一個
    BeanDefinitionRegistryPostProcessor
    ii. 第一階段
    iii. 從BeanFactory中找到類型為BeanDefinitionRegistryPostProcessor的beanName,也就是ConfigurationClassPostProcessor, 然后調用BeanFactory的getBean方法得到實例對象
    iv. 執行 ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry() 方法:

    	a. 解析AppConfig類b. 掃描得到BeanDefinition并注冊c. 解析@Import,@Bean等注解得到BeanDefinition并注冊d. 詳細的看另外的筆記,專門分析了ConfigurationClassPostProcessor是如何工作的e. 在這里,我們只需要知道在這一步會去得到BeanDefinition,而這些BeanDefinition中可能存在BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,所以執行完ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法后,還需要繼續執行其他BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法
    

    v. 執行其他BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry() 方法
    vi. 執行所有BeanDefinitionRegistryPostProcessor的 postProcessBeanFactory() 方法
    vii. 第二階段
    viii. 從BeanFactory中找到類型為BeanFactoryPostProcessor的beanName,而這些BeanFactoryPostProcessor包括了上面的BeanDefinitionRegistryPostProcessor
    ix. 執行還沒有執行過的BeanFactoryPostProcessor的postProcessBeanFactory() 方法

  11. 到此,所有的BeanFactoryPostProcessor的邏輯都執行完了,主要做的事情就是得到BeanDefinition并注冊到BeanFactory中

  12. registerBeanPostProcessors(beanFactory):因為上面的步驟完成了掃描,這個過程中程序員可能自己定義了一些BeanPostProcessor,在這一步就會把BeanFactory中所有的BeanPostProcessor找出來并實例化得到一個對象,并添加到BeanFactory中去(屬性beanPostProcessors),最后再重新添加一個ApplicationListenerDetector對象(之前其實就添加了過,這里是為了把ApplicationListenerDetector移動到最后)

  13. initMessageSource():如果BeanFactory中存在一個叫做"messageSource"的BeanDefinition,那么就會把這個Bean對象創建出來并賦值給ApplicationContext的messageSource屬性,讓ApplicationContext擁有國際化的功能

  14. initApplicationEventMulticaster():如果BeanFactory中存在一個叫做"applicationEventMulticaster"的BeanDefinition,那么就會把這個Bean對象創建出來并賦值給ApplicationContext的applicationEventMulticaster屬性,讓ApplicationContext擁有事件發布的功能

  15. onRefresh():提供給AbstractApplicationContext的子類進行擴展

  16. registerListeners():從BeanFactory中獲取ApplicationListener類型的beanName,然后添加到ApplicationContext中的事件廣播器applicationEventMulticaster中去,到這一步因為FactoryBean還沒有調用getObject()方法生成Bean對象,所以這里要在根據類型找一下ApplicationListener,記錄一下對應的beanName

  17. finishBeanFactoryInitialization(beanFactory):完成BeanFactory的初始化,主要就是實例化非懶加載的單例Bean,單獨的筆記去講。

  18. finishRefresh():BeanFactory的初始化完后,就到了Spring啟動的最后一步了

  19. 設置ApplicationContext的lifecycleProcessor,默認情況下設置的是DefaultLifecycleProcessor

  20. 調用lifecycleProcessor的onRefresh()方法,如果是DefaultLifecycleProcessor,那么會獲取所有類型為Lifecycle的Bean對象,然后調用它的start()方法,這就是ApplicationContext的生命周期擴展機制

  21. 發布ContextRefreshedEvent事件

執行BeanFactoryPostProcessor

  1. 執行通過ApplicationContext添加進來的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法
  2. 執行BeanFactory中實現了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法
  3. 執行BeanFactory中實現了Ordered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法
  4. 執行BeanFactory中其他的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法
  5. 執行上面所有的BeanDefinitionRegistryPostProcessor的postProcessBeanFactory()方法
  6. 執行通過ApplicationContext添加進來的BeanFactoryPostProcessor的postProcessBeanFactory()方法
  7. 執行BeanFactory中實現了PriorityOrdered接口的BeanFactoryPostProcessor的postProcessBeanFactory()方法
  8. 執行BeanFactory中實現了Ordered接口的BeanFactoryPostProcessor的postProcessBeanFactory()方法
  9. 執行BeanFactory中其他的BeanFactoryPostProcessor的postProcessBeanFactory()方法

Lifecycle的使用

Lifecycle表示的是ApplicationContext的生命周期,可以定義一個SmartLifecycle來監聽ApplicationContext的啟動和關閉:

@Component
public class TestLifecycle implements SmartLifecycle {private boolean isRunning = false;@Overridepublic void start() {System.out.println("啟動");isRunning = true;}@Overridepublic void stop() {// 要觸發stop(),要調用context.close(),或者注冊關閉鉤子(context.registerShutdownHook();)System.out.println("停止");isRunning = false;}@Overridepublic boolean isRunning() {return isRunning;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211886.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211886.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211886.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CrystalDiskInfo中文版(硬盤檢測工具) v9.1.1.0 綠色漢化版-供大家學習研究參考

更新內容 重新支持三星SATA SSD壽命報告 增加對ZHITAI SC001的支持 新增SK hynix Gold S31支持 增加了KLEVV NEO N610的支持。 改進的Micron/Crucial SATA SSD支持 已更改 卸載程序將顯示一個確認對話框&#xff0c;用于刪除設置。 強大功能 1.擁有多國語言&#xff0c;…

27 動態規劃解最大子序和

問題描述&#xff1a;給定一個整數數組nums&#xff0c;找到一個具有最大和的連續子數組(子數組最少含有一個元素)&#xff0c;返回其最大和。 動態規劃求解&#xff1a;定義dp[i]表示以i元素為結尾的最大和&#xff0c;如果dp[i-1]小于零的話&#xff0c;dp[i]nums[i],否則dp…

React-hook-form-mui(三):表單驗證

前言 在上一篇文章中&#xff0c;我們介紹了react-hook-form-mui的基礎用法。本文將著重講解表單驗證功能。 react-hook-form-mui提供了豐富的表單驗證功能&#xff0c;可以通過validation屬性來設置表單驗證規則。本文將詳細介紹validation的三種實現方法&#xff0c;以及如何…

ts中type和interface類型聲明的區別

1. 寫法上 type 使用關鍵字 type 進行聲明。 interface 使用關鍵字 interface 進行聲明。 // 使用 type type MyType {param: string; };// 使用 interface interface MyInterface {param: string; }2. 可合并性 interface 具有可合并性&#xff0c;允許在同一作用域內多次…

045:Vue讀取本地上傳JSON文件,導出JSON文件方法

第045個 查看專欄目錄: VUE ------ element UI 專欄目標 在vue和element UI聯合技術棧的操控下&#xff0c;本專欄提供行之有效的源代碼示例和信息點介紹&#xff0c;做到靈活運用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安裝、引用&#xff0c;模板使…

jquery手寫廣告輪播圖,無限循環功能

說明 在很多情況下&#xff0c;我們都需要開發廣告輪播圖&#xff0c;當我們進行頁面的功能開發時&#xff0c;采用輪播圖來實現也行&#xff0c;但是很多情況下&#xff0c;我們只需要簡單的控制輪播循環輪播展示即可&#xff0c;所以用jq開開發廣告輪播波&#xff0c;自定義…

spring更加松散的獲取bean的方式ObjectProvider

概述 ObjectProvider直譯就是對象提供者; 平時從spring中獲取bean都是調用beanFactory.getBean()方法&#xff0c;如果bean不存在則會直接拋異常; 從spring 4.3開始引入了org.springframework.beans.factory.ObjectProvider接口,其提供了若干的方法&#xff0c;可以更松散的…

Idea 插件開發: Swing Designer設計器創建的組件全部為空問題記錄

問題現象 通過Swing 設計器創建的對象, Swing組件全部是空的, 導致ToolWindowFactory工廠的實現類調用時候出現了空指針異常 如下方式創建的 問題分析 問題出現時候, 同時給我生成了一個createUIComponents的私有方法, 由于個人當時理解有誤, 把他當成了初始化方法, 在里面…

Oracle高可用一家老小全在這里

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈嘍&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人稱jeames007&#xff0c;10余年DBA及大數據工作經驗 一位上進心十足的【大數據領域博主】&#xff01;&#x1f61c;&am…

用Java實現一對一聊天

目錄 服務端 客戶端 服務端 package 一對一用戶; import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; imp…

s3-dist-cp 介紹教程示例使用方法

s3-dist-cp 是 AWS EMR 內置的用于 S3 和 HDFS 之間文件拷貝的專用工具,與 Hadoop 的 distcp 類似,也是通過 Map-Reduce 作業的方式實現分布式的文件復制(distcp 就是 distributed copy 分布式拷貝的意思)。 s3-dist-cp 并不是一個簡單的在 S3 和 HDFS 之間拷貝文件的工具…

SpringBoot中MyBatis-Flex的集成和使用

一、MyBatis-Flex 是什么? MyBatis-Flex是一個基于MyBatis的數據訪問框架&#xff0c;專門為Flex應用程序而設計的。它提供了一種靈活而高效的方式來處理Flex應用程序中的數據訪問&#xff0c;可以輕松地連接到各種數據源&#xff0c;并提供了一些方便的工具和功能&#xff0c…

虛擬機和主機間復制粘貼

文章目錄 前言一、版本介紹二、安裝工具1.確認配置2.安裝工具3.重啟 總結 前言 在Windows中使用虛擬機&#xff0c;可以很方便地linux&#xff0c;就像是在本地操作服務器一樣。 一、版本介紹 虛擬機&#xff1a;VMware 15 操作系統&#xff1a;CentOS 7 二、安裝工具 1.確…

delphi android打開外部文件,報錯android.os.FileUriExposedException解決方法

Android 7.0強制啟用了被稱作 StrictMode的策略&#xff0c;帶來的影響就是你的App對外無法暴露file://類型的URI了。 如果你使用Intent攜帶這樣的URI去打開外部App(比如&#xff1a;打開系統相機拍照)&#xff0c;那么會拋出FileUriExposedException異常。 Delphi 為Android…

C++(14):通過tuple在構造對象時注入類型不確定的對象/插件

有的時候我們需要在構建對象時注入一系列類型不確定的對象或插件,怎么才能實現呢? #include <iostream> #include <string> #include <tuple>using namespace std;class A{ public:A(int a) : m_a(a){cout<<"construct A:"<<m_a<…

windows系統安裝RocketMQ_dashboard

1.下載源碼 按照官網說明下載源碼 官網 官網文檔 2.源碼安裝 2.1.① 編譯rocketmq-dashboard 注釋掉報錯的maven插件frontend-maven-plugin、maven-antrun-plugin mvn clean package -Dmaven.test.skiptrue2.2.② 運行rocketmq-dashboard java -jar target/rocketmq-…

Qt基礎-connect函數詳解

本文詳解Qt的connect函數用法。 目錄 定義 形式 函數原型 實例說明 定義 Qt中的信號槽為核心內容,一定要熟練掌握。鏈接信號使用connect函數。 QObject::connect函數,顧名思義,鏈接函數,作用是鏈接信號(signal)和槽(

tamcat亂碼

學習springmvc時tamcat亂碼 ①、啟動時tomcat控制臺亂碼 解決方法是&#xff1a;1、先把idea設置里的默認字節碼改成utf-8 ? 2、把idea顯示編碼改成utf-8&#xff0c;在末尾加上&#xff08; -Dfile.encodingUTF-8&#xff09; ? 3、最后重啟idea 加上這個 -Dfile.encodingU…

CSS基礎概念之選擇器類型

CSS選擇器類型 選擇器表示元素在樹結構中的特定模式。選擇器(selector)術語指的是&#xff0c;簡單選擇器(simple selector)&#xff0c;復合選擇器(compound selector)&#xff0c;復雜選擇器(complex selector)&#xff0c;或者選擇器列表(selector list)。選擇器的主題是任…

【軟考中級——軟件設計師】備戰經驗 筆記總結分享

考試成績 我第一次備考是在2022 然后那時候取消了這次是第二次 靠前我一個月復習的看了以前的筆記 然后刷了七八道歷年題目學習資料推薦 &#xff1a;zst——2021 b站鏈接自薦一下我的筆記 &#xff1a; 軟考筆記專欄 視頻確實很長 &#xff0c; 我的建議就是先看筆記 然后不會…