AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當(dāng)前位置:首頁(yè) > 科技  > 軟件

三萬(wàn)字盤點(diǎn) Spring 九大核心基礎(chǔ)功能

來(lái)源: 責(zé)編: 時(shí)間:2023-08-05 11:45:50 5318觀看
導(dǎo)讀大家好,我是三友~~今天來(lái)跟大家聊一聊Spring的9大核心基礎(chǔ)功能。話不多說(shuō),先上目錄:圖片友情提示,本文過(guò)長(zhǎng),建議收藏,嘿嘿嘿!一、資源管理資源管理是Spring的一個(gè)核心的基礎(chǔ)功能,不過(guò)在說(shuō)Spring的資源管理之前,先來(lái)簡(jiǎn)單說(shuō)一下J

大家好,我是三友~~I4828資訊網(wǎng)——每日最新資訊28at.com

今天來(lái)跟大家聊一聊Spring的9大核心基礎(chǔ)功能。I4828資訊網(wǎng)——每日最新資訊28at.com

話不多說(shuō),先上目錄:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

友情提示,本文過(guò)長(zhǎng),建議收藏,嘿嘿嘿!I4828資訊網(wǎng)——每日最新資訊28at.com

一、資源管理

資源管理是Spring的一個(gè)核心的基礎(chǔ)功能,不過(guò)在說(shuō)Spring的資源管理之前,先來(lái)簡(jiǎn)單說(shuō)一下Java中的資源管理。I4828資訊網(wǎng)——每日最新資訊28at.com

Java資源管理

Java中的資源管理主要是通過(guò)java.URL來(lái)實(shí)現(xiàn)的,通過(guò)URL的openConnection方法可以對(duì)資源打開(kāi)一個(gè)連接,通過(guò)這個(gè)連接讀取資源的內(nèi)容。I4828資訊網(wǎng)——每日最新資訊28at.com

資源不僅僅指的是網(wǎng)絡(luò)資源,還可以是本地文件、一個(gè)jar包等等。I4828資訊網(wǎng)——每日最新資訊28at.com

1、來(lái)個(gè)Demo

舉個(gè)例子,比如你想到訪問(wèn)www.baidu.com這個(gè)百度首頁(yè)網(wǎng)絡(luò)資源,那么此時(shí)就可以這么寫(xiě)。I4828資訊網(wǎng)——每日最新資訊28at.com

public class JavaResourceDemo {    public static void main(String[] args) throws IOException {        //構(gòu)建URL 指定資源的協(xié)議為http協(xié)議        URL url = new URL("http://www.baidu.com");        //打開(kāi)資源連接        URLConnection urlConnection = url.openConnection();        //獲取資源輸入流        InputStream inputStream = urlConnection.getInputStream();        //通過(guò)hutool工具類讀取流中數(shù)據(jù)        String content = IoUtil.read(new InputStreamReader(inputStream));        System.out.println(content);    }}

解釋一下上面代碼的意思:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 首先構(gòu)建一個(gè)URL,指定資源的訪問(wèn)協(xié)議為http協(xié)議
  • 通過(guò)URL打開(kāi)一個(gè)資源訪問(wèn)連接,然后獲取一個(gè)輸入流,讀取內(nèi)容

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

成功讀取到百度首頁(yè)的數(shù)據(jù)。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然,也可以通過(guò)URL訪問(wèn)本地文件資源,在創(chuàng)建URL的時(shí)候只需要指定協(xié)議類型為file://和文件的路徑就行了I4828資訊網(wǎng)——每日最新資訊28at.com

URL url = new URL("file://" + "文件的路徑");

這種方式這里我就不演示了。I4828資訊網(wǎng)——每日最新資訊28at.com

其實(shí)這種方式實(shí)際上最終也是通過(guò)FileInputStream來(lái)讀取文件數(shù)據(jù)的,不信你可以自己debug試試。I4828資訊網(wǎng)——每日最新資訊28at.com

2、原理

每種協(xié)議的URL資源都需要一個(gè)對(duì)應(yīng)的一個(gè)URLStreamHandler來(lái)處理。I4828資訊網(wǎng)——每日最新資訊28at.com

URLStreamHandlerURLStreamHandlerI4828資訊網(wǎng)——每日最新資訊28at.com

比如說(shuō),http://協(xié)議有對(duì)應(yīng)的URLStreamHandler的實(shí)現(xiàn),file://協(xié)議的有對(duì)應(yīng)的URLStreamHandler的實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

Java除了支持http://和file://協(xié)議之外,還支持其它的協(xié)議,如下圖所示:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

對(duì)于的URLStreamHandler如下圖所示:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)在構(gòu)建URL的時(shí)候,會(huì)去解析資源的訪問(wèn)協(xié)議,根據(jù)訪問(wèn)協(xié)議找到對(duì)應(yīng)的URLStreamHandler的實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然,除了Java本身支持的協(xié)議之外,我們還可以自己去擴(kuò)展這個(gè)協(xié)議,大致只需要兩步即可:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 實(shí)現(xiàn)URLConnection,可以通過(guò)這個(gè)連接讀取資源的內(nèi)容
  • 實(shí)現(xiàn)URLStreamHandler,通過(guò)URLStreamHandler可以獲取到URLConnection

不過(guò)需要注意的是,URLStreamHandler的實(shí)現(xiàn)需要放在sun.www.protocol.協(xié)議名稱包下,類名必須是Handler,這也是為什么截圖中的實(shí)現(xiàn)類名都叫Handler的原因。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然如果不放在指定的包下也可以,但是需要實(shí)現(xiàn)java.URLStreamHandlerFactory接口。I4828資訊網(wǎng)——每日最新資訊28at.com

對(duì)于擴(kuò)展我就不演示了,如果你感興趣可以自行谷歌一下。I4828資訊網(wǎng)——每日最新資訊28at.com

Spring資源管理

雖然Java提供了標(biāo)準(zhǔn)的資源管理方式,但是Spring并沒(méi)有用,而是自己搞了一套資源管理方式。I4828資訊網(wǎng)——每日最新資訊28at.com

1、資源抽象

在Spring中,資源大致被抽象為兩個(gè)接口:I4828資訊網(wǎng)——每日最新資訊28at.com

  • Resource:可讀資源,可以獲取到資源的輸入流
  • WritableResource:讀寫(xiě)資源,除了資源輸入流之外,還可以獲取到資源的輸出流
Resource

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

Resource接口繼承了InputStreamSource接口,而InputStreamSource接口可以獲取定義了獲取輸入流的方法。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

WritableResource

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

WritableResource繼承了Resource接口,可以獲取到資源的輸出流,因?yàn)橛械馁Y源不僅可讀,還可寫(xiě),就比如一些本地文件的資源,往往都是可讀可寫(xiě)的I4828資訊網(wǎng)——每日最新資訊28at.com

Resource的實(shí)現(xiàn)很多,這里我舉幾個(gè)常見(jiàn)的:I4828資訊網(wǎng)——每日最新資訊28at.com

  • FileSystemResource:讀取文件系統(tǒng)的資源
  • UrlResource:前面提到的Java的標(biāo)準(zhǔn)資源管理的封裝,底層就是通過(guò)URL來(lái)訪問(wèn)資源
  • ClassPathResource:讀取classpath路徑下的資源
  • ByteArrayResource:讀取靜態(tài)字節(jié)數(shù)組的數(shù)據(jù)

比如,想要通過(guò)Spring的資源管理方式來(lái)訪問(wèn)前面提到百度首頁(yè)網(wǎng)絡(luò)資源,就可以這么寫(xiě)I4828資訊網(wǎng)——每日最新資訊28at.com

//構(gòu)建資源Resource resource = new UrlResource("http://www.baidu.com");//獲取資源輸入流InputStream inputStream = resource.getInputStream();

如果是一個(gè)本地文件資源,那么除了可以使用UrlResource,也可以使用FileSystemResource,都是可以的。I4828資訊網(wǎng)——每日最新資訊28at.com

2、資源加載

雖然Resource有很多實(shí)現(xiàn),但是在實(shí)際使用中,可能無(wú)法判斷使用具體的哪個(gè)實(shí)現(xiàn),所以Spring提供了ResourceLoader資源加載器來(lái)根據(jù)資源的類型來(lái)加載資源。I4828資訊網(wǎng)——每日最新資訊28at.com

ResourceLoaderResourceLoaderI4828資訊網(wǎng)——每日最新資訊28at.com

通過(guò)getResource方法,傳入一個(gè)路徑就可以加載到對(duì)應(yīng)的資源,而這個(gè)路徑不一定是本地文件,可以是任何可加載的路徑。I4828資訊網(wǎng)——每日最新資訊28at.com

ResourceLoader有個(gè)唯一的實(shí)現(xiàn)DefaultResourceLoader。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

比如對(duì)于上面的例子,就可以通過(guò)ResourceLoader來(lái)加載資源,而不用直接new具體的實(shí)現(xiàn)了。I4828資訊網(wǎng)——每日最新資訊28at.com

//創(chuàng)建ResourceLoaderResourceLoader resourceLoader = new DefaultResourceLoader();//獲取資源Resource resource = resourceLoader.getResource("http://www.baidu.com");

除了ResourceLoader之外,還有一個(gè)ResourcePatternResolver可以加載資源。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

ResourcePatternResolver繼承了ResourceLoader。I4828資訊網(wǎng)——每日最新資訊28at.com

通過(guò)ResourcePatternResolver提供的方法可以看出,他可以加載多個(gè)資源,支持使用通配符的方式,比如classpath*:,就可以加載所有classpath的資源。I4828資訊網(wǎng)——每日最新資訊28at.com

ResourcePatternResolver只有一個(gè)實(shí)現(xiàn)PathMatchingResourcePatternResolver。I4828資訊網(wǎng)——每日最新資訊28at.com

PathMatchingResourcePatternResolverPathMatchingResourcePatternResolverI4828資訊網(wǎng)——每日最新資訊28at.com

3、小結(jié)

到這就講完了Spring的資源管理,這里總結(jié)一下本節(jié)大致的內(nèi)容。I4828資訊網(wǎng)——每日最新資訊28at.com

Java的標(biāo)準(zhǔn)資源管理:I4828資訊網(wǎng)——每日最新資訊28at.com

  • URL
  • URLStreamHandler

Spring的資源管理:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 資源抽象:Resource 、WritableResource
  • 資源加載:ResourceLoader 、ResourcePatternResolver

Spring的資源管理在Spring中用的很多,比如在SpringBoot中,application.yml的文件就是通過(guò)ResourceLoader加載成Resource,之后再讀取文件的內(nèi)容的。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

二、環(huán)境

上一節(jié)末尾舉的例子中提到,SpringBoot配置文件是通過(guò)ResourceLoader來(lái)加載配置文件,讀取文件的配置內(nèi)容I4828資訊網(wǎng)——每日最新資訊28at.com

那么當(dāng)配置文件都加載完成之后,這個(gè)配置應(yīng)該存到哪里,怎么能夠讀到呢?I4828資訊網(wǎng)——每日最新資訊28at.com

