今天來跟大家聊一聊Spring的9大核心基礎功能。
其實最近有小夥伴私信問我怎麼不寫文章了,催更來了
其實我不是不寫,而是一直在寫這篇文章,隻不過令我沒想到的是,從前期的選題、準備、翻源碼、動手到寫完,前後跨度接近一個月的時間,花了好幾個周末,寫了三萬字,最終才算完成。
是以如果本篇文章對你有所幫助,還請多多點贊、轉發、在看,非常感謝!!
話不多說,先上目錄
友情提示,本文過長,建議收藏,嘿嘿嘿!
資源管理
資源管理是Spring的一個核心的基礎功能,不過在說Spring的資源管理之前,先來簡單說一下Java中的資源管理。
Java資源管理
Java中的資源管理主要是通過java.net.URL來實作的,通過URL的openConnection方法可以對資源打開一個連接配接,通過這個連接配接讀取資源的内容。
資源不僅僅指的是網絡資源,還可以是本地檔案、一個jar包等等。
1、來個Demo
舉個例子,比如你想到通路www.baidu.com這個百度首頁網絡資源,那麼此時就可以這麼寫
public class JavaResourceDemo {
public static void main(String[] args) throws IOException {
//建構URL 指定資源的協定為http協定
URL url = new URL("http://www.baidu.com");
//打開資源連接配接
URLConnection urlConnection = url.openConnection();
//擷取資源輸入流
InputStream inputStream = urlConnection.getInputStream();
//通過hutool工具類讀取流中資料
String content = IoUtil.read(new InputStreamReader(inputStream));
System.out.println(content);
}
}
解釋一下上面代碼的意思:
- 首先建構一個URL,指定資源的通路協定為http協定
- 通過URL打開一個資源通路連接配接,然後擷取一個輸入流,讀取内容
運作結果
成功讀取到百度首頁的資料。
當然,也可以通過URL通路本地檔案資源,在建立URL的時候隻需要指定協定類型為file://和檔案的路徑就行了
URL url = new URL("file://" + "檔案的路徑");
這種方式這裡我就不示範了。
其實這種方式實際上最終也是通過FileInputStream來讀取檔案資料的,不信你可以自己debug試試。
2、原理
每種協定的URL資源都需要一個對應的一個URLStreamHandler來處理。
URLStreamHandler
比如說,http://協定有對應的URLStreamHandler的實作,file://協定的有對應的URLStreamHandler的實作。
Java除了支援http://和file://協定之外,還支援其它的協定,如下圖所示:
對于的URLStreamHandler如下圖所示
當在建構URL的時候,會去解析資源的通路協定,根據通路協定找到對應的URLStreamHandler的實作。
當然,除了Java本身支援的協定之外,我們還可以自己去擴充這個協定,大緻隻需要兩步即可:
- 實作URLConnection,可以通過這個連接配接讀取資源的内容
- 實作URLStreamHandler,通過URLStreamHandler可以擷取到URLConnection
不過需要注意的是,URLStreamHandler的實作需要放在sun.net.www.protocol.協定名稱包下,類名必須是Handler,這也是為什麼截圖中的實作類名都叫Handler的原因。
當然如果不放在指定的包下也可以,但是需要實作java.net.URLStreamHandlerFactory接口。
對于擴充我就不示範了,如果你感興趣可以自行谷歌一下。
Spring資源管理
雖然Java提供了标準的資源管理方式,但是Spring并沒有用,而是自己搞了一套資源管理方式。
1、資源抽象
在Spring中,資源大緻被抽象為兩個接口
- Resource:可讀資源,可以擷取到資源的輸入流
- WritableResource:讀寫資源,除了資源輸入流之外,還可以擷取到資源的輸出流
Resource
Resource接口繼承了InputStreamSource接口,而InputStreamSource接口可以擷取定義了擷取輸入流的方法
WritableResource
WritableResource繼承了Resource接口,可以擷取到資源的輸出流,因為有的資源不僅可讀,還可寫,就比如一些本地檔案的資源,往往都是可讀可寫的
Resource的實作很多,這裡我舉幾個常見的:
- FileSystemResource:讀取檔案系統的資源
- UrlResource:前面提到的Java的标準資源管理的封裝,底層就是通過URL來通路資源
- ClassPathResource:讀取classpath路徑下的資源
- ByteArrayResource:讀取靜态位元組數組的資料
比如,想要通過Spring的資源管理方式來通路前面提到百度首頁網絡資源,就可以這麼寫
//建構資源
Resource resource = new UrlResource("http://www.baidu.com");
//擷取資源輸入流
InputStream inputStream = resource.getInputStream();
如果是一個本地檔案資源,那麼除了可以使用UrlResource,也可以使用FileSystemResource,都是可以的。
2、資源加載
雖然Resource有很多實作,但是在實際使用中,可能無法判斷使用具體的哪個實作,是以Spring提供了ResourceLoader資源加載器來根據資源的類型來加載資源。
ResourceLoader
通過getResource方法,傳入一個路徑就可以加載到對應的資源,而這個路徑不一定是本地檔案,可以是任何可加載的路徑。
ResourceLoader有個唯一的實作DefaultResourceLoader
比如對于上面的例子,就可以通過ResourceLoader來加載資源,而不用直接new具體的實作了
//建立ResourceLoader
ResourceLoader resourceLoader = new DefaultResourceLoader();
//擷取資源
Resource resource = resourceLoader.getResource("http://www.baidu.com");
除了ResourceLoader之外,還有一個ResourcePatternResolver可以加載資源
ResourcePatternResolver繼承了ResourceLoader
通過ResourcePatternResolver提供的方法可以看出,他可以加載多個資源,支援使用通配符的方式,比如classpath*:,就可以加載所有classpath的資源。
ResourcePatternResolver隻有一個實作PathMatchingResourcePatternResolver
PathMatchingResourcePatternResolver
3、小結
到這就講完了Spring的資源管理,這裡總結一下本節大緻的内容
Java的标準資源管理:
- URL
- URLStreamHandler
Spring的資源管理:
- 資源抽象:Resource 、WritableResource
- 資源加載:ResourceLoader 、ResourcePatternResolver
Spring的資源管理在Spring中用的很多,比如在SpringBoot中,application.yml的檔案就是通過ResourceLoader加載成Resource,之後再讀取檔案的内容的。
環境
上一節末尾舉的例子中提到,SpringBoot配置檔案是通過ResourceLoader來加載配置檔案,讀取檔案的配置内容
那麼當配置檔案都加載完成之後,這個配置應該存到哪裡,怎麼能夠讀到呢?
這就引出了Spring架構中的一個關鍵概念,環境,它其實就是用于管理應用程式配置的。
1、Environment
Environment就是環境抽象出來的接口
Environment繼承PropertyResolver
public interface PropertyResolver {
boolean containsProperty(String key);
String getProperty(String key);
<T> T getProperty(String key, Class<T> targetType);
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
String resolvePlaceholders(String text);
}
如上是PropertyResolver提供的部分方法,這裡簡單說一下上面方法的作用
- getProperty(String key),很明顯是通過配置的key擷取對應的value值
- getProperty(String key, Class<T> targetType),這是擷取配置,并轉換成對應的類型,比如你擷取的是個字元串的"true",這裡就可以給你轉換成布爾值的true,具體的底層實作留到下一節講
- resolvePlaceholders(String text),這類方法可以處理${...}占位符,也就是先取出${...}占位符中的key,然後再通過key擷取到值
是以Environment主要有一下幾種功能:
- 根據key擷取配置
- 擷取到指定類型的配置
- 處理占位符
來個demo
先在application.yml的配置檔案中加入配置
測試代碼如下
@SpringBootApplication
public class EnvironmentDemo {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(EnvironmentDemo.class, args);
//從ApplicationContext中擷取到ConfigurableEnvironment
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//擷取name屬性對應的值
String name = environment.getProperty("name");
System.out.println("name = " + name);
}
}
啟動應用,擷取到ConfigurableEnvironment對象,再擷取到值
ConfigurableEnvironment是Environment子接口,通過命名也可以知道,他可以對Environment進行一些功能的配置。
運作結果:
name = 三友的java日記
2、配置屬性源PropertySource
PropertySource是真正存配置的地方,屬于配置的來源,它提供了一個統一的通路接口,使得應用程式可以以統一的方式擷取配置擷取到屬性。
PropertySource
來個簡單demo
public class PropertySourceDemo {
public static void main(String[] args) {
Map<String, Object> source = new HashMap<>();
source.put("name", "三友的java日記");
PropertySource<Map<String, Object>> propertySource = new MapPropertySource("myPropertySource", source);
Object name = propertySource.getProperty("name");
System.out.println("name = " + name);
}
}
簡單說一下上面代碼的意思
- 首先建立了一個map,就是配置來源,往裡面添加了一個配置key-value
- 建立了一個PropertySource,使用的實作是MapPropertySource,需要傳入配置map,是以最終擷取到屬性不用想就知道是從map中擷取的
最後成擷取到屬性
除了MapPropertySource之外,還有非常多的實作
PropertySource實作
比如CommandLinePropertySource,它其實就封裝了通過指令啟動時的傳遞的配置參數
既然PropertySource才是真正存儲配置的地方,那麼Environment擷取到的配置真正也就是從PropertySource擷取的,并且他們其實是一對多的關系
其實很好了解一對多的關系,因為一個應用程式的配置可能來源很多地方,比如在SpringBoot環境底下,除了我們自定義的配置外,還有比如系統環境配置等等,這些都可以通過Environment擷取到
當從Environment中擷取配置的時候,會去周遊所有的PropertySource,一旦找到配置key對應的值,就會傳回
是以,如果有多個PropertySource都含有同一個配置項的話,也就是配置key相同,那麼擷取到的配置是從排在前面的PropertySource的擷取的
這就是為什麼,當你在配置檔案配置username屬性時擷取到的卻是系統變量username對應的值,因為系統的PropertySource排在配置檔案對應的PropertySource之前
3、SpringBoot是如何解析配置檔案
SpringBoot是通過PropertySourceLoader來解析配置檔案的
load方法的第二個參數就是我們前面提到的資源接口Resource
通過Resource就可以擷取到配置檔案的輸入流,之後就可以讀取到配置檔案的内容,再把配置檔案解析成多個PropertySource,之後把PropertySource放入到Environment中,這樣我們就可以通過Environment擷取到配置檔案的内容了。
PropertySourceLoader預設有兩個實作,分别用來解析properties和yml格式的配置檔案
此時,上面的圖就可以優化成這樣
類型轉換
在上一節介紹Environment時提到了它的getProperty(String key, Class<T> targetType)可以将配置的字元串轉換成對應的類型,那麼他是如何轉換的呢?
這就跟本文要講的Spring類型轉換機制有關了
1、類型轉換API
Spring類型轉換主要涉及到以下幾個api:
- PropertyEditor
- Converter
- GenericConverter
- ConversionService
- TypeConverter
接下來我會來詳細介紹這幾個api的原理和他們之間的關系。
1.1、PropertyEditor
PropertyEditor并不是Spring提供的api,而是JDK提供的api,他的主要作用其實就是将String類型的字元串轉換成Java對象屬性值。
public interface PropertyEditor {
void setValue(Object value);
Object getValue();
String getAsText();
void setAsText(String text) throws java.lang.IllegalArgumentException;
}
就拿項目中常用的@Value來舉例子,當我們通過@Value注解的方式将配置注入到字段時,大緻步驟如下圖所示:
- 取出@Value配置的key
- 根據@Value配置的key調用Environment的resolvePlaceholders(String text)方法,解析占位符,找到配置檔案中對應的值
- 調用PropertyEditor将對應的值轉換成注入的屬性字段類型,比如注入的字段類型是數字,那麼就會将字元串轉換成數字
在轉換的過程中,Spring會先調用PropertyEditor的setAsText方法将字元串傳入,然後再調用getValue方法擷取轉換後的值。
Spring提供了很多PropertyEditor的實作,可以實作字元串到多種類型的轉換
在這麼多實作中,有一個跟我們前面提到的Resource有關的實作ResourceEditor,它是将字元串轉換成Resource對象
ResourceEditor
也就是說,可以直接通過@Value的方式直接注入一個Resource對象,就像下面這樣
@Value("http://www.baidu.com")
private Resource resource;
其實歸根到底,底層也是通過ResourceLoader來加載的,這個結論是不變的。
是以,如果你想知道@Value到底支援注入哪些字段類型的時候,看看PropertyEditor的實作就可以了,當然如果Spring自帶的都不滿足你的要求,你可以自己實作PropertyEditor,比如把String轉成Date類型,Spring就不支援。
1.2、Converter
由于PropertyEditor局限于字元串的轉換,是以Spring在後續的版本中提供了叫Converter的接口,他也用于類型轉換的,相比于PropertyEditor更加靈活、通用
Converter
Converter是個接口,泛型S是被轉換的對象類型,泛型T是需要被轉成的類型。
同樣地,Spring也提供了很多Converter的實作
這些主要包括日期類型的轉換和String類型轉換成其它的類型
1.3、GenericConverter
GenericConverter也是類型轉換的接口
這個接口的主要作用是可以處理帶有泛型類型的轉換,主要的就是面向集合數組轉換操作,從Spring預設提供的實作就可以看出
那Converter跟GenericConverter有什麼關系呢?
這裡我舉個例子,假設現在需要将将源集合Collection<String>轉換成目标集合Collection<Date>
假設現在有個String轉換成Date類型的Converter,咱就叫StringToDateConverter,那麼整個轉換過程如下:
- 首先會找到GenericConverter的一個實作CollectionToCollectionConverter,從名字也可以看出來,是将一個幾個轉換成另一個集合
- 然後周遊源集合Collection<String>,取出元素
- 根據目标集合泛型Date,找到StringToDateConverter,将String轉換成Date,将轉換的Date存到一個新的集合
- 傳回這個新的集合,這樣就實作了集合到集合的轉換
是以通過這就可以看出Converter和GenericConverter其實是依賴關系
1.4、ConversionService
對于我們使用者來說,不論是Converter還是GenericConverter,其實都是類型轉換的,并且類型轉換的實作也很多,是以Spring為了友善我們使用Converter還是GenericConverter,提供了一個門面接口ConversionService
ConversionService
我們可以直接通過ConversionService來進行類型轉換,而不需要面向具體的Converter或者是GenericConverter
ConversionService有一個基本的實作GenericConversionService
GenericConversionService
同時GenericConversionService還實作了ConverterRegistry的接口
ConverterRegistry提供了對Converter和GenericConverter進行增删改查的方法。
ConverterRegistry
這樣就可以往ConversionService中添加Converter或者是GenericConverter了,因為最終還是通過Converter和GenericConverter來實作轉換的
但是我們一般不直接用GenericConversionService,而是用DefaultConversionService或者是ApplicationConversionService(SpringBoot環境底下使用)
因為DefaultConversionService和ApplicationConversionService在建立的時候,會添加很多Spring自帶的Converter和GenericConverter,就不需要我們手動添加了。
1.5、TypeConverter
TypeConverter其實也是算是一個門面接口,他也定義了轉換方法
他是将PropertyEditor和ConversionService進行整合,友善我們同時使用PropertyEditor和ConversionService
convertIfNecessary方法會去調用PropertyEditor和ConversionService進行類型轉換,值得注意的是,優先使用PropertyEditor進行轉換,如果沒有找到對應的PropertyEditor,會使用ConversionService進行轉換
TypeConverter有個簡單的實作SimpleTypeConverter,這裡來個簡單的demo
public class TypeConverterDemo {
public static void main(String[] args) {
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
//設定ConversionService
typeConverter.setConversionService(DefaultConversionService.getSharedInstance());
//将字元串"true"轉換成Boolean類型的true
Boolean b = typeConverter.convertIfNecessary("true", Boolean.class);
System.out.println("b = " + b);
}
}
這裡需要注意,ConversionService需要我們手動設定,但是PropertyEditor不需要,因為SimpleTypeConverter預設會去添加PropertyEditor的實作。
小結
到這就講完了類型轉換的常見的幾個api,這裡再簡單總結一下:
- PropertyEditor:String轉換成目标類型
- Converter:用于一個類型轉換成另一個類型
- GenericConverter:用于處理泛型的轉換,主要用于集合
- ConversionService:門面接口,内部會調用Converter和GenericConverter
- TypeConverter:門面接口,内部會調用PropertyEditor和ConversionService
畫張圖來總結他們之間的關系
前面在舉@Value的例子時說,類型轉換是根據PropertyEditor來的,其實隻說了一半,因為底層實際上是根據TypeConverter來轉換的,是以@Value類型轉換時也能使用ConversionService類轉換,是以那張圖實際上應該這麼畫才算對
2、Environment中到底是如何進行類型轉換的?
這裡我們回到開頭提到的話題,Environment中到底是如何進行類型轉換的,讓我們看看Environment類的接口體系
Environment有個子接口ConfigurableEnvironment中,前面也提到過
它繼承了ConfigurablePropertyResolver接口
而ConfigurablePropertyResolver有一個setConversionService方法
是以從這可以看出,Environment底層實際上是通過ConversionService實作類型轉換的
這其實也就造成了一個問題,因為ConversionService和PropertyEditor屬于并列關系,那麼就會導緻Environment無法使用PropertyEditor來進行類型轉換,也就會喪失部分Spring提供的類型轉換功能,就比如無法通過Environment将String轉換成Resource對象,因為Spring沒有實作String轉換成Resource的Converter
當然你可以自己實作一個String轉換成Resource的Converter,然後添加到ConversionService,之後Environment就支援String轉換成Resource了。
資料綁定
上一節我們講了類型轉換,而既然提到了類型轉換,那麼就不得不提到資料綁定了,他們是密不可分的,因為在資料綁定時,往往都會伴随着類型轉換,
資料綁定的意思就是将一些配置屬性跟我們的Bean對象的屬性進行綁定。
不知你是否記得,在遠古的ssm時代,我們一般通過xml方式聲明Bean的時候,可以通過<property/>來設定Bean的屬性
<bean class="com.sanyou.spring.core.basic.User">
<property name="username" value="三友的java日記"/>
</bean>
@Data
public class User {
private String username;
}
然後Spring在建立User的過程中,就會給username屬性設定為三友的java日記。
這就是資料綁定,将三友的java日記綁定到username這個屬性上。
資料綁定的核心api主要包括以下幾個:
- PropertyValues
- BeanWrapper
- DataBinder
1、PropertyValues
這裡我們先來講一下PropertyValue(注意沒有s)
顧明思議,PropertyValue就是就是封裝了屬性名和對應的屬性值,它就是資料綁定時屬性值的來源。
以前面的提到的xml建立Bean為例,Spring在啟動的時候會去解析xml中的<property/>标簽,然後将name和value封裝成PropertyValue
當建立User這個Bean的時候,到了屬性綁定的階段的時候,就會取出PropertyValue,設定到User的username屬性上。
而PropertyValues,比PropertyValue多了一個s,也就是複數的意思,是以其實PropertyValues本質上就是PropertyValue的一個集合
因為一個Bean可能有多個屬性配置,是以就用PropertyValues來儲存。
2、BeanWrapper
BeanWrapper其實就資料綁定的核心api了,因為在Spring中涉及到資料綁定都是通過BeanWrapper來完成的,比如前面提到的Bean的屬性的綁定,就是通過BeanWrapper來的
BeanWrapper是一個接口,他有一個唯一的實作BeanWrapperImpl。
先來個demo
public class BeanWrapperDemo {
public static void main(String[] args) {
//建立user對象
User user = new User();
//建立BeanWrapper對象,把需要進行屬性綁定的user對象放進去
BeanWrapper beanWrapper = new BeanWrapperImpl(user);
//進行資料綁定,将三友的java日記這個屬性值指派到username這個屬性上
beanWrapper.setPropertyValue(new PropertyValue("username", "三友的java日記"));
System.out.println("username = " + user.getUsername());
}
}
結果
成功擷取到,說明設定成功
BeanWrapperImpl也間接實作了TypeConverter接口
當然底層還是通過前面提到的ConversionService和PropertyEditor實作的
是以當配置的類型跟屬性的類型不同時,就可以對配置的類型進行轉換,然後再綁定到屬性上
這裡簡單說一下資料綁定和@Value的異同,因為這兩者看起來好像是一樣的,但實際還是有點差別的
相同點:
兩者都會涉及到類型轉換,@Value和資料綁定都會将值轉換成目标屬性對應的類型,并且都是通過TypeConverter來轉換的
不同點:
1、發生時機不同,@Value比資料綁定更早,當@Value都注入完成之後才會發生資料綁定(屬性指派)
2、屬性指派方式不同,@Value是通過反射來的,而是資料綁定是通過setter方法來的,如果沒有setter方法,屬性是沒辦法綁定的
3、DataBinder
DataBinder也是用來進行資料綁定的,它的底層也是間接通過BeanWrapper來實作的資料綁定的
但是他相比于BeanWrapper多了一些功能,比如在資料綁定之後,可以對資料校驗,比如可以校驗字段的長度等等
說到資料校驗,是不是想到了SpringMVC中的參數校驗,通過@Valid配合一些諸如@NotBlank、@NotNull等注解,實作優雅的參數校驗。
其實SpringMVC的參數校驗就是通過DataBinder來的,是以DataBinder其實在SpringMVC中用的比較多,但是在Spring中确用的很少。
如果你有興趣,可以翻一下SpringMVC中關于請求參數處理的HandlerMethodArgumentResolver的實作,裡面有的實作會用到DataBinder(WebDataBinder)來進行資料請求參數跟實體類的資料綁定、類型轉換、資料校驗等等。
不知道你有沒有注意過,平時寫接口的時候,前端傳來的參數String類型的時間字元串無法通過Spring架構本身轉換成Date類型,有部分原因就是前面提到的Spring沒有相關的Converter實作
總的來說,資料綁定在xml配置和SpringMVC中用的比較多的,并且資料綁定也是Spring Bean生命周期中一個很重要的環節。
泛型處理
Spring為了友善操作和處理泛型類型,提供了一個強大的工具類——ResolvableType。
泛型處理其實是一塊相對獨立的東西,因為它就隻是一個工具類,隻還不過這個工具類在Spring中卻是無處不在!
ResolvableType提供了有一套靈活的API,可以在運作時擷取和處理泛型類型等資訊。
ResolvableType
接下來就通過一個案例,來看一看如何通過ResolvableType快速簡單的擷取到泛型的
首先我聲明了一個MyMap類,繼承HashMap,第一個泛型參數是Integer類型,第二個泛型參數是List類型,List的泛型參數又是String
public class MyMap extends HashMap<Integer, List<String>> {
}
接下來就來示範一下如何擷取到HashMap的泛型參數以及List的泛型參數
第一步,先來通過ResolvableType#forClass方法建立一個MyMap類型對應的ResolvableType
//建立MyMap對應的ResolvableType
ResolvableType myMapType = ResolvableType.forClass(MyMap.class);
因為泛型參數是在父類HashMap中,是以我們得擷取到父類HashMap對應的ResolvableType,通過ResolvableType#getSuperType()方法擷取
//擷取父類HashMap對應的ResolvableType
ResolvableType hashMapType = myMapType.getSuperType();
接下來需要擷取HashMap的泛型參數對應的ResolvableType類型,可以通過ResolvableType#getGeneric(int... indexes)就可以擷取指定位置的泛型參數ResolvableType,方法參數就是指第幾個位置的泛型參數,從0開始
比如擷取第一個位置的對應的ResolvableType類型
//擷取第一個泛型參數對應的ResolvableType
ResolvableType firstGenericType = hashMapType.getGeneric(0);
現在有了第一個泛型參數的ResolvableType類型,隻需要通過ResolvableType#resolve()方法就可以擷取到ResolvableType類型對應的class類型,這樣就可以擷取到一個泛型參數的class類型
//擷取第一個泛型參數對應的ResolvableType對應的class類型,也就是Integer的class類型
Class<?> firstGenericClass = firstGenericType.resolve();
如果你想擷取到HashMap第二個泛型參數的泛型類型,也就是List泛型類型就可以這麼寫
//HashMap第二個泛型參數的對應的ResolvableType,也就是List<String>
ResolvableType secondGenericType = hashMapType.getGeneric(1);
//HashMap第二個泛型參數List<String>的第一個泛型類型String對應的ResolvableType
ResolvableType secondFirstGenericType = secondGenericType.getGeneric(0);
//這樣就擷取到了List<String>的泛型類型String
Class<?> secondFirstGenericClass = secondFirstGenericType.resolve();
從上面的示範下來可以發現,其實每變化一步,其實就是擷取對應泛型或者是父類等等對應的ResolvableType,父類或者是泛型參數又可能有泛型之類的,隻需要一步一步擷取就可以了,當需要擷取到具體的class類型的時候,通過ResolvableType#resolve()方法就行了。
除了上面提到的通過ResolvableType#forClass方法建立ResolvableType之外,還可以通過一下幾個方法建立:
- forField(Field field):擷取字段類型對應的ResolvableType
- forMethodReturnType(Method method):擷取方法傳回值類型對應的ResolvableType
- forMethodParameter(Method method, int parameterIndex):擷取方法某個位置方法參數對應的ResolvableType
- forConstructorParameter(Constructor<?> constructor, int parameterIndex):擷取構造方法某個構造參數對應的ResolvableType
通過上面解釋可以看出,對于一個類方法參數,方法傳回值,字段等等都可以擷取到對應的ResolvableType
國際化
國際化(Internationalization,簡稱i18n)也是Spring提供的一個核心功能,它其實也是一塊相對獨立的功能。
所謂的國際化,其實了解簡單點就是對于不同的地區國家,輸出的文本内容語言不同。
Spring的國際化其實主要是依賴Java中的國際化和文本處理方式。
1、Java中的國際化
Locale
Locale是Java提供的一個類,它可以用來辨別不同的語言和地區,如en_US表示美國英語,zh_CN表示中國大陸中文等。
目前Java已經窮舉了很多國家的地區Locale。
我們可以使用Locale類擷取系統預設的Locale,也可以手動設定Locale,以适應不同的語言環境。
ResourceBundle
ResourceBundle是一個加載本地資源的一個類,他可以根據傳入的Locale不同,加載不同的資源。
來個demo
首先準備資源檔案,資源檔案通常是.properties檔案,檔案名命名規則如下:
basename_lang_country.properties
basename無所謂,叫什麼都可以,而lang和country是從Locale中擷取的。
舉個例子,我們看看英語地區的Locale
從上圖可以看出,英語Locale的lang為en,country為空字元串,那麼此時英語地區對應資源檔案就可以命名為:basename_en.properties,由于country為空字元串,可以省略
中國大陸Locale如下圖
此時檔案就可以命為:basename_zh_CN.properties
好了,現在既然知道了命名規則,我們就建立兩個檔案,basename就叫message,一個英語,一個中文,放在classpath路徑下
中文資源檔案:message_zh_CN.properties,内容為:
name=三友的java日記
英文資源檔案:message_en.properties,内容為:
name=sanyou's java diary
有了檔案之後,就可以通過ResourceBundle#getBundle(String baseName,Locale locale)方法來擷取擷取ResourceBundle
- 第一個參數baseName就是我們的檔案名中的basename,對于我們的demo來說,就是message
- 第二個參數就是地區,根據地區的不同加載不同地區的檔案
測試一下
public class ResourceBundleDemo {
public static void main(String[] args) {
//擷取ResourceBundle,第一個參數baseName就是我們的檔案名稱,第二個參數就是地區
ResourceBundle chineseResourceBundle = ResourceBundle.getBundle("message", Locale.SIMPLIFIED_CHINESE);
//根據name鍵取值
String chineseName = chineseResourceBundle.getString("name");
System.out.println("chineseName = " + chineseName);
ResourceBundle englishResourceBundle = ResourceBundle.getBundle("message", Locale.ENGLISH);
String englishName = englishResourceBundle.getString("name");
System.out.println("englishName = " + englishName);
}
}
運作結果
其實運作結果可以看出,其實是成功擷取了,隻不過中文亂碼了,這主要是因為ResourceBundle底層其實編碼是ISO-8859-1,是以會導緻亂碼。
解決辦法最簡單就是把中文用Java Unicode序列來表示,之後就可以讀出中文了了,比如三友的java日記用Java Unicode序清單示為\u4e09\u53cb\u7684java\u65e5\u8bb0
除了這種方式之外,其實還可以繼承ResourceBundle内部一個Control類
Control
重寫newBundle方法
newBundle
newBundle是建立ResourceBundle對應核心方法,重寫的時候你就可以随心所欲讓它支援其它編碼方式。
有了新的Control之後,擷取ResourceBundle時隻需要通過ResourceBundle#getBundle(String baseName, Locale targetLocale,Control control)方法指定Control就可以了。
Spring實際上就是通過這種方式擴充,支援不同編碼的,後面也有提到。
MessageFormat
MessageFormat顧明思議就是把消息格式化。它可以接收一條包含占位符的消息模闆,并根據提供的參數替換占位符,生成最終的消息。
MessageFormat對于将動态值插入到消息中非常有用,如歡迎消息、錯誤消息等。
先來個Demo
public class MessageFormatDemo {
public static void main(String[] args) {
String message = MessageFormat.format("你好:{0}", "張三");
System.out.println("message = " + message);
}
}
解釋一下上面這段代碼:
- 你好:{0}其實就是前面提到的消息的模闆,{0}就是占位符,中間的0代表消息格式化的時候将提供的參數第一個參數替換占位符的值
- 張三就是提供的參數,你可以寫很多個,但是我們的demo隻會取第一個參數,因為是{0}
是以輸出結果為:
message = 你好:張三
成功格式化消息。
2、Spring國際化
Spring提供了一個國際化接口MessageSource
MessageSource
他有一個基于ResourceBundle + MessageFormat的實作ResourceBundleMessageSource
ResourceBundleMessageSource
他的本質可以在資源檔案存儲消息的模闆,然後通過MessageFormat來替換占位符,MessageSource的getMessage方法就可以傳遞具體的參數
來個demo
現在模拟登入歡迎語句,對于不同的人肯定要有不同的名字,是以資源檔案需要存模闆,需要在不同的資源檔案加不同的模闆
中文資源檔案:message_zh_CN.properties
welcome=您好:{0}
英文資源檔案:message_en.properties
welcome=hello:{0}
占位符,就是不同人不同名字
測試代碼
public class MessageSourceDemo {
public static void main(String[] args) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//Spring已經擴充了ResourceBundle的Control,支援資源檔案的不同編碼方式,但是需要設定一下
messageSource.setDefaultEncoding("UTF-8");
//添加 baseName,就是前面提到的檔案中的basename
messageSource.addBasenames("message");
//中文,傳個中文名字
String chineseWelcome = messageSource.getMessage("welcome", new Object[]{"張三"}, Locale.SIMPLIFIED_CHINESE);
System.out.println("chineseWelcome = " + chineseWelcome);
//英文,英語國家肯定是英文名
String englishWelcome = messageSource.getMessage("welcome", new Object[]{"Bob"}, Locale.ENGLISH);
System.out.println("englishWelcome = " + englishWelcome);
}
}
運作結果
chineseWelcome = 您好:張三
englishWelcome = hello:Bob
成功根據完成不同國家資源的加載和模闆消息的格式化。
小結
這裡來簡單總結一下這一小節說的内容
- Locale:不同國家和地區的資訊封裝
- ResourceBundle:根據不同國家的Locale,加載對應的資源檔案,這個資源檔案的命名需要遵守basename_lang_country.properties命名規範
- MessageFormat:其實就是一個文本處理的方式,他可以解析模闆,根據參數替換模闆的占位符
- MessageSource:Spring提供的國際化接口,其實他底層主要是依賴Java的ResourceBundle和MessageFormat,資源檔案存儲模闆資訊,MessageFormat根據MessageSource方法的傳參替換模闆中的占位符
BeanFactory
我們知道Spring的核心就是IOC和AOP,而BeanFactory就是大名鼎鼎的IOC容器,他可以幫我們生産對象。
1、BeanFactory接口體系
BeanFactory本身是一個接口
BeanFactory
從上面的接口定義可以看出從可以從BeanFactory擷取到Bean。
他也有很多子接口,不同的子接口有着不同的功能
- ListableBeanFactory
- HierarchicalBeanFactory
- ConfigurableBeanFactory
- AutowireCapableBeanFactory
ListableBeanFactory
ListableBeanFactory
從提供的方法可以看出,提供了一些擷取集合的功能,比如有的接口可能有多個實作,通過這些方法就可以擷取這些實作對象的集合。
HierarchicalBeanFactory
HierarchicalBeanFactory
從接口定義可以看出,可以擷取到父容器,說明BeanFactory有子父容器的概念。
ConfigurableBeanFactory
ConfigurableBeanFactory
從命名可以看出,可配置BeanFactory,是以可以對BeanFactory進行配置,比如截圖中的方法,可以設定我們前面提到的類型轉換的東西,這樣在生成Bean的時候就可以類型屬性的類型轉換了。
AutowireCapableBeanFactory
提供了自動裝配Bean的實作、屬性填充、初始化、處理擷取依賴注入對象的功能。
比如@Autowired最終就會調用AutowireCapableBeanFactory#resolveDependency處理注入的依賴。
其實從這裡也可以看出,Spring在BeanFactory的接口設計上面還是基于不同的職責進行接口的劃分,其實不僅僅是在BeanFactory,前面提到的那些接口也基本符合這個原則。
2、BeanDefinition及其相關元件
BeanDefinition
BeanDefinition是Spring Bean建立環節中很重要的一個東西,它封裝了Bean建立過程中所需要的元資訊。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
//設定Bean className
void setBeanClassName(@Nullable String beanClassName);
//擷取Bean className
@Nullable
String getBeanClassName();
//設定是否是懶加載
void setLazyInit(boolean lazyInit);
//判斷是否是懶加載
boolean isLazyInit();
//判斷是否是單例
boolean isSingleton();
}
如上代碼是BeanDefinition接口的部分方法,從這方法的定義名稱可以看出,一個Bean所建立過程中所需要的一些資訊都可以從BeanDefinition中擷取,比如這個Bean的class類型,這個Bean是否是懶加載,這個Bean是否是單例的等等,因為有了這些資訊,Spring才知道要建立一個什麼樣的Bean。
讀取BeanDefinition
讀取BeanDefinition大緻分為以下幾類
- BeanDefinitionReader
- ClassPathBeanDefinitionScanner
BeanDefinitionReader
BeanDefinitionReader
BeanDefinitionReader可以通過loadBeanDefinitions(Resource resource)方法來加載BeanDefinition,方法參數就是我們前面說的資源,比如可以将Bean定義在xml檔案中,這個xml檔案就是一個資源
BeanDefinitionReader的相關實作:
- XmlBeanDefinitionReader:讀取xml配置的Bean
- PropertiesBeanDefinitionReader:讀取properties檔案配置的Bean,是的,你沒看錯,Bean可以定義在properties檔案配置中
- AnnotatedBeanDefinitionReader:讀取通過注解定義的Bean,比如@Lazy注解等等,AnnotatedBeanDefinitionReader不是BeanDefinitionReader的實作,但是作用是一樣的
ClassPathBeanDefinitionScanner
這個作用就是掃描指定包下通過@Component及其派生注解(@Service等等)注解定義的Bean,其實就是@ComponentScan注解的底層實作
ClassPathBeanDefinitionScanner這個類其實在很多其它架構中都有使用到,因為這個類可以掃描指定包下,生成BeanDefinition,對于那些需要掃描包來生成BeanDefinition來說,用的很多
比如說常見的MyBatis架構,他的注解@MapperScan可以掃描指定包下的Mapper接口,其實他也是通過繼承ClassPathBeanDefinitionScanner來掃描Mapper接口的
BeanDefinitionRegistry
這個從命名就可以看出,是BeanDefinition的注冊中心,也就是用來儲存BeanDefinition的。
提供了BeanDefinition的增删查的功能。
講到這裡,就可以用一張圖來把前面提到東西關聯起來
- 通過BeanDefinitionReader或者是ClassPathBeanDefinitionScanner為每一個Bean生成一個BeanDefinition
- BeanDefinition生成之後,添加到BeanDefinitionRegistry中
- 當從BeanFactory中擷取Bean時,會從BeanDefinitionRegistry中拿出需要建立的Bean對應的BeanDefinition,根據BeanDefinition的資訊來生成Bean
- 當生成的Bean是單例的時候,Spring會将Bean儲存到SingletonBeanRegistry中,也就是平時說的三級緩存中的第一級緩存中,以免重複建立,需要使用的時候直接從SingletonBeanRegistry中查找
3、BeanFactory核心實作
前面提到的BeanFactory體系都是一個接口,那麼BeanFactory的實作類是哪個類呢?
BeanFactory真正底層的實作類,其實就隻有一個,那就是DefaultListableBeanFactory這個類,這個類以及父類真正實作了BeanFactory及其子接口的所有的功能。
并且接口的實作上可以看出,他也實作了BeanDefinitionRegistry,也就是說,在底層的實作上,其實BeanFactory跟BeanDefinitionRegistry的實作是同一個實作類。
上面說了這麼多,來個demo
public class BeanFactoryDemo {
public static void main(String[] args) {
//建立一個BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//建立一個BeanDefinitionReader,構造參數是一個BeanDefinitionRegistry
//因為DefaultListableBeanFactory實作了BeanDefinitionRegistry,是以直接把beanFactory當做構造參數傳過去
AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(beanFactory);
//讀取目前類 BeanFactoryDemo 為一個Bean,讓Spring幫我們生成這個Bean
beanDefinitionReader.register(BeanFactoryDemo.class);
//從容器中擷取注冊的BeanFactoryDemo的Bean
BeanFactoryDemo beanFactoryDemo = beanFactory.getBean(BeanFactoryDemo.class);
System.out.println("beanFactoryDemo = " + beanFactoryDemo);
}
}
簡單說一下上面代碼的意思
- 建立一個BeanFactory,就是DefaultListableBeanFactory
- 建立一個AnnotatedBeanDefinitionReader,構造參數是一個BeanDefinitionRegistry,因為BeanDefinitionReader需要把讀出來的BeanDefinition存到BeanDefinitionRegistry中,同時因為DefaultListableBeanFactory實作了BeanDefinitionRegistry,是以直接把beanFactory當做構造參數傳過去
- 讀取目前類 BeanFactoryDemo 為一個Bean,讓Spring幫我們生成這個Bean
- 後面就是擷取列印
運作結果
成功擷取到我們注冊的Bean
總結
本節主要講了實作IOC的幾個核心的元件
BeanFactory及其接口體系:
- ListableBeanFactory
- HierarchicalBeanFactory
- ConfigurableBeanFactory
- AutowireCapableBeanFactory
BeanDefinition及其相關元件:
- BeanDefinition
- BeanDefinitionReader和ClassPathBeanDefinitionScanner:讀取資源,生成BeanDefinition
- BeanDefinitionRegistry:存儲BeanDefinition
BeanFactory核心實作:
- DefaultListableBeanFactory:IOC容器,同時實作了BeanDefinitionRegistry接口
ApplicationContext
終于講到了ApplicationContext,因為前面說的那麼多其實就是為ApplicationContext做鋪墊的
先來看看ApplicationContext的接口
你會驚訝地發現,ApplicationContext繼承的幾個接口,除了EnvironmentCapable和ApplicationEventPublisher之外,其餘都是前面說的。
EnvironmentCapable這個接口比較簡單,提供了擷取Environment的功能
EnvironmentCapable
說明了可以從ApplicationContext中擷取到Environment,是以EnvironmentCapable也算是前面說過了
至于ApplicationEventPublisher我們留到下一節說。
ApplicationContext也繼承了ListableBeanFactory和HierarchicalBeanFactory,也就說明ApplicationContext其實他也是一個BeanFactory,是以說ApplicationContext是IOC容器的說法也沒什麼毛病,但是由于他還繼承了其它接口,功能比BeanFactory多多了。
是以,ApplicationContext是一個集萬千功能為一身的接口,一旦你擷取到了ApplicationContext(可以@Autowired注入),你就可以用來擷取Bean、加載資源、擷取環境,還可以國際化一下,屬實是個王炸。
雖然ApplicationContext繼承了這些接口,但是ApplicationContext對于接口的實作是通過一種委派的方式,而真正的實作都是我們前面說的那些實作
什麼叫委派呢,咱寫一個例子你就知道了
public class MyApplicationContext implements ApplicationContext {
private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
@Override
public Resource[] getResources(String locationPattern) throws IOException {
return resourcePatternResolver.getResources(locationPattern);
}
}
如上,其實是一段僞代碼
因為ApplicationContext繼承了ResourcePatternResolver接口,是以我實作了getResources方法,但是真正的實作其實是交給變量中的PathMatchingResourcePatternResolver來實作的,這其實就是委派,不直接實作,而是交給其它真正實作了這個接口的類來處理
同理,ApplicationContext對于BeanFactory接口的實作其實最終也是交由DefaultListableBeanFactory來委派處理的。
委派這種方式在Spring内部還是用的非常多的,前面提到的某些接口在的實作上也是通過委派的方式來的
ApplicationContext有一個子接口,ConfigurableApplicationContext
從提供的方法看出,就是可以對ApplicationContext進行配置,比如設定Environment,同時也能設定parent,說明了ApplicationContext也有子父的概念
我們已經看到了很多以Configurable開頭的接口,這就是命名習慣,表示了可配置的意思,提供的都是set、add之類的方法
ApplicationContext的實作很多,但是他有一個非常重要的抽象實作AbstractApplicationContext,因為其它的實作都是繼承這個抽象實作
AbstractApplicationContext
這個類主要是實作了一些繼承的接口方法,通過委派的方式,比如對于BeanFactory接口的實作
并且AbstractApplicationContext這個類也實作了一個非常核心的refresh方法
所有的ApplicationContext在建立之後必須調用這個refresh方法之後才能使用,至于這個方法幹了哪些事,後面有機會再寫一篇文章來着重扒一扒。
事件
上一小節在說ApplicationContext繼承的接口的時候,我們留下了一個懸念,那就是ApplicationEventPublisher的作用,而ApplicationEventPublisher就跟本節要說的事件有關。
Spring事件是一種觀察者模式的實作,他的作用主要是用來解耦合的。
當發生了某件事,隻要釋出一個事件,對這個事件的監聽者(觀察者)就可以對事件進行響應或者處理。
舉個例子來說,假設發生了火災,可能需要打119、救人,那麼就可以基于事件的模型來實作,隻需要打119、救人監聽火災的發生就行了,當發生了火災,通知這些打119、救人去觸發相應的邏輯操作。
1、什麼是Spring Event 事件
Spring Event 事件就是Spring實作了這種事件模型,你隻需要基于Spring提供的API進行擴充,就可以輕易地完成事件的釋出與訂閱
Spring事件相關api主要有以下幾個:
- ApplicationEvent
- ApplicationListener
- ApplicationEventPublisher
ApplicationEvent
ApplicationEvent
事件的父類,所有具體的事件都得繼承這個類,構造方法的參數是這個事件攜帶的參數,監聽器就可以通過這個參數來進行一些業務操作。
ApplicationListener
ApplicationListener
事件監聽的接口,泛型是需要監聽的事件類型,子類需要實作onApplicationEvent,參數就是監聽的事件類型,onApplicationEvent方法的實作就代表了對事件的處理,當事件發生時,Spring會回調onApplicationEvent方法的實作,傳入釋出的事件。
ApplicationEventPublisher
ApplicationEventPublisher
上一小節留下來的接口,事件釋出器,通過publishEvent方法就可以釋出一個事件,然後就可以觸發監聽這個事件的監聽器的回調。
ApplicationContext繼承了ApplicationEventPublisher,說明隻要有ApplicationContext就可以來釋出事件了。
話不多說,上代碼
就以上面的火災為例
建立一個火災事件類
火災事件類繼承ApplicationEvent
// 火災事件
public class FireEvent extends ApplicationEvent {
public FireEvent(String source) {
super(source);
}
}
建立火災事件的監聽器
打119的火災事件的監聽器:
public class Call119FireEventListener implements ApplicationListener<FireEvent> {
@Override
public void onApplicationEvent(FireEvent event) {
System.out.println("打119");
}
}
救人的火災事件的監聽器:
public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {
@Override
public void onApplicationEvent(FireEvent event) {
System.out.println("救人");
}
}
事件和對應的監聽都有了,接下來進行測試:
public class Application {
public static void main(String[] args) {
//建立一個Spring容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 事件監聽器 注冊到容器中
applicationContext.register(Call119FireEventListener.class);
applicationContext.register(SavePersonFireEventListener.class);
applicationContext.refresh();
// 釋出着火的事件,觸發監聽
applicationContext.publishEvent(new FireEvent("着火了"));
}
}
将兩個事件注冊到Spring容器中,然後釋出FireEvent事件
運作結果:
打119
救人
控制台列印出了結果,觸發了監聽。
如果現在需要對火災進行救火,那麼隻需要去監聽FireEvent,實作救火的邏輯,注入到Spring容器中,就可以了,其餘的代碼根本不用動。
2、Spring内置的事件
Spring内置的事件很多,這裡我羅列幾個
事件類型觸發時機ContextRefreshedEvent在調用ConfigurableApplicationContext 接口中的refresh()方法時觸發ContextStartedEvent在調用ConfigurableApplicationContext的start()方法時觸發ContextStoppedEvent在調用ConfigurableApplicationContext的stop()方法時觸發ContextClosedEvent當ApplicationContext被關閉時觸發該事件,也就是調用close()方法觸發
在ApplicationContext(Spring容器)啟動的過程中,Spring會釋出這些事件,如果你需要這Spring容器啟動的某個時刻進行什麼操作,隻需要監聽對應的事件即可。
3、Spring事件的傳播特性
Spring事件的傳播是什麼意思呢?
前面提到,ApplicationContext有子父容器的概念,而Spring事件的傳播就是指當通過子容器釋出一個事件之後,不僅可以觸發在這個子容器的事件監聽器,還可以觸發在父容器的這個事件的監聽器。
上代碼
public class EventPropagateApplication {
public static void main(String[] args) {
// 建立一個父容器
AnnotationConfigApplicationContext parentApplicationContext = new AnnotationConfigApplicationContext();
//将 打119監聽器 注冊到父容器中
parentApplicationContext.register(Call119FireEventListener.class);
parentApplicationContext.refresh();
// 建立一個子容器
AnnotationConfigApplicationContext childApplicationContext = new AnnotationConfigApplicationContext();
//将 救人監聽器 注冊到子容器中
childApplicationContext.register(SavePersonFireEventListener.class);
childApplicationContext.refresh();
// 設定一下父容器
childApplicationContext.setParent(parentApplicationContext);
// 通過子容器釋出着火的事件,觸發監聽
childApplicationContext.publishEvent(new FireEvent("着火了"));
}
}
建立了兩個容器,父容器注冊了打119的監聽器,子容器注冊了救人的監聽器,然後将子父容器通過setParent關聯起來,最後通過子容器,釋出了着火的事件。
運作結果:
救人
打119
從列印的日志,的确可以看出,雖然是子容器釋出了着火的事件,但是父容器的監聽器也成功監聽了着火事件。
而這種傳播特性,從源碼中也可以看出來
事件傳播源碼
如果父容器不為空,就會通過父容器再釋出一次事件。
傳播特性的一個小坑
前面說過,在Spring容器啟動的過程,會釋出很多事件,如果你需要有相應的擴充,可以監聽這些事件。
但是,不知道你有沒有遇到過這麼一個坑,就是在SpringCloud環境下,你監聽這些Spring事件的監聽器會執行很多次,這其實就是跟傳播特性有關。
在SpringCloud環境下,為了使像FeignClient和RibbonClient這些不同服務的配置互相隔離,會為每個FeignClient或者是RibbonClient建立一個Spring容器,而這些容器都有一個公共的父容器,那就是SpringBoot項目啟動時建立的容器
假設你監聽了容器重新整理的ContextRefreshedEvent事件,那麼你自己寫的監聽器就在SpringBoot項目啟動時建立的容器中
每個服務的配置容器他也是Spring容器,啟動時也會釋出ContextRefreshedEvent,那麼由于傳播特性的關系,你的事件監聽器就會觸發執行多次
如何解決這個坑呢?
你可以進行判斷這些監聽器有沒有執行過,比如加一個判斷的标志;或者是監聽類似的事件,比如ApplicationStartedEvent事件,這種事件是在SpringBoot啟動中釋出的事件,而子容器不是SpringBoot,是以不會多次發這種事件,也就會隻執行一次。
總結
到這到這整篇文章終于寫完了,這裡再來簡單地回顧一下本文說的幾個核心功能:
- 資源管理:對資源進行統一的封裝,友善資源讀取和管理
- 環境:對容器或者是項目的配置進行管理
- 類型轉換:将一種類型轉換成另一種類型
- 資料綁定:将資料跟對象的屬性進行綁定,綁定之前涉及到類型轉換
- 泛型處理:一個操作泛型的工具類,Spring中到處可見
- 國際化:對Java的國際化進行了統一的封裝
- BeanFactory:IOC容器
- ApplicationContext:一個集萬千功能于一身的王炸接口,也可以說是IOC容器
- 事件:Spring提供的基于觀察者模式實作的解耦合利器
當然除了上面,Spring還有很多其它核心功能,就比如AOP、SpEL表達式等等,由于AOP涉及到Bean生命周期,本篇文章也沒有涉及到Bean生命周期的講解,是以這裡就不講了,後面有機會再講;至于SpEL他是Spring提供的表達式語言,主要是文法,解析文法的一些東西,這裡也不講了。
最後,我怕你文章看得過于入迷,是以再來重複一遍,如果本篇文章對你有所幫助,還請多多點贊、轉發、在看,非常感謝!!
來源:https://mp.weixin.qq.com/s/QSchk0uHNbdvlAHxJbCMuA