這就引出了Spring框架中的一個(gè)關(guān)鍵概念,環(huán)境,它其實(shí)就是用于管理應(yīng)用程序配置的。I4828資訊網(wǎng)——每日最新資訊28at.com

1、Environment

Environment就是環(huán)境抽象出來(lái)的接口。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

Environment繼承PropertyResolverI4828資訊網(wǎng)——每日最新資訊28at.com

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提供的部分方法,這里簡(jiǎn)單說(shuō)一下上面方法的作用I4828資訊網(wǎng)——每日最新資訊28at.com

  • getProperty(String key),很明顯是通過(guò)配置的key獲取對(duì)應(yīng)的value值
  • getProperty(String key, Class<T> targetType),這是獲取配置,并轉(zhuǎn)換成對(duì)應(yīng)的類型,比如你獲取的是個(gè)字符串的"true",這里就可以給你轉(zhuǎn)換成布爾值的true,具體的底層實(shí)現(xiàn)留到下一節(jié)講
  • resolvePlaceholders(String text),這類方法可以處理${...}占位符,也就是先取出${...}占位符中的key,然后再通過(guò)key獲取到值

所以Environment主要有一下幾種功能:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 根據(jù)key獲取配置
  • 獲取到指定類型的配置
  • 處理占位符

來(lái)個(gè)demoI4828資訊網(wǎng)——每日最新資訊28at.com

先在application.yml的配置文件中加入配置I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

測(cè)試代碼如下:I4828資訊網(wǎng)——每日最新資訊28at.com

@SpringBootApplicationpublic class EnvironmentDemo {    public static void main(String[] args) {        ConfigurableApplicationContext applicationContext = SpringApplication.run(EnvironmentDemo.class, args);        //從ApplicationContext中獲取到ConfigurableEnvironment        ConfigurableEnvironment environment = applicationContext.getEnvironment();        //獲取name屬性對(duì)應(yīng)的值        String name = environment.getProperty("name");        System.out.println("name = " + name);    }}

啟動(dòng)應(yīng)用,獲取到ConfigurableEnvironment對(duì)象,再獲取到值。I4828資訊網(wǎng)——每日最新資訊28at.com

ConfigurableEnvironment是Environment子接口,通過(guò)命名也可以知道,他可以對(duì)Environment進(jìn)行一些功能的配置。I4828資訊網(wǎng)——每日最新資訊28at.com

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

name = 三友的java日記

2、配置屬性源PropertySource

PropertySource是真正存配置的地方,屬于配置的來(lái)源,它提供了一個(gè)統(tǒng)一的訪問(wèn)接口,使得應(yīng)用程序可以以統(tǒng)一的方式獲取配置獲取到屬性。I4828資訊網(wǎng)——每日最新資訊28at.com

PropertySourcePropertySourceI4828資訊網(wǎng)——每日最新資訊28at.com

來(lái)個(gè)簡(jiǎn)單demo:I4828資訊網(wǎng)——每日最新資訊28at.com

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);    }}

簡(jiǎn)單說(shuō)一下上面代碼的意思I4828資訊網(wǎng)——每日最新資訊28at.com

  • 首先創(chuàng)建了一個(gè)map,就是配置來(lái)源,往里面添加了一個(gè)配置key-value
  • 創(chuàng)建了一個(gè)PropertySource,使用的實(shí)現(xiàn)是MapPropertySource,需要傳入配置map,所以最終獲取到屬性不用想就知道是從map中獲取的

最后成獲取到屬性I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

除了MapPropertySource之外,還有非常多的實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

PropertySource實(shí)現(xiàn)PropertySource實(shí)現(xiàn)I4828資訊網(wǎng)——每日最新資訊28at.com

比如CommandLinePropertySource,它其實(shí)就封裝了通過(guò)命令啟動(dòng)時(shí)的傳遞的配置參數(shù)。I4828資訊網(wǎng)——每日最新資訊28at.com

既然PropertySource才是真正存儲(chǔ)配置的地方,那么Environment獲取到的配置真正也就是從PropertySource獲取的,并且他們其實(shí)是一對(duì)多的關(guān)系。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

其實(shí)很好理解一對(duì)多的關(guān)系,因?yàn)橐粋€(gè)應(yīng)用程序的配置可能來(lái)源很多地方,比如在SpringBoot環(huán)境底下,除了我們自定義的配置外,還有比如系統(tǒng)環(huán)境配置等等,這些都可以通過(guò)Environment獲取到。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)從Environment中獲取配置的時(shí)候,會(huì)去遍歷所有的PropertySource,一旦找到配置key對(duì)應(yīng)的值,就會(huì)返回。I4828資訊網(wǎng)——每日最新資訊28at.com

所以,如果有多個(gè)PropertySource都含有同一個(gè)配置項(xiàng)的話,也就是配置key相同,那么獲取到的配置是從排在前面的PropertySource的獲取的。I4828資訊網(wǎng)——每日最新資訊28at.com

這就是為什么,當(dāng)你在配置文件配置username屬性時(shí)獲取到的卻是系統(tǒng)變量username對(duì)應(yīng)的值,因?yàn)橄到y(tǒng)的PropertySource排在配置文件對(duì)應(yīng)的PropertySource之前。I4828資訊網(wǎng)——每日最新資訊28at.com

3、SpringBoot是如何解析配置文件

SpringBoot是通過(guò)PropertySourceLoader來(lái)解析配置文件的。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

load方法的第二個(gè)參數(shù)就是我們前面提到的資源接口Resource。I4828資訊網(wǎng)——每日最新資訊28at.com

通過(guò)Resource就可以獲取到配置文件的輸入流,之后就可以讀取到配置文件的內(nèi)容,再把配置文件解析成多個(gè)PropertySource,之后把PropertySource放入到Environment中,這樣我們就可以通過(guò)Environment獲取到配置文件的內(nèi)容了。I4828資訊網(wǎng)——每日最新資訊28at.com

PropertySourceLoader默認(rèn)有兩個(gè)實(shí)現(xiàn),分別用來(lái)解析properties和yml格式的配置文件。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

此時(shí),上面的圖就可以優(yōu)化成這樣。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

三、類型轉(zhuǎn)換

在上一節(jié)介紹Environment時(shí)提到了它的getProperty(String key, Class<T> targetType)可以將配置的字符串轉(zhuǎn)換成對(duì)應(yīng)的類型,那么他是如何轉(zhuǎn)換的呢?I4828資訊網(wǎng)——每日最新資訊28at.com

這就跟本文要講的Spring類型轉(zhuǎn)換機(jī)制有關(guān)了。I4828資訊網(wǎng)——每日最新資訊28at.com

1、類型轉(zhuǎn)換API

Spring類型轉(zhuǎn)換主要涉及到以下幾個(gè)api:I4828資訊網(wǎng)——每日最新資訊28at.com

  • PropertyEditor
  • Converter
  • GenericConverter
  • ConversionService
  • TypeConverter

接下來(lái)我會(huì)來(lái)詳細(xì)介紹這幾個(gè)api的原理和他們之間的關(guān)系。I4828資訊網(wǎng)——每日最新資訊28at.com

1.1、PropertyEditor

PropertyEditor并不是Spring提供的api,而是JDK提供的api,他的主要作用其實(shí)就是將String類型的字符串轉(zhuǎn)換成Java對(duì)象屬性值。I4828資訊網(wǎng)——每日最新資訊28at.com

public interface PropertyEditor {    void setValue(Object value);    Object getValue();    String getAsText();    void setAsText(String text) throws java.lang.IllegalArgumentException;    }

就拿項(xiàng)目中常用的@Value來(lái)舉例子,當(dāng)我們通過(guò)@Value注解的方式將配置注入到字段時(shí),大致步驟如下圖所示:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

  • 取出@Value配置的key
  • 根據(jù)@Value配置的key調(diào)用Environment的resolvePlaceholders(String text)方法,解析占位符,找到配置文件中對(duì)應(yīng)的值
  • 調(diào)用PropertyEditor將對(duì)應(yīng)的值轉(zhuǎn)換成注入的屬性字段類型,比如注入的字段類型是數(shù)字,那么就會(huì)將字符串轉(zhuǎn)換成數(shù)字

在轉(zhuǎn)換的過(guò)程中,Spring會(huì)先調(diào)用PropertyEditor的setAsText方法將字符串傳入,然后再調(diào)用getValue方法獲取轉(zhuǎn)換后的值。I4828資訊網(wǎng)——每日最新資訊28at.com

Spring提供了很多PropertyEditor的實(shí)現(xiàn),可以實(shí)現(xiàn)字符串到多種類型的轉(zhuǎn)換。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

在這么多實(shí)現(xiàn)中,有一個(gè)跟我們前面提到的Resource有關(guān)的實(shí)現(xiàn)ResourceEditor,它是將字符串轉(zhuǎn)換成Resource對(duì)象。I4828資訊網(wǎng)——每日最新資訊28at.com

ResourceEditorResourceEditorI4828資訊網(wǎng)——每日最新資訊28at.com

也就是說(shuō),可以直接通過(guò)@Value的方式直接注入一個(gè)Resource對(duì)象,就像下面這樣。I4828資訊網(wǎng)——每日最新資訊28at.com

@Value("http://www.baidu.com")private Resource resource;

其實(shí)歸根到底,底層也是通過(guò)ResourceLoader來(lái)加載的,這個(gè)結(jié)論是不變的。I4828資訊網(wǎng)——每日最新資訊28at.com

所以,如果你想知道@Value到底支持注入哪些字段類型的時(shí)候,看看PropertyEditor的實(shí)現(xiàn)就可以了,當(dāng)然如果Spring自帶的都不滿足你的要求,你可以自己實(shí)現(xiàn)PropertyEditor,比如把String轉(zhuǎn)成Date類型,Spring就不支持。I4828資訊網(wǎng)——每日最新資訊28at.com

1.2、Converter

由于PropertyEditor局限于字符串的轉(zhuǎn)換,所以Spring在后續(xù)的版本中提供了叫Converter的接口,他也用于類型轉(zhuǎn)換的,相比于PropertyEditor更加靈活、通用。I4828資訊網(wǎng)——每日最新資訊28at.com

ConverterConverterI4828資訊網(wǎng)——每日最新資訊28at.com

Converter是個(gè)接口,泛型S是被轉(zhuǎn)換的對(duì)象類型,泛型T是需要被轉(zhuǎn)成的類型。I4828資訊網(wǎng)——每日最新資訊28at.com

同樣地,Spring也提供了很多Converter的實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

這些主要包括日期類型的轉(zhuǎn)換和String類型轉(zhuǎn)換成其它的類型。I4828資訊網(wǎng)——每日最新資訊28at.com

1.3、GenericConverter

GenericConverter也是類型轉(zhuǎn)換的接口。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

這個(gè)接口的主要作用是可以處理帶有泛型類型的轉(zhuǎn)換,主要的就是面向集合數(shù)組轉(zhuǎn)換操作,從Spring默認(rèn)提供的實(shí)現(xiàn)就可以看出。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

那Converter跟GenericConverter有什么關(guān)系呢?I4828資訊網(wǎng)——每日最新資訊28at.com

這里我舉個(gè)例子,假設(shè)現(xiàn)在需要將將源集合Collection<String>轉(zhuǎn)換成目標(biāo)集合Collection<Date>。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

假設(shè)現(xiàn)在有個(gè)String轉(zhuǎn)換成Date類型的Converter,咱就叫StringToDateConverter,那么整個(gè)轉(zhuǎn)換過(guò)程如下:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 首先會(huì)找到GenericConverter的一個(gè)實(shí)現(xiàn)CollectionToCollectionConverter,從名字也可以看出來(lái),是將一個(gè)幾個(gè)轉(zhuǎn)換成另一個(gè)集合
  • 然后遍歷源集合Collection<String>,取出元素
  • 根據(jù)目標(biāo)集合泛型Date,找到StringToDateConverter,將String轉(zhuǎn)換成Date,將轉(zhuǎn)換的Date存到一個(gè)新的集合
  • 返回這個(gè)新的集合,這樣就實(shí)現(xiàn)了集合到集合的轉(zhuǎn)換

所以通過(guò)這就可以看出Converter和GenericConverter其實(shí)是依賴關(guān)系I4828資訊網(wǎng)——每日最新資訊28at.com

1.4、ConversionService

對(duì)于我們使用者來(lái)說(shuō),不論是Converter還是GenericConverter,其實(shí)都是類型轉(zhuǎn)換的,并且類型轉(zhuǎn)換的實(shí)現(xiàn)也很多,所以Spring為了方便我們使用Converter還是GenericConverter,提供了一個(gè)門面接口ConversionService。I4828資訊網(wǎng)——每日最新資訊28at.com

ConversionServiceConversionServiceI4828資訊網(wǎng)——每日最新資訊28at.com

我們可以直接通過(guò)ConversionService來(lái)進(jìn)行類型轉(zhuǎn)換,而不需要面向具體的Converter或者是GenericConverter。I4828資訊網(wǎng)——每日最新資訊28at.com

ConversionService有一個(gè)基本的實(shí)現(xiàn)GenericConversionService。I4828資訊網(wǎng)——每日最新資訊28at.com

GenericConversionServiceGenericConversionServiceI4828資訊網(wǎng)——每日最新資訊28at.com

同時(shí)GenericConversionService還實(shí)現(xiàn)了ConverterRegistry的接口。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

ConverterRegistry提供了對(duì)Converter和GenericConverter進(jìn)行增刪改查的方法。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片ConverterRegistryI4828資訊網(wǎng)——每日最新資訊28at.com

這樣就可以往ConversionService中添加Converter或者是GenericConverter了,因?yàn)樽罱K還是通過(guò)Converter和GenericConverter來(lái)實(shí)現(xiàn)轉(zhuǎn)換的。I4828資訊網(wǎng)——每日最新資訊28at.com

但是我們一般不直接用GenericConversionService,而是用DefaultConversionService或者是ApplicationConversionService(SpringBoot環(huán)境底下使用)。I4828資訊網(wǎng)——每日最新資訊28at.com

因?yàn)镈efaultConversionService和ApplicationConversionService在創(chuàng)建的時(shí)候,會(huì)添加很多Spring自帶的Converter和GenericConverter,就不需要我們手動(dòng)添加了。I4828資訊網(wǎng)——每日最新資訊28at.com

1.5、TypeConverter

TypeConverter其實(shí)也是算是一個(gè)門面接口,他也定義了轉(zhuǎn)換方法。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

他是將PropertyEditor和ConversionService進(jìn)行整合,方便我們同時(shí)使用PropertyEditor和ConversionService。I4828資訊網(wǎng)——每日最新資訊28at.com

convertIfNecessary方法會(huì)去調(diào)用PropertyEditor和ConversionService進(jìn)行類型轉(zhuǎn)換,值得注意的是,優(yōu)先使用PropertyEditor進(jìn)行轉(zhuǎn)換,如果沒(méi)有找到對(duì)應(yīng)的PropertyEditor,會(huì)使用ConversionService進(jìn)行轉(zhuǎn)換。I4828資訊網(wǎng)——每日最新資訊28at.com

TypeConverter有個(gè)簡(jiǎn)單的實(shí)現(xiàn)SimpleTypeConverter,這里來(lái)個(gè)簡(jiǎn)單的demo。I4828資訊網(wǎng)——每日最新資訊28at.com

public class TypeConverterDemo {    public static void main(String[] args) {        SimpleTypeConverter typeConverter = new SimpleTypeConverter();                //設(shè)置ConversionService        typeConverter.setConversionService(DefaultConversionService.getSharedInstance());        //將字符串"true"轉(zhuǎn)換成Boolean類型的true        Boolean b = typeConverter.convertIfNecessary("true", Boolean.class);        System.out.println("b = " + b);    }}

這里需要注意,ConversionService需要我們手動(dòng)設(shè)置,但是PropertyEditor不需要,因?yàn)镾impleTypeConverter默認(rèn)會(huì)去添加PropertyEditor的實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

小結(jié)

到這就講完了類型轉(zhuǎn)換的常見(jiàn)的幾個(gè)api,這里再簡(jiǎn)單總結(jié)一下:I4828資訊網(wǎng)——每日最新資訊28at.com

  • PropertyEditor:String轉(zhuǎn)換成目標(biāo)類型
  • Converter:用于一個(gè)類型轉(zhuǎn)換成另一個(gè)類型
  • GenericConverter:用于處理泛型的轉(zhuǎn)換,主要用于集合
  • ConversionService:門面接口,內(nèi)部會(huì)調(diào)用Converter和GenericConverter
  • TypeConverter:門面接口,內(nèi)部會(huì)調(diào)用PropertyEditor和ConversionService

畫(huà)張圖來(lái)總結(jié)他們之間的關(guān)系:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

前面在舉@Value的例子時(shí)說(shuō),類型轉(zhuǎn)換是根據(jù)PropertyEditor來(lái)的,其實(shí)只說(shuō)了一半,因?yàn)榈讓訉?shí)際上是根據(jù)TypeConverter來(lái)轉(zhuǎn)換的,所以@Value類型轉(zhuǎn)換時(shí)也能使用ConversionService類轉(zhuǎn)換,所以那張圖實(shí)際上應(yīng)該這么畫(huà)才算對(duì)。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

2、Environment中到底是如何進(jìn)行類型轉(zhuǎn)換的?

這里我們回到開(kāi)頭提到的話題,Environment中到底是如何進(jìn)行類型轉(zhuǎn)換的,讓我們看看Environment類的接口體系。I4828資訊網(wǎng)——每日最新資訊28at.com

Environment有個(gè)子接口ConfigurableEnvironment中,前面也提到過(guò)。I4828資訊網(wǎng)——每日最新資訊28at.com

它繼承了ConfigurablePropertyResolver接口。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

而ConfigurablePropertyResolver有一個(gè)setConversionService方法。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

所以從這可以看出,Environment底層實(shí)際上是通過(guò)ConversionService實(shí)現(xiàn)類型轉(zhuǎn)換的。I4828資訊網(wǎng)——每日最新資訊28at.com

這其實(shí)也就造成了一個(gè)問(wèn)題,因?yàn)镃onversionService和PropertyEditor屬于并列關(guān)系,那么就會(huì)導(dǎo)致Environment無(wú)法使用PropertyEditor來(lái)進(jìn)行類型轉(zhuǎn)換,也就會(huì)喪失部分Spring提供的類型轉(zhuǎn)換功能,就比如無(wú)法通過(guò)Environment將String轉(zhuǎn)換成Resource對(duì)象,因?yàn)镾pring沒(méi)有實(shí)現(xiàn)String轉(zhuǎn)換成Resource的Converter。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然你可以自己實(shí)現(xiàn)一個(gè)String轉(zhuǎn)換成Resource的Converter,然后添加到ConversionService,之后Environment就支持String轉(zhuǎn)換成Resource了。I4828資訊網(wǎng)——每日最新資訊28at.com

四、數(shù)據(jù)綁定

上一節(jié)我們講了類型轉(zhuǎn)換,而既然提到了類型轉(zhuǎn)換,那么就不得不提到數(shù)據(jù)綁定了,他們是密不可分的,因?yàn)樵跀?shù)據(jù)綁定時(shí),往往都會(huì)伴隨著類型轉(zhuǎn)換,數(shù)據(jù)綁定的意思就是將一些配置屬性跟我們的Bean對(duì)象的屬性進(jìn)行綁定。I4828資訊網(wǎng)——每日最新資訊28at.com

不知你是否記得,在遠(yuǎn)古的ssm時(shí)代,我們一般通過(guò)xml方式聲明Bean的時(shí)候,可以通過(guò)<property/>來(lái)設(shè)置Bean的屬性。I4828資訊網(wǎng)——每日最新資訊28at.com

<bean class="com.sanyou.spring.core.basic.User">    <property name="username" value="三友的java日記"/></bean>
@Datapublic class User {    private String username;}

然后Spring在創(chuàng)建User的過(guò)程中,就會(huì)給username屬性設(shè)置為三友的java日記。I4828資訊網(wǎng)——每日最新資訊28at.com

這就是數(shù)據(jù)綁定,將三友的java日記綁定到username這個(gè)屬性上。I4828資訊網(wǎng)——每日最新資訊28at.com

數(shù)據(jù)綁定的核心api主要包括以下幾個(gè):I4828資訊網(wǎng)——每日最新資訊28at.com

  • PropertyValues
  • BeanWrapper
  • DataBinder

1、PropertyValues

這里我們先來(lái)講一下PropertyValue(注意沒(méi)有s)。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

顧明思議,PropertyValue就是就是封裝了屬性名和對(duì)應(yīng)的屬性值,它就是數(shù)據(jù)綁定時(shí)屬性值的來(lái)源。I4828資訊網(wǎng)——每日最新資訊28at.com

以前面的提到的xml創(chuàng)建Bean為例,Spring在啟動(dòng)的時(shí)候會(huì)去解析xml中的<property/>標(biāo)簽,然后將name和value封裝成PropertyValue。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)創(chuàng)建User這個(gè)Bean的時(shí)候,到了屬性綁定的階段的時(shí)候,就會(huì)取出PropertyValue,設(shè)置到User的username屬性上。I4828資訊網(wǎng)——每日最新資訊28at.com

而PropertyValues,比PropertyValue多了一個(gè)s,也就是復(fù)數(shù)的意思,所以其實(shí)PropertyValues本質(zhì)上就是PropertyValue的一個(gè)集合。I4828資訊網(wǎng)——每日最新資訊28at.com

因?yàn)橐粋€(gè)Bean可能有多個(gè)屬性配置,所以就用PropertyValues來(lái)保存。I4828資訊網(wǎng)——每日最新資訊28at.com

2、BeanWrapper

BeanWrapper其實(shí)就數(shù)據(jù)綁定的核心api了,因?yàn)樵赟pring中涉及到數(shù)據(jù)綁定都是通過(guò)BeanWrapper來(lái)完成的,比如前面提到的Bean的屬性的綁定,就是通過(guò)BeanWrapper來(lái)的。I4828資訊網(wǎng)——每日最新資訊28at.com

BeanWrapper是一個(gè)接口,他有一個(gè)唯一的實(shí)現(xiàn)BeanWrapperImpl。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

先來(lái)個(gè)demo。I4828資訊網(wǎng)——每日最新資訊28at.com

public class BeanWrapperDemo {    public static void main(String[] args) {        //創(chuàng)建user對(duì)象        User user = new User();        //創(chuàng)建BeanWrapper對(duì)象,把需要進(jìn)行屬性綁定的user對(duì)象放進(jìn)去        BeanWrapper beanWrapper = new BeanWrapperImpl(user);        //進(jìn)行數(shù)據(jù)綁定,將三友的java日記這個(gè)屬性值賦值到username這個(gè)屬性上        beanWrapper.setPropertyValue(new PropertyValue("username", "三友的java日記"));        System.out.println("username = " + user.getUsername());    }}

結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

成功獲取到,說(shuō)明設(shè)置成功。I4828資訊網(wǎng)——每日最新資訊28at.com

BeanWrapperImpl也間接實(shí)現(xiàn)了TypeConverter接口。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然底層還是通過(guò)前面提到的ConversionService和PropertyEditor實(shí)現(xiàn)的。I4828資訊網(wǎng)——每日最新資訊28at.com

所以當(dāng)配置的類型跟屬性的類型不同時(shí),就可以對(duì)配置的類型進(jìn)行轉(zhuǎn)換,然后再綁定到屬性上。I4828資訊網(wǎng)——每日最新資訊28at.com

這里簡(jiǎn)單說(shuō)一下數(shù)據(jù)綁定和@Value的異同,因?yàn)檫@兩者看起來(lái)好像是一樣的,但實(shí)際還是有點(diǎn)區(qū)別的。I4828資訊網(wǎng)——每日最新資訊28at.com

相同點(diǎn):I4828資訊網(wǎng)——每日最新資訊28at.com

兩者都會(huì)涉及到類型轉(zhuǎn)換,@Value和數(shù)據(jù)綁定都會(huì)將值轉(zhuǎn)換成目標(biāo)屬性對(duì)應(yīng)的類型,并且都是通過(guò)TypeConverter來(lái)轉(zhuǎn)換的。I4828資訊網(wǎng)——每日最新資訊28at.com

不同點(diǎn):I4828資訊網(wǎng)——每日最新資訊28at.com

  • 發(fā)生時(shí)機(jī)不同,@Value比數(shù)據(jù)綁定更早,當(dāng)@Value都注入完成之后才會(huì)發(fā)生數(shù)據(jù)綁定(屬性賦值)。
  • 屬性賦值方式不同,@Value是通過(guò)反射來(lái)的,而是數(shù)據(jù)綁定是通過(guò)setter方法來(lái)的,如果沒(méi)有setter方法,屬性是沒(méi)辦法綁定的。

3、DataBinder

DataBinder也是用來(lái)進(jìn)行數(shù)據(jù)綁定的,它的底層也是間接通過(guò)BeanWrapper來(lái)實(shí)現(xiàn)的數(shù)據(jù)綁定的。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

但是他相比于BeanWrapper多了一些功能,比如在數(shù)據(jù)綁定之后,可以對(duì)數(shù)據(jù)校驗(yàn),比如可以校驗(yàn)字段的長(zhǎng)度等等。I4828資訊網(wǎng)——每日最新資訊28at.com

說(shuō)到數(shù)據(jù)校驗(yàn),是不是想到了SpringMVC中的參數(shù)校驗(yàn),通過(guò)@Valid配合一些諸如@NotBlank、@NotNull等注解,實(shí)現(xiàn)優(yōu)雅的參數(shù)校驗(yàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

其實(shí)SpringMVC的參數(shù)校驗(yàn)就是通過(guò)DataBinder來(lái)的,所以DataBinder其實(shí)在SpringMVC中用的比較多,但是在Spring中確用的很少。I4828資訊網(wǎng)——每日最新資訊28at.com

如果你有興趣,可以翻一下SpringMVC中關(guān)于請(qǐng)求參數(shù)處理的HandlerMethodArgumentResolver的實(shí)現(xiàn),里面有的實(shí)現(xiàn)會(huì)用到DataBinder(WebDataBinder)來(lái)進(jìn)行數(shù)據(jù)請(qǐng)求參數(shù)跟實(shí)體類的數(shù)據(jù)綁定、類型轉(zhuǎn)換、數(shù)據(jù)校驗(yàn)等等。I4828資訊網(wǎng)——每日最新資訊28at.com

不知道你有沒(méi)有注意過(guò),平時(shí)寫(xiě)接口的時(shí)候,前端傳來(lái)的參數(shù)String類型的時(shí)間字符串無(wú)法通過(guò)Spring框架本身轉(zhuǎn)換成Date類型,有部分原因就是前面提到的Spring沒(méi)有相關(guān)的Converter實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

總的來(lái)說(shuō),數(shù)據(jù)綁定在xml配置和SpringMVC中用的比較多的,并且數(shù)據(jù)綁定也是Spring Bean生命周期中一個(gè)很重要的環(huán)節(jié)。I4828資訊網(wǎng)——每日最新資訊28at.com

五、泛型處理

Spring為了方便操作和處理泛型類型,提供了一個(gè)強(qiáng)大的工具類——ResolvableType。I4828資訊網(wǎng)——每日最新資訊28at.com

泛型處理其實(shí)是一塊相對(duì)獨(dú)立的東西,因?yàn)樗椭皇且粋€(gè)工具類,只還不過(guò)這個(gè)工具類在Spring中卻是無(wú)處不在!I4828資訊網(wǎng)——每日最新資訊28at.com

ResolvableType提供了有一套靈活的API,可以在運(yùn)行時(shí)獲取和處理泛型類型等信息。I4828資訊網(wǎng)——每日最新資訊28at.com

ResolvableTypeResolvableTypeI4828資訊網(wǎng)——每日最新資訊28at.com

接下來(lái)就通過(guò)一個(gè)案例,來(lái)看一看如何通過(guò)ResolvableType快速簡(jiǎn)單的獲取到泛型的。I4828資訊網(wǎng)——每日最新資訊28at.com

首先,我聲明了一個(gè)MyMap類,繼承HashMap,第一個(gè)泛型參數(shù)是Integer類型,第二個(gè)泛型參數(shù)是List類型,List的泛型參數(shù)又是String。I4828資訊網(wǎng)——每日最新資訊28at.com

public class MyMap extends HashMap<Integer, List<String>> {}

接下來(lái)就來(lái)演示一下如何獲取到HashMap的泛型參數(shù)以及List的泛型參數(shù)。I4828資訊網(wǎng)——每日最新資訊28at.com

第一步,先來(lái)通過(guò)ResolvableType#forClass方法創(chuàng)建一個(gè)MyMap類型對(duì)應(yīng)的ResolvableType。I4828資訊網(wǎng)——每日最新資訊28at.com

//創(chuàng)建MyMap對(duì)應(yīng)的ResolvableTypeResolvableType myMapType = ResolvableType.forClass(MyMap.class);

因?yàn)榉盒蛥?shù)是在父類HashMap中,所以我們得獲取到父類HashMap對(duì)應(yīng)的ResolvableType,通過(guò)ResolvableType#getSuperType()方法獲取。I4828資訊網(wǎng)——每日最新資訊28at.com

//獲取父類HashMap對(duì)應(yīng)的ResolvableTypeResolvableType hashMapType = myMapType.getSuperType();

接下來(lái)需要獲取HashMap的泛型參數(shù)對(duì)應(yīng)的ResolvableType類型,可以通過(guò)ResolvableType#getGeneric(int... indexes)就可以獲取指定位置的泛型參數(shù)ResolvableType,方法參數(shù)就是指第幾個(gè)位置的泛型參數(shù),從0開(kāi)始。I4828資訊網(wǎng)——每日最新資訊28at.com

比如獲取第一個(gè)位置的對(duì)應(yīng)的ResolvableType類型。I4828資訊網(wǎng)——每日最新資訊28at.com

//獲取第一個(gè)泛型參數(shù)對(duì)應(yīng)的ResolvableTypeResolvableType firstGenericType = hashMapType.getGeneric(0);

現(xiàn)在有了第一個(gè)泛型參數(shù)的ResolvableType類型,只需要通過(guò)ResolvableType#resolve()方法就可以獲取到ResolvableType類型對(duì)應(yīng)的class類型,這樣就可以獲取到一個(gè)泛型參數(shù)的class類型。I4828資訊網(wǎng)——每日最新資訊28at.com

//獲取第一個(gè)泛型參數(shù)對(duì)應(yīng)的ResolvableType對(duì)應(yīng)的class類型,也就是Integer的class類型Class<?> firstGenericClass = firstGenericType.resolve();

如果你想獲取到HashMap第二個(gè)泛型參數(shù)的泛型類型,也就是List泛型類型就可以這么寫(xiě)。I4828資訊網(wǎng)——每日最新資訊28at.com

//HashMap第二個(gè)泛型參數(shù)的對(duì)應(yīng)的ResolvableType,也就是List<String>ResolvableType secondGenericType = hashMapType.getGeneric(1);//HashMap第二個(gè)泛型參數(shù)List<String>的第一個(gè)泛型類型String對(duì)應(yīng)的ResolvableTypeResolvableType secondFirstGenericType = secondGenericType.getGeneric(0);//這樣就獲取到了List<String>的泛型類型StringClass<?> secondFirstGenericClass = secondFirstGenericType.resolve();

從上面的演示下來(lái)可以發(fā)現(xiàn),其實(shí)每變化一步,其實(shí)就是獲取對(duì)應(yīng)泛型或者是父類等等對(duì)應(yīng)的ResolvableType,父類或者是泛型參數(shù)又可能有泛型之類的,只需要一步一步獲取就可以了,當(dāng)需要獲取到具體的class類型的時(shí)候,通過(guò)ResolvableType#resolve()方法就行了。I4828資訊網(wǎng)——每日最新資訊28at.com

除了上面提到的通過(guò)ResolvableType#forClass方法創(chuàng)建ResolvableType之外,還可以通過(guò)一下幾個(gè)方法創(chuàng)建:I4828資訊網(wǎng)——每日最新資訊28at.com

  • forField(Field field):獲取字段類型對(duì)應(yīng)的ResolvableType
  • forMethodReturnType(Method method):獲取方法返回值類型對(duì)應(yīng)的ResolvableType
  • forMethodParameter(Method method, int parameterIndex):獲取方法某個(gè)位置方法參數(shù)對(duì)應(yīng)的ResolvableType
  • forConstructorParameter(Constructor<?> constructor, int parameterIndex):獲取構(gòu)造方法某個(gè)構(gòu)造參數(shù)對(duì)應(yīng)的ResolvableType

通過(guò)上面解釋可以看出,對(duì)于一個(gè)類方法參數(shù),方法返回值,字段等等都可以獲取到對(duì)應(yīng)的ResolvableTypeI4828資訊網(wǎng)——每日最新資訊28at.com

六、國(guó)際化

國(guó)際化(Internationalization,簡(jiǎn)稱i18n)也是Spring提供的一個(gè)核心功能,它其實(shí)也是一塊相對(duì)獨(dú)立的功能。I4828資訊網(wǎng)——每日最新資訊28at.com

所謂的國(guó)際化,其實(shí)理解簡(jiǎn)單點(diǎn)就是對(duì)于不同的地區(qū)國(guó)家,輸出的文本內(nèi)容語(yǔ)言不同。I4828資訊網(wǎng)——每日最新資訊28at.com

Spring的國(guó)際化其實(shí)主要是依賴Java中的國(guó)際化和文本處理方式。I4828資訊網(wǎng)——每日最新資訊28at.com

1、Java中的國(guó)際化

Locale

Locale是Java提供的一個(gè)類,它可以用來(lái)標(biāo)識(shí)不同的語(yǔ)言和地區(qū),如en_US表示美國(guó)英語(yǔ),zh_CN表示中國(guó)大陸中文等。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

目前Java已經(jīng)窮舉了很多國(guó)家的地區(qū)Locale。I4828資訊網(wǎng)——每日最新資訊28at.com

我們可以使用Locale類獲取系統(tǒng)默認(rèn)的Locale,也可以手動(dòng)設(shè)置Locale,以適應(yīng)不同的語(yǔ)言環(huán)境。I4828資訊網(wǎng)——每日最新資訊28at.com

ResourceBundle

ResourceBundle是一個(gè)加載本地資源的一個(gè)類,他可以根據(jù)傳入的Locale不同,加載不同的資源。I4828資訊網(wǎng)——每日最新資訊28at.com

來(lái)個(gè)demo:I4828資訊網(wǎng)——每日最新資訊28at.com

首先準(zhǔn)備資源文件,資源文件通常是.properties文件,文件名命名規(guī)則如下:I4828資訊網(wǎng)——每日最新資訊28at.com

basename_lang_country.properties

basename無(wú)所謂,叫什么都可以,而lang和country是從Locale中獲取的。I4828資訊網(wǎng)——每日最新資訊28at.com

舉個(gè)例子,我們看看英語(yǔ)地區(qū)的Locale。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

從上圖可以看出,英語(yǔ)Locale的lang為en,country為空字符串,那么此時(shí)英語(yǔ)地區(qū)對(duì)應(yīng)資源文件就可以命名為:basename_en.properties,由于country為空字符串,可以省略。I4828資訊網(wǎng)——每日最新資訊28at.com

中國(guó)大陸Locale如下圖:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

此時(shí)文件就可以命為:basename_zh_CN.properties。I4828資訊網(wǎng)——每日最新資訊28at.com

好了,現(xiàn)在既然知道了命名規(guī)則,我們就創(chuàng)建兩個(gè)文件,basename就叫message,一個(gè)英語(yǔ),一個(gè)中文,放在classpath路徑下。I4828資訊網(wǎng)——每日最新資訊28at.com

中文資源文件:message_zh_CN.properties,內(nèi)容為:I4828資訊網(wǎng)——每日最新資訊28at.com

name=三友的java日記

英文資源文件:message_en.properties,內(nèi)容為:I4828資訊網(wǎng)——每日最新資訊28at.com

name=sanyou's java diary

有了文件之后,就可以通過(guò)ResourceBundle#getBundle(String baseName,Locale locale)方法來(lái)獲取獲取ResourceBundle。I4828資訊網(wǎng)——每日最新資訊28at.com

  • 第一個(gè)參數(shù)baseName就是我們的文件名中的basename,對(duì)于我們的demo來(lái)說(shuō),就是message
  • 第二個(gè)參數(shù)就是地區(qū),根據(jù)地區(qū)的不同加載不同地區(qū)的文件

測(cè)試一下:I4828資訊網(wǎng)——每日最新資訊28at.com

public class ResourceBundleDemo {    public static void main(String[] args) {        //獲取ResourceBundle,第一個(gè)參數(shù)baseName就是我們的文件名稱,第二個(gè)參數(shù)就是地區(qū)        ResourceBundle chineseResourceBundle = ResourceBundle.getBundle("message", Locale.SIMPLIFIED_CHINESE);        //根據(jù)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);    }}

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

其實(shí)運(yùn)行結(jié)果可以看出,其實(shí)是成功獲取了,只不過(guò)中文亂碼了,這主要是因?yàn)镽esourceBundle底層其實(shí)編碼是ISO-8859-1,所以會(huì)導(dǎo)致亂碼。I4828資訊網(wǎng)——每日最新資訊28at.com

解決辦法最簡(jiǎn)單就是把中文用Java Unicode序列來(lái)表示,之后就可以讀出中文了了,比如三友的java日記用Java Unicode序列表示為/u4e09/u53cb/u7684java/u65e5/u8bb0。I4828資訊網(wǎng)——每日最新資訊28at.com

除了這種方式之外,其實(shí)還可以繼承ResourceBundle內(nèi)部一個(gè)Control類。I4828資訊網(wǎng)——每日最新資訊28at.com

ControlControlI4828資訊網(wǎng)——每日最新資訊28at.com

重寫(xiě)newBundle方法。I4828資訊網(wǎng)——每日最新資訊28at.com

newBundlenewBundleI4828資訊網(wǎng)——每日最新資訊28at.com

newBundle是創(chuàng)建ResourceBundle對(duì)應(yīng)核心方法,重寫(xiě)的時(shí)候你就可以隨心所欲讓它支持其它編碼方式。I4828資訊網(wǎng)——每日最新資訊28at.com

有了新的Control之后,獲取ResourceBundle時(shí)只需要通過(guò)ResourceBundle#getBundle(String baseName, Locale targetLocale,Control control)方法指定Control就可以了。I4828資訊網(wǎng)——每日最新資訊28at.com

Spring實(shí)際上就是通過(guò)這種方式擴(kuò)展,支持不同編碼的,后面也有提到。I4828資訊網(wǎng)——每日最新資訊28at.com

MessageFormat

MessageFormat顧明思議就是把消息格式化。它可以接收一條包含占位符的消息模板,并根據(jù)提供的參數(shù)替換占位符,生成最終的消息。I4828資訊網(wǎng)——每日最新資訊28at.com

MessageFormat對(duì)于將動(dòng)態(tài)值插入到消息中非常有用,如歡迎消息、錯(cuò)誤消息等。I4828資訊網(wǎng)——每日最新資訊28at.com

先來(lái)個(gè)Demo:I4828資訊網(wǎng)——每日最新資訊28at.com

public class MessageFormatDemo {    public static void main(String[] args) {        String message = MessageFormat.format("你好:{0}", "張三");        System.out.println("message = " + message);    }}

解釋一下上面這段代碼:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 你好:{0}其實(shí)就是前面提到的消息的模板,{0}就是占位符,中間的0代表消息格式化的時(shí)候?qū)⑻峁┑膮?shù)第一個(gè)參數(shù)替換占位符的值。
  • 張三就是提供的參數(shù),你可以寫(xiě)很多個(gè),但是我們的demo只會(huì)取第一個(gè)參數(shù),因?yàn)槭莧0}。

所以輸出結(jié)果為:I4828資訊網(wǎng)——每日最新資訊28at.com

message = 你好:張三

成功格式化消息。I4828資訊網(wǎng)——每日最新資訊28at.com

2、Spring國(guó)際化

Spring提供了一個(gè)國(guó)際化接口MessageSource。I4828資訊網(wǎng)——每日最新資訊28at.com

MessageSourceMessageSourceI4828資訊網(wǎng)——每日最新資訊28at.com

他有一個(gè)基于ResourceBundle + MessageFormat的實(shí)現(xiàn)ResourceBundleMessageSource。I4828資訊網(wǎng)——每日最新資訊28at.com

ResourceBundleMessageSourceResourceBundleMessageSourceI4828資訊網(wǎng)——每日最新資訊28at.com

他的本質(zhì)可以在資源文件存儲(chǔ)消息的模板,然后通過(guò)MessageFormat來(lái)替換占位符,MessageSource的getMessage方法就可以傳遞具體的參數(shù)。I4828資訊網(wǎng)——每日最新資訊28at.com

來(lái)個(gè)demo:I4828資訊網(wǎng)——每日最新資訊28at.com

現(xiàn)在模擬登錄歡迎語(yǔ)句,對(duì)于不同的人肯定要有不同的名字,所以資源文件需要存模板,需要在不同的資源文件加不同的模板。I4828資訊網(wǎng)——每日最新資訊28at.com

中文資源文件:message_zh_CN.propertiesI4828資訊網(wǎng)——每日最新資訊28at.com

welcome=您好:{0}

英文資源文件:message_en.propertiesI4828資訊網(wǎng)——每日最新資訊28at.com

welcome=hello:{0}

占位符,就是不同人不同名字。I4828資訊網(wǎng)——每日最新資訊28at.com

測(cè)試代碼:I4828資訊網(wǎng)——每日最新資訊28at.com

public class MessageSourceDemo {    public static void main(String[] args) {        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();        //Spring已經(jīng)擴(kuò)展了ResourceBundle的Control,支持資源文件的不同編碼方式,但是需要設(shè)置一下        messageSource.setDefaultEncoding("UTF-8");        //添加 baseName,就是前面提到的文件中的basename        messageSource.addBasenames("message");        //中文,傳個(gè)中文名字        String chineseWelcome = messageSource.getMessage("welcome", new Object[]{"張三"}, Locale.SIMPLIFIED_CHINESE);        System.out.println("chineseWelcome = " + chineseWelcome);        //英文,英語(yǔ)國(guó)家肯定是英文名        String englishWelcome = messageSource.getMessage("welcome", new Object[]{"Bob"}, Locale.ENGLISH);        System.out.println("englishWelcome = " + englishWelcome);    }}

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

chineseWelcome = 您好:張三englishWelcome = hello:Bob

成功根據(jù)完成不同國(guó)家資源的加載和模板消息的格式化。I4828資訊網(wǎng)——每日最新資訊28at.com

小結(jié)

這里來(lái)簡(jiǎn)單總結(jié)一下這一小節(jié)說(shuō)的內(nèi)容。I4828資訊網(wǎng)——每日最新資訊28at.com

  • Locale:不同國(guó)家和地區(qū)的信息封裝
  • ResourceBundle:根據(jù)不同國(guó)家的Locale,加載對(duì)應(yīng)的資源文件,這個(gè)資源文件的命名需要遵守basename_lang_country.properties命名規(guī)范
  • MessageFormat:其實(shí)就是一個(gè)文本處理的方式,他可以解析模板,根據(jù)參數(shù)替換模板的占位符
  • MessageSource:Spring提供的國(guó)際化接口,其實(shí)他底層主要是依賴Java的ResourceBundle和MessageFormat,資源文件存儲(chǔ)模板信息,MessageFormat根據(jù)MessageSource方法的傳參替換模板中的占位符

七、BeanFactory

我們知道Spring的核心就是IOC和AOP,而B(niǎo)eanFactory就是大名鼎鼎的IOC容器,他可以幫我們生產(chǎn)對(duì)象。I4828資訊網(wǎng)——每日最新資訊28at.com

1、BeanFactory接口體系

BeanFactory本身是一個(gè)接口。I4828資訊網(wǎng)——每日最新資訊28at.com

BeanFactoryBeanFactoryI4828資訊網(wǎng)——每日最新資訊28at.com

從上面的接口定義可以看出從可以從BeanFactory獲取到Bean。I4828資訊網(wǎng)——每日最新資訊28at.com

他也有很多子接口,不同的子接口有著不同的功能:I4828資訊網(wǎng)——每日最新資訊28at.com

  • ListableBeanFactory
  • HierarchicalBeanFactory
  • ConfigurableBeanFactory
  • AutowireCapableBeanFactory

ListableBeanFactory

ListableBeanFactoryListableBeanFactoryI4828資訊網(wǎng)——每日最新資訊28at.com

從提供的方法可以看出,提供了一些獲取集合的功能,比如有的接口可能有多個(gè)實(shí)現(xiàn),通過(guò)這些方法就可以獲取這些實(shí)現(xiàn)對(duì)象的集合。I4828資訊網(wǎng)——每日最新資訊28at.com

HierarchicalBeanFactory

HierarchicalBeanFactoryHierarchicalBeanFactoryI4828資訊網(wǎng)——每日最新資訊28at.com

從接口定義可以看出,可以獲取到父容器,說(shuō)明BeanFactory有子父容器的概念。I4828資訊網(wǎng)——每日最新資訊28at.com

ConfigurableBeanFactory

ConfigurableBeanFactoryConfigurableBeanFactoryI4828資訊網(wǎng)——每日最新資訊28at.com

從命名可以看出,可配置BeanFactory,所以可以對(duì)BeanFactory進(jìn)行配置,比如截圖中的方法,可以設(shè)置我們前面提到的類型轉(zhuǎn)換的東西,這樣在生成Bean的時(shí)候就可以類型屬性的類型轉(zhuǎn)換了。I4828資訊網(wǎng)——每日最新資訊28at.com

AutowireCapableBeanFactory

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

提供了自動(dòng)裝配Bean的實(shí)現(xiàn)、屬性填充、初始化、處理獲取依賴注入對(duì)象的功能。I4828資訊網(wǎng)——每日最新資訊28at.com

比如@Autowired最終就會(huì)調(diào)用AutowireCapableBeanFactory#resolveDependency處理注入的依賴。I4828資訊網(wǎng)——每日最新資訊28at.com

其實(shí)從這里也可以看出,Spring在BeanFactory的接口設(shè)計(jì)上面還是基于不同的職責(zé)進(jìn)行接口的劃分,其實(shí)不僅僅是在BeanFactory,前面提到的那些接口也基本符合這個(gè)原則。I4828資訊網(wǎng)——每日最新資訊28at.com

2、BeanDefinition及其相關(guān)組件

BeanDefinition

BeanDefinition是Spring Bean創(chuàng)建環(huán)節(jié)中很重要的一個(gè)東西,它封裝了Bean創(chuàng)建過(guò)程中所需要的元信息。I4828資訊網(wǎng)——每日最新資訊28at.com

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {    //設(shè)置Bean className    void setBeanClassName(@Nullable String beanClassName);    //獲取Bean className    @Nullable    String getBeanClassName();        //設(shè)置是否是懶加載    void setLazyInit(boolean lazyInit);    //判斷是否是懶加載    boolean isLazyInit();        //判斷是否是單例    boolean isSingleton();}

如上代碼是BeanDefinition接口的部分方法,從這方法的定義名稱可以看出,一個(gè)Bean所創(chuàng)建過(guò)程中所需要的一些信息都可以從BeanDefinition中獲取,比如這個(gè)Bean的class類型,這個(gè)Bean是否是懶加載,這個(gè)Bean是否是單例的等等,因?yàn)橛辛诉@些信息,Spring才知道要?jiǎng)?chuàng)建一個(gè)什么樣的Bean。I4828資訊網(wǎng)——每日最新資訊28at.com

讀取BeanDefinition

讀取BeanDefinition大致分為以下幾類:I4828資訊網(wǎng)——每日最新資訊28at.com

  • BeanDefinitionReader
  • ClassPathBeanDefinitionScanner

BeanDefinitionReader

BeanDefinitionReaderBeanDefinitionReaderI4828資訊網(wǎng)——每日最新資訊28at.com

BeanDefinitionReader可以通過(guò)loadBeanDefinitions(Resource resource)方法來(lái)加載BeanDefinition,方法參數(shù)就是我們前面說(shuō)的資源,比如可以將Bean定義在xml文件中,這個(gè)xml文件就是一個(gè)資源。I4828資訊網(wǎng)——每日最新資訊28at.com

BeanDefinitionReader的相關(guān)實(shí)現(xiàn):I4828資訊網(wǎng)——每日最新資訊28at.com

  • XmlBeanDefinitionReader:讀取xml配置的Bean
  • PropertiesBeanDefinitionReader:讀取properties文件配置的Bean,是的,你沒(méi)看錯(cuò),Bean可以定義在properties文件配置中
  • AnnotatedBeanDefinitionReader:讀取通過(guò)注解定義的Bean,比如@Lazy注解等等,AnnotatedBeanDefinitionReader不是BeanDefinitionReader的實(shí)現(xiàn),但是作用是一樣的

ClassPathBeanDefinitionScannerI4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

這個(gè)作用就是掃描指定包下通過(guò)@Component及其派生注解(@Service等等)注解定義的Bean,其實(shí)就是@ComponentScan注解的底層實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

ClassPathBeanDefinitionScanner這個(gè)類其實(shí)在很多其它框架中都有使用到,因?yàn)檫@個(gè)類可以掃描指定包下,生成BeanDefinition,對(duì)于那些需要掃描包來(lái)生成BeanDefinition來(lái)說(shuō),用的很多。I4828資訊網(wǎng)——每日最新資訊28at.com

比如說(shuō)常見(jiàn)的MyBatis框架,他的注解@MapperScan可以掃描指定包下的Mapper接口,其實(shí)他也是通過(guò)繼承ClassPathBeanDefinitionScanner來(lái)掃描Mapper接口的。I4828資訊網(wǎng)——每日最新資訊28at.com

BeanDefinitionRegistryBeanDefinitionRegistryI4828資訊網(wǎng)——每日最新資訊28at.com

這個(gè)從命名就可以看出,是BeanDefinition的注冊(cè)中心,也就是用來(lái)保存BeanDefinition的。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

提供了BeanDefinition的增刪查的功能。I4828資訊網(wǎng)——每日最新資訊28at.com

講到這里,就可以用一張圖來(lái)把前面提到東西關(guān)聯(lián)起來(lái)。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

  • 通過(guò)BeanDefinitionReader或者是ClassPathBeanDefinitionScanner為每一個(gè)Bean生成一個(gè)BeanDefinition
  • BeanDefinition生成之后,添加到BeanDefinitionRegistry中
  • 當(dāng)從BeanFactory中獲取Bean時(shí),會(huì)從BeanDefinitionRegistry中拿出需要?jiǎng)?chuàng)建的Bean對(duì)應(yīng)的BeanDefinition,根據(jù)BeanDefinition的信息來(lái)生成Bean
  • 當(dāng)生成的Bean是單例的時(shí)候,Spring會(huì)將Bean保存到SingletonBeanRegistry中,也就是平時(shí)說(shuō)的三級(jí)緩存中的第一級(jí)緩存中,以免重復(fù)創(chuàng)建,需要使用的時(shí)候直接從SingletonBeanRegistry中查找

3、BeanFactory核心實(shí)現(xiàn)

前面提到的BeanFactory體系都是一個(gè)接口,那么BeanFactory的實(shí)現(xiàn)類是哪個(gè)類呢?I4828資訊網(wǎng)——每日最新資訊28at.com

BeanFactory真正底層的實(shí)現(xiàn)類,其實(shí)就只有一個(gè),那就是DefaultListableBeanFactory這個(gè)類,這個(gè)類以及父類真正實(shí)現(xiàn)了BeanFactory及其子接口的所有的功能。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

并且接口的實(shí)現(xiàn)上可以看出,他也實(shí)現(xiàn)了BeanDefinitionRegistry,也就是說(shuō),在底層的實(shí)現(xiàn)上,其實(shí)BeanFactory跟BeanDefinitionRegistry的實(shí)現(xiàn)是同一個(gè)實(shí)現(xiàn)類。I4828資訊網(wǎng)——每日最新資訊28at.com

上面說(shuō)了這么多,來(lái)個(gè)demo。I4828資訊網(wǎng)——每日最新資訊28at.com

public class BeanFactoryDemo {    public static void main(String[] args) {        //創(chuàng)建一個(gè)BeanFactory        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();        //創(chuàng)建一個(gè)BeanDefinitionReader,構(gòu)造參數(shù)是一個(gè)BeanDefinitionRegistry        //因?yàn)镈efaultListableBeanFactory實(shí)現(xiàn)了BeanDefinitionRegistry,所以直接把beanFactory當(dāng)做構(gòu)造參數(shù)傳過(guò)去        AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(beanFactory);        //讀取當(dāng)前類 BeanFactoryDemo 為一個(gè)Bean,讓Spring幫我們生成這個(gè)Bean        beanDefinitionReader.register(BeanFactoryDemo.class);        //從容器中獲取注冊(cè)的BeanFactoryDemo的Bean        BeanFactoryDemo beanFactoryDemo = beanFactory.getBean(BeanFactoryDemo.class);        System.out.println("beanFactoryDemo = " + beanFactoryDemo);    }}

簡(jiǎn)單說(shuō)一下上面代碼的意思:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 創(chuàng)建一個(gè)BeanFactory,就是DefaultListableBeanFactory
  • 創(chuàng)建一個(gè)AnnotatedBeanDefinitionReader,構(gòu)造參數(shù)是一個(gè)BeanDefinitionRegistry,因?yàn)锽eanDefinitionReader需要把讀出來(lái)的BeanDefinition存到BeanDefinitionRegistry中,同時(shí)因?yàn)镈efaultListableBeanFactory實(shí)現(xiàn)了BeanDefinitionRegistry,所以直接把beanFactory當(dāng)做構(gòu)造參數(shù)傳過(guò)去
  • 讀取當(dāng)前類 BeanFactoryDemo 為一個(gè)Bean,讓Spring幫我們生成這個(gè)Bean
  • 后面就是獲取打印

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

成功獲取到我們注冊(cè)的Bean。I4828資訊網(wǎng)——每日最新資訊28at.com

總結(jié)

本節(jié)主要講了實(shí)現(xiàn)IOC的幾個(gè)核心的組件。I4828資訊網(wǎng)——每日最新資訊28at.com

BeanFactory及其接口體系:I4828資訊網(wǎng)——每日最新資訊28at.com

  • ListableBeanFactory
  • HierarchicalBeanFactory
  • ConfigurableBeanFactory
  • AutowireCapableBeanFactory

BeanDefinition及其相關(guān)組件:I4828資訊網(wǎng)——每日最新資訊28at.com

  • BeanDefinition
  • BeanDefinitionReader和ClassPathBeanDefinitionScanner:讀取資源,生成BeanDefinition
  • BeanDefinitionRegistry:存儲(chǔ)BeanDefinition

BeanFactory核心實(shí)現(xiàn):I4828資訊網(wǎng)——每日最新資訊28at.com

  • DefaultListableBeanFactory:IOC容器,同時(shí)實(shí)現(xiàn)了BeanDefinitionRegistry接口

八、ApplicationContext

終于講到了ApplicationContext,因?yàn)榍懊嬲f(shuō)的那么多其實(shí)就是為ApplicationContext做鋪墊的。I4828資訊網(wǎng)——每日最新資訊28at.com

先來(lái)看看ApplicationContext的接口。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

你會(huì)驚訝地發(fā)現(xiàn),ApplicationContext繼承的幾個(gè)接口,除了EnvironmentCapable和ApplicationEventPublisher之外,其余都是前面說(shuō)的。I4828資訊網(wǎng)——每日最新資訊28at.com

EnvironmentCapable這個(gè)接口比較簡(jiǎn)單,提供了獲取Environment的功能。I4828資訊網(wǎng)——每日最新資訊28at.com

EnvironmentCapableEnvironmentCapableI4828資訊網(wǎng)——每日最新資訊28at.com

說(shuō)明了可以從ApplicationContext中獲取到Environment,所以EnvironmentCapable也算是前面說(shuō)過(guò)了。I4828資訊網(wǎng)——每日最新資訊28at.com

至于ApplicationEventPublisher我們留到下一節(jié)說(shuō)。I4828資訊網(wǎng)——每日最新資訊28at.com

ApplicationContext也繼承了ListableBeanFactory和HierarchicalBeanFactory,也就說(shuō)明ApplicationContext其實(shí)他也是一個(gè)BeanFactory,所以說(shuō)ApplicationContext是IOC容器的說(shuō)法也沒(méi)什么毛病,但是由于他還繼承了其它接口,功能比BeanFactory多多了。I4828資訊網(wǎng)——每日最新資訊28at.com

所以,ApplicationContext是一個(gè)集萬(wàn)千功能為一身的接口,一旦你獲取到了ApplicationContext(可以@Autowired注入),你就可以用來(lái)獲取Bean、加載資源、獲取環(huán)境,還可以國(guó)際化一下,屬實(shí)是個(gè)王炸。I4828資訊網(wǎng)——每日最新資訊28at.com

雖然ApplicationContext繼承了這些接口,但是ApplicationContext對(duì)于接口的實(shí)現(xiàn)是通過(guò)一種委派的方式,而真正的實(shí)現(xiàn)都是我們前面說(shuō)的那些實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

什么叫委派呢,咱寫(xiě)一個(gè)例子你就知道了。I4828資訊網(wǎng)——每日最新資訊28at.com

public class MyApplicationContext implements ApplicationContext {    private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();    @Override    public Resource[] getResources(String locationPattern) throws IOException {        return resourcePatternResolver.getResources(locationPattern);    }    }

如上,其實(shí)是一段偽代碼。I4828資訊網(wǎng)——每日最新資訊28at.com

因?yàn)锳pplicationContext繼承了ResourcePatternResolver接口,所以我實(shí)現(xiàn)了getResources方法,但是真正的實(shí)現(xiàn)其實(shí)是交給變量中的PathMatchingResourcePatternResolver來(lái)實(shí)現(xiàn)的,這其實(shí)就是委派,不直接實(shí)現(xiàn),而是交給其它真正實(shí)現(xiàn)了這個(gè)接口的類來(lái)處理。I4828資訊網(wǎng)——每日最新資訊28at.com

同理,ApplicationContext對(duì)于BeanFactory接口的實(shí)現(xiàn)其實(shí)最終也是交由DefaultListableBeanFactory來(lái)委派處理的。I4828資訊網(wǎng)——每日最新資訊28at.com

委派這種方式在Spring內(nèi)部還是用的非常多的,前面提到的某些接口在的實(shí)現(xiàn)上也是通過(guò)委派的方式來(lái)的。I4828資訊網(wǎng)——每日最新資訊28at.com

ApplicationContext有一個(gè)子接口,ConfigurableApplicationContextI4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

從提供的方法看出,就是可以對(duì)ApplicationContext進(jìn)行配置,比如設(shè)置Environment,同時(shí)也能設(shè)置parent,說(shuō)明了ApplicationContext也有子父的概念。I4828資訊網(wǎng)——每日最新資訊28at.com

我們已經(jīng)看到了很多以Configurable開(kāi)頭的接口,這就是命名習(xí)慣,表示了可配置的意思,提供的都是set、add之類的方法。I4828資訊網(wǎng)——每日最新資訊28at.com

ApplicationContext的實(shí)現(xiàn)很多,但是他有一個(gè)非常重要的抽象實(shí)現(xiàn)AbstractApplicationContext,因?yàn)槠渌膶?shí)現(xiàn)都是繼承這個(gè)抽象實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

AbstractApplicationContextAbstractApplicationContextI4828資訊網(wǎng)——每日最新資訊28at.com

這個(gè)類主要是實(shí)現(xiàn)了一些繼承的接口方法,通過(guò)委派的方式,比如對(duì)于BeanFactory接口的實(shí)現(xiàn)。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

并且AbstractApplicationContext這個(gè)類也實(shí)現(xiàn)了一個(gè)非常核心的refresh方法。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

所有的ApplicationContext在創(chuàng)建之后必須調(diào)用這個(gè)refresh方法之后才能使用,至于這個(gè)方法干了哪些事,后面有機(jī)會(huì)再寫(xiě)一篇文章來(lái)著重扒一扒。I4828資訊網(wǎng)——每日最新資訊28at.com

九、事件

上一小節(jié)在說(shuō)ApplicationContext繼承的接口的時(shí)候,我們留下了一個(gè)懸念,那就是ApplicationEventPublisher的作用,而ApplicationEventPublisher就跟本節(jié)要說(shuō)的事件有關(guān)。I4828資訊網(wǎng)——每日最新資訊28at.com

Spring事件是一種觀察者模式的實(shí)現(xiàn),他的作用主要是用來(lái)解耦合的。I4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)發(fā)生了某件事,只要發(fā)布一個(gè)事件,對(duì)這個(gè)事件的監(jiān)聽(tīng)者(觀察者)就可以對(duì)事件進(jìn)行響應(yīng)或者處理。I4828資訊網(wǎng)——每日最新資訊28at.com

舉個(gè)例子來(lái)說(shuō),假設(shè)發(fā)生了火災(zāi),可能需要打119、救人,那么就可以基于事件的模型來(lái)實(shí)現(xiàn),只需要打119、救人監(jiān)聽(tīng)火災(zāi)的發(fā)生就行了,當(dāng)發(fā)生了火災(zāi),通知這些打119、救人去觸發(fā)相應(yīng)的邏輯操作。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

1、什么是Spring Event 事件

Spring Event 事件就是Spring實(shí)現(xiàn)了這種事件模型,你只需要基于Spring提供的API進(jìn)行擴(kuò)展,就可以輕易地完成事件的發(fā)布與訂閱。I4828資訊網(wǎng)——每日最新資訊28at.com

Spring事件相關(guān)api主要有以下幾個(gè):I4828資訊網(wǎng)——每日最新資訊28at.com

  • ApplicationEvent
  • ApplicationListener
  • ApplicationEventPublisher

ApplicationEvent

ApplicationEventApplicationEventI4828資訊網(wǎng)——每日最新資訊28at.com

事件的父類,所有具體的事件都得繼承這個(gè)類,構(gòu)造方法的參數(shù)是這個(gè)事件攜帶的參數(shù),監(jiān)聽(tīng)器就可以通過(guò)這個(gè)參數(shù)來(lái)進(jìn)行一些業(yè)務(wù)操作。I4828資訊網(wǎng)——每日最新資訊28at.com

ApplicationListener

ApplicationListenerApplicationListenerI4828資訊網(wǎng)——每日最新資訊28at.com

事件監(jiān)聽(tīng)的接口,泛型是需要監(jiān)聽(tīng)的事件類型,子類需要實(shí)現(xiàn)onApplicationEvent,參數(shù)就是監(jiān)聽(tīng)的事件類型,onApplicationEvent方法的實(shí)現(xiàn)就代表了對(duì)事件的處理,當(dāng)事件發(fā)生時(shí),Spring會(huì)回調(diào)onApplicationEvent方法的實(shí)現(xiàn),傳入發(fā)布的事件。I4828資訊網(wǎng)——每日最新資訊28at.com

ApplicationEventPublisher

ApplicationEventPublisherApplicationEventPublisherI4828資訊網(wǎng)——每日最新資訊28at.com

上一小節(jié)留下來(lái)的接口,事件發(fā)布器,通過(guò)publishEvent方法就可以發(fā)布一個(gè)事件,然后就可以觸發(fā)監(jiān)聽(tīng)這個(gè)事件的監(jiān)聽(tīng)器的回調(diào)。I4828資訊網(wǎng)——每日最新資訊28at.com

ApplicationContext繼承了ApplicationEventPublisher,說(shuō)明只要有ApplicationContext就可以來(lái)發(fā)布事件了。I4828資訊網(wǎng)——每日最新資訊28at.com

話不多說(shuō),上代碼

就以上面的火災(zāi)為例。I4828資訊網(wǎng)——每日最新資訊28at.com

創(chuàng)建一個(gè)火災(zāi)事件類I4828資訊網(wǎng)——每日最新資訊28at.com

火災(zāi)事件類繼承ApplicationEventI4828資訊網(wǎng)——每日最新資訊28at.com

// 火災(zāi)事件public class FireEvent extends ApplicationEvent {    public FireEvent(String source) {        super(source);    }}

創(chuàng)建火災(zāi)事件的監(jiān)聽(tīng)器I4828資訊網(wǎng)——每日最新資訊28at.com

打119的火災(zāi)事件的監(jiān)聽(tīng)器:I4828資訊網(wǎng)——每日最新資訊28at.com

public class Call119FireEventListener implements ApplicationListener<FireEvent> {    @Override    public void onApplicationEvent(FireEvent event) {        System.out.println("打119");    }}

救人的火災(zāi)事件的監(jiān)聽(tīng)器:I4828資訊網(wǎng)——每日最新資訊28at.com

public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {    @Override    public void onApplicationEvent(FireEvent event) {        System.out.println("救人");    }}

事件和對(duì)應(yīng)的監(jiān)聽(tīng)都有了,接下來(lái)進(jìn)行測(cè)試:I4828資訊網(wǎng)——每日最新資訊28at.com

public class Application {    public static void main(String[] args) {        //創(chuàng)建一個(gè)Spring容器        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();        //將 事件監(jiān)聽(tīng)器 注冊(cè)到容器中        applicationContext.register(Call119FireEventListener.class);        applicationContext.register(SavePersonFireEventListener.class);        applicationContext.refresh();        // 發(fā)布著火的事件,觸發(fā)監(jiān)聽(tīng)        applicationContext.publishEvent(new FireEvent("著火了"));    }}

將兩個(gè)事件注冊(cè)到Spring容器中,然后發(fā)布FireEvent事件I4828資訊網(wǎng)——每日最新資訊28at.com

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

打119救人

控制臺(tái)打印出了結(jié)果,觸發(fā)了監(jiān)聽(tīng)。I4828資訊網(wǎng)——每日最新資訊28at.com

如果現(xiàn)在需要對(duì)火災(zāi)進(jìn)行救火,那么只需要去監(jiān)聽(tīng)FireEvent,實(shí)現(xiàn)救火的邏輯,注入到Spring容器中,就可以了,其余的代碼根本不用動(dòng)。I4828資訊網(wǎng)——每日最新資訊28at.com

2、Spring內(nèi)置的事件

Spring內(nèi)置的事件很多,這里我羅列幾個(gè):I4828資訊網(wǎng)——每日最新資訊28at.com

事件類型I4828資訊網(wǎng)——每日最新資訊28at.com

觸發(fā)時(shí)機(jī)I4828資訊網(wǎng)——每日最新資訊28at.com

ContextRefreshedEventI4828資訊網(wǎng)——每日最新資訊28at.com

在調(diào)用ConfigurableApplicationContext 接口中的refresh()方法時(shí)觸發(fā)I4828資訊網(wǎng)——每日最新資訊28at.com

ContextStartedEventI4828資訊網(wǎng)——每日最新資訊28at.com

在調(diào)用ConfigurableApplicationContext的start()方法時(shí)觸發(fā)I4828資訊網(wǎng)——每日最新資訊28at.com

ContextStoppedEventI4828資訊網(wǎng)——每日最新資訊28at.com

在調(diào)用ConfigurableApplicationContext的stop()方法時(shí)觸發(fā)I4828資訊網(wǎng)——每日最新資訊28at.com

ContextClosedEventI4828資訊網(wǎng)——每日最新資訊28at.com

當(dāng)ApplicationContext被關(guān)閉時(shí)觸發(fā)該事件,也就是調(diào)用close()方法觸發(fā)I4828資訊網(wǎng)——每日最新資訊28at.com

在ApplicationContext(Spring容器)啟動(dòng)的過(guò)程中,Spring會(huì)發(fā)布這些事件,如果你需要這Spring容器啟動(dòng)的某個(gè)時(shí)刻進(jìn)行什么操作,只需要監(jiān)聽(tīng)對(duì)應(yīng)的事件即可。I4828資訊網(wǎng)——每日最新資訊28at.com

3、Spring事件的傳播特性

Spring事件的傳播是什么意思呢?I4828資訊網(wǎng)——每日最新資訊28at.com

前面提到,ApplicationContext有子父容器的概念,而Spring事件的傳播就是指當(dāng)通過(guò)子容器發(fā)布一個(gè)事件之后,不僅可以觸發(fā)在這個(gè)子容器的事件監(jiān)聽(tīng)器,還可以觸發(fā)在父容器的這個(gè)事件的監(jiān)聽(tīng)器。I4828資訊網(wǎng)——每日最新資訊28at.com

上代碼

public class EventPropagateApplication {    public static void main(String[] args) {        // 創(chuàng)建一個(gè)父容器        AnnotationConfigApplicationContext parentApplicationContext = new AnnotationConfigApplicationContext();        //將 打119監(jiān)聽(tīng)器 注冊(cè)到父容器中        parentApplicationContext.register(Call119FireEventListener.class);        parentApplicationContext.refresh();        // 創(chuàng)建一個(gè)子容器        AnnotationConfigApplicationContext childApplicationContext = new AnnotationConfigApplicationContext();        //將 救人監(jiān)聽(tīng)器 注冊(cè)到子容器中        childApplicationContext.register(SavePersonFireEventListener.class);        childApplicationContext.refresh();        // 設(shè)置一下父容器        childApplicationContext.setParent(parentApplicationContext);        // 通過(guò)子容器發(fā)布著火的事件,觸發(fā)監(jiān)聽(tīng)        childApplicationContext.publishEvent(new FireEvent("著火了"));    }}

創(chuàng)建了兩個(gè)容器,父容器注冊(cè)了打119的監(jiān)聽(tīng)器,子容器注冊(cè)了救人的監(jiān)聽(tīng)器,然后將子父容器通過(guò)setParent關(guān)聯(lián)起來(lái),最后通過(guò)子容器,發(fā)布了著火的事件。I4828資訊網(wǎng)——每日最新資訊28at.com

運(yùn)行結(jié)果:I4828資訊網(wǎng)——每日最新資訊28at.com

救人打119

從打印的日志,的確可以看出,雖然是子容器發(fā)布了著火的事件,但是父容器的監(jiān)聽(tīng)器也成功監(jiān)聽(tīng)了著火事件。I4828資訊網(wǎng)——每日最新資訊28at.com

而這種傳播特性,從源碼中也可以看出來(lái):I4828資訊網(wǎng)——每日最新資訊28at.com

事件傳播源碼事件傳播源碼I4828資訊網(wǎng)——每日最新資訊28at.com

如果父容器不為空,就會(huì)通過(guò)父容器再發(fā)布一次事件。I4828資訊網(wǎng)——每日最新資訊28at.com

傳播特性的一個(gè)小坑

前面說(shuō)過(guò),在Spring容器啟動(dòng)的過(guò)程,會(huì)發(fā)布很多事件,如果你需要有相應(yīng)的擴(kuò)展,可以監(jiān)聽(tīng)這些事件。I4828資訊網(wǎng)——每日最新資訊28at.com

但是,不知道你有沒(méi)有遇到過(guò)這么一個(gè)坑,就是在SpringCloud環(huán)境下,你監(jiān)聽(tīng)這些Spring事件的監(jiān)聽(tīng)器會(huì)執(zhí)行很多次,這其實(shí)就是跟傳播特性有關(guān)。I4828資訊網(wǎng)——每日最新資訊28at.com

在SpringCloud環(huán)境下,為了使像FeignClient和RibbonClient這些不同服務(wù)的配置相互隔離,會(huì)為每個(gè)FeignClient或者是RibbonClient創(chuàng)建一個(gè)Spring容器,而這些容器都有一個(gè)公共的父容器,那就是SpringBoot項(xiàng)目啟動(dòng)時(shí)創(chuàng)建的容器。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

假設(shè)你監(jiān)聽(tīng)了容器刷新的ContextRefreshedEvent事件,那么你自己寫(xiě)的監(jiān)聽(tīng)器就在SpringBoot項(xiàng)目啟動(dòng)時(shí)創(chuàng)建的容器中。I4828資訊網(wǎng)——每日最新資訊28at.com

每個(gè)服務(wù)的配置容器他也是Spring容器,啟動(dòng)時(shí)也會(huì)發(fā)布ContextRefreshedEvent,那么由于傳播特性的關(guān)系,你的事件監(jiān)聽(tīng)器就會(huì)觸發(fā)執(zhí)行多次。I4828資訊網(wǎng)——每日最新資訊28at.com

圖片圖片I4828資訊網(wǎng)——每日最新資訊28at.com

如何解決這個(gè)坑呢?I4828資訊網(wǎng)——每日最新資訊28at.com

你可以進(jìn)行判斷這些監(jiān)聽(tīng)器有沒(méi)有執(zhí)行過(guò),比如加一個(gè)判斷的標(biāo)志;或者是監(jiān)聽(tīng)類似的事件,比如ApplicationStartedEvent事件,這種事件是在SpringBoot啟動(dòng)中發(fā)布的事件,而子容器不是SpringBoot,所以不會(huì)多次發(fā)這種事件,也就會(huì)只執(zhí)行一次。I4828資訊網(wǎng)——每日最新資訊28at.com

十、總結(jié)

到這到這整篇文章終于寫(xiě)完了,這里再來(lái)簡(jiǎn)單地回顧一下本文說(shuō)的幾個(gè)核心功能:I4828資訊網(wǎng)——每日最新資訊28at.com

  • 資源管理:對(duì)資源進(jìn)行統(tǒng)一的封裝,方便資源讀取和管理
  • 環(huán)境:對(duì)容器或者是項(xiàng)目的配置進(jìn)行管理
  • 類型轉(zhuǎn)換:將一種類型轉(zhuǎn)換成另一種類型
  • 數(shù)據(jù)綁定:將數(shù)據(jù)跟對(duì)象的屬性進(jìn)行綁定,綁定之前涉及到類型轉(zhuǎn)換
  • 泛型處理:一個(gè)操作泛型的工具類,Spring中到處可見(jiàn)
  • 國(guó)際化:對(duì)Java的國(guó)際化進(jìn)行了統(tǒng)一的封裝
  • BeanFactory:IOC容器
  • ApplicationContext:一個(gè)集萬(wàn)千功能于一身的王炸接口,也可以說(shuō)是IOC容器
  • 事件:Spring提供的基于觀察者模式實(shí)現(xiàn)的解耦合利器

當(dāng)然除了上面,Spring還有很多其它核心功能,就比如AOP、SpEL表達(dá)式等等,由于AOP涉及到Bean生命周期,本篇文章也沒(méi)有涉及到Bean生命周期的講解,所以這里就不講了,后面有機(jī)會(huì)再講;至于SpEL他是Spring提供的表達(dá)式語(yǔ)言,主要是語(yǔ)法,解析語(yǔ)法的一些東西,這里也不講了。I4828資訊網(wǎng)——每日最新資訊28at.com

最后,我怕你文章看得過(guò)于入迷,所以再來(lái)重復(fù)一遍,如果本篇文章對(duì)你有所幫助,還請(qǐng)多多點(diǎn)贊、轉(zhuǎn)發(fā)、在看,非常感謝!!I4828資訊網(wǎng)——每日最新資訊28at.com

哦,真差點(diǎn)就忘了,本文所有demo代碼都在這了I4828資訊網(wǎng)——每日最新資訊28at.com

https://github.com/sanyou3/spring-core-basic.git

參考資料:I4828資訊網(wǎng)——每日最新資訊28at.com

[1].《極客時(shí)間--小馬哥講Spring核心編程思想》I4828資訊網(wǎng)——每日最新資訊28at.com

[2].https://blog.csdn/zzuhkp/article/details/119455964 I4828資訊網(wǎng)——每日最新資訊28at.com

[3].https://blog.csdn/zzuhkp/article/details/119455948I4828資訊網(wǎng)——每日最新資訊28at.com

[4].https://blog.csdn/u010086122/article/details/81566515I4828資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-133-0.html三萬(wàn)字盤點(diǎn) Spring 九大核心基礎(chǔ)功能

聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 深度探索 Elasticsearch 8.X:function_score 參數(shù)解讀與實(shí)戰(zhàn)案例分析

下一篇: 只需五步,使用start.spring.io快速入門Spring編程

標(biāo)簽:
  • 熱門焦點(diǎn)
Top