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

當前位置:首頁 > 科技  > 軟件

三萬字盤點 Spring 九大核心基礎功能

來源: 責編: 時間:2023-08-05 11:45:50 4672觀看
導讀大家好,我是三友~~今天來跟大家聊一聊Spring的9大核心基礎功能。話不多說,先上目錄:圖片友情提示,本文過長,建議收藏,嘿嘿嘿!一、資源管理資源管理是Spring的一個核心的基礎功能,不過在說Spring的資源管理之前,先來簡單說一下J

大家好,我是三友~~JZM28資訊網——每日最新資訊28at.com

今天來跟大家聊一聊Spring的9大核心基礎功能。JZM28資訊網——每日最新資訊28at.com

話不多說,先上目錄:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

友情提示,本文過長,建議收藏,嘿嘿嘿!JZM28資訊網——每日最新資訊28at.com

一、資源管理

資源管理是Spring的一個核心的基礎功能,不過在說Spring的資源管理之前,先來簡單說一下Java中的資源管理。JZM28資訊網——每日最新資訊28at.com

Java資源管理

Java中的資源管理主要是通過java.URL來實現的,通過URL的openConnection方法可以對資源打開一個連接,通過這個連接讀取資源的內容。JZM28資訊網——每日最新資訊28at.com

資源不僅僅指的是網絡資源,還可以是本地文件、一個jar包等等。JZM28資訊網——每日最新資訊28at.com

1、來個Demo

舉個例子,比如你想到訪問www.baidu.com這個百度首頁網絡資源,那么此時就可以這么寫。JZM28資訊網——每日最新資訊28at.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);    }}

解釋一下上面代碼的意思:JZM28資訊網——每日最新資訊28at.com

  • 首先構建一個URL,指定資源的訪問協議為http協議
  • 通過URL打開一個資源訪問連接,然后獲取一個輸入流,讀取內容

運行結果:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

成功讀取到百度首頁的數據。JZM28資訊網——每日最新資訊28at.com

當然,也可以通過URL訪問本地文件資源,在創建URL的時候只需要指定協議類型為file://和文件的路徑就行了JZM28資訊網——每日最新資訊28at.com

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

這種方式這里我就不演示了。JZM28資訊網——每日最新資訊28at.com

其實這種方式實際上最終也是通過FileInputStream來讀取文件數據的,不信你可以自己debug試試。JZM28資訊網——每日最新資訊28at.com

2、原理

每種協議的URL資源都需要一個對應的一個URLStreamHandler來處理。JZM28資訊網——每日最新資訊28at.com

URLStreamHandlerURLStreamHandlerJZM28資訊網——每日最新資訊28at.com

比如說,http://協議有對應的URLStreamHandler的實現,file://協議的有對應的URLStreamHandler的實現。JZM28資訊網——每日最新資訊28at.com

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

圖片圖片JZM28資訊網——每日最新資訊28at.com

對于的URLStreamHandler如下圖所示:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

當在構建URL的時候,會去解析資源的訪問協議,根據訪問協議找到對應的URLStreamHandler的實現。JZM28資訊網——每日最新資訊28at.com

當然,除了Java本身支持的協議之外,我們還可以自己去擴展這個協議,大致只需要兩步即可:JZM28資訊網——每日最新資訊28at.com

  • 實現URLConnection,可以通過這個連接讀取資源的內容
  • 實現URLStreamHandler,通過URLStreamHandler可以獲取到URLConnection

不過需要注意的是,URLStreamHandler的實現需要放在sun.www.protocol.協議名稱包下,類名必須是Handler,這也是為什么截圖中的實現類名都叫Handler的原因。JZM28資訊網——每日最新資訊28at.com

當然如果不放在指定的包下也可以,但是需要實現java.URLStreamHandlerFactory接口。JZM28資訊網——每日最新資訊28at.com

對于擴展我就不演示了,如果你感興趣可以自行谷歌一下。JZM28資訊網——每日最新資訊28at.com

Spring資源管理

雖然Java提供了標準的資源管理方式,但是Spring并沒有用,而是自己搞了一套資源管理方式。JZM28資訊網——每日最新資訊28at.com

1、資源抽象

在Spring中,資源大致被抽象為兩個接口:JZM28資訊網——每日最新資訊28at.com

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

圖片圖片JZM28資訊網——每日最新資訊28at.com

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

圖片圖片JZM28資訊網——每日最新資訊28at.com

WritableResource

圖片圖片JZM28資訊網——每日最新資訊28at.com

WritableResource繼承了Resource接口,可以獲取到資源的輸出流,因為有的資源不僅可讀,還可寫,就比如一些本地文件的資源,往往都是可讀可寫的JZM28資訊網——每日最新資訊28at.com

Resource的實現很多,這里我舉幾個常見的:JZM28資訊網——每日最新資訊28at.com

  • FileSystemResource:讀取文件系統的資源
  • UrlResource:前面提到的Java的標準資源管理的封裝,底層就是通過URL來訪問資源
  • ClassPathResource:讀取classpath路徑下的資源
  • ByteArrayResource:讀取靜態字節數組的數據

比如,想要通過Spring的資源管理方式來訪問前面提到百度首頁網絡資源,就可以這么寫JZM28資訊網——每日最新資訊28at.com

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

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

2、資源加載

雖然Resource有很多實現,但是在實際使用中,可能無法判斷使用具體的哪個實現,所以Spring提供了ResourceLoader資源加載器來根據資源的類型來加載資源。JZM28資訊網——每日最新資訊28at.com

ResourceLoaderResourceLoaderJZM28資訊網——每日最新資訊28at.com

通過getResource方法,傳入一個路徑就可以加載到對應的資源,而這個路徑不一定是本地文件,可以是任何可加載的路徑。JZM28資訊網——每日最新資訊28at.com

ResourceLoader有個唯一的實現DefaultResourceLoader。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

比如對于上面的例子,就可以通過ResourceLoader來加載資源,而不用直接new具體的實現了。JZM28資訊網——每日最新資訊28at.com

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

除了ResourceLoader之外,還有一個ResourcePatternResolver可以加載資源。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

ResourcePatternResolver繼承了ResourceLoader。JZM28資訊網——每日最新資訊28at.com

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

ResourcePatternResolver只有一個實現PathMatchingResourcePatternResolver。JZM28資訊網——每日最新資訊28at.com

PathMatchingResourcePatternResolverPathMatchingResourcePatternResolverJZM28資訊網——每日最新資訊28at.com

3、小結

到這就講完了Spring的資源管理,這里總結一下本節大致的內容。JZM28資訊網——每日最新資訊28at.com

Java的標準資源管理:JZM28資訊網——每日最新資訊28at.com

  • URL
  • URLStreamHandler

Spring的資源管理:JZM28資訊網——每日最新資訊28at.com

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

Spring的資源管理在Spring中用的很多,比如在SpringBoot中,application.yml的文件就是通過ResourceLoader加載成Resource,之后再讀取文件的內容的。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

二、環境

上一節末尾舉的例子中提到,SpringBoot配置文件是通過ResourceLoader來加載配置文件,讀取文件的配置內容JZM28資訊網——每日最新資訊28at.com

那么當配置文件都加載完成之后,這個配置應該存到哪里,怎么能夠讀到呢?JZM28資訊網——每日最新資訊28at.com

這就引出了Spring框架中的一個關鍵概念,環境,它其實就是用于管理應用程序配置的。JZM28資訊網——每日最新資訊28at.com

1、Environment

Environment就是環境抽象出來的接口。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

Environment繼承PropertyResolverJZM28資訊網——每日最新資訊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提供的部分方法,這里簡單說一下上面方法的作用JZM28資訊網——每日最新資訊28at.com

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

所以Environment主要有一下幾種功能:JZM28資訊網——每日最新資訊28at.com

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

來個demoJZM28資訊網——每日最新資訊28at.com

先在application.yml的配置文件中加入配置JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

測試代碼如下:JZM28資訊網——每日最新資訊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屬性對應的值        String name = environment.getProperty("name");        System.out.println("name = " + name);    }}

啟動應用,獲取到ConfigurableEnvironment對象,再獲取到值。JZM28資訊網——每日最新資訊28at.com

ConfigurableEnvironment是Environment子接口,通過命名也可以知道,他可以對Environment進行一些功能的配置。JZM28資訊網——每日最新資訊28at.com

運行結果:JZM28資訊網——每日最新資訊28at.com

name = 三友的java日記

2、配置屬性源PropertySource

PropertySource是真正存配置的地方,屬于配置的來源,它提供了一個統一的訪問接口,使得應用程序可以以統一的方式獲取配置獲取到屬性。JZM28資訊網——每日最新資訊28at.com

PropertySourcePropertySourceJZM28資訊網——每日最新資訊28at.com

來個簡單demo:JZM28資訊網——每日最新資訊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);    }}

簡單說一下上面代碼的意思JZM28資訊網——每日最新資訊28at.com

  • 首先創建了一個map,就是配置來源,往里面添加了一個配置key-value
  • 創建了一個PropertySource,使用的實現是MapPropertySource,需要傳入配置map,所以最終獲取到屬性不用想就知道是從map中獲取的

最后成獲取到屬性JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

除了MapPropertySource之外,還有非常多的實現。JZM28資訊網——每日最新資訊28at.com

PropertySource實現PropertySource實現JZM28資訊網——每日最新資訊28at.com

比如CommandLinePropertySource,它其實就封裝了通過命令啟動時的傳遞的配置參數。JZM28資訊網——每日最新資訊28at.com

既然PropertySource才是真正存儲配置的地方,那么Environment獲取到的配置真正也就是從PropertySource獲取的,并且他們其實是一對多的關系。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

其實很好理解一對多的關系,因為一個應用程序的配置可能來源很多地方,比如在SpringBoot環境底下,除了我們自定義的配置外,還有比如系統環境配置等等,這些都可以通過Environment獲取到。JZM28資訊網——每日最新資訊28at.com

當從Environment中獲取配置的時候,會去遍歷所有的PropertySource,一旦找到配置key對應的值,就會返回。JZM28資訊網——每日最新資訊28at.com

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

這就是為什么,當你在配置文件配置username屬性時獲取到的卻是系統變量username對應的值,因為系統的PropertySource排在配置文件對應的PropertySource之前。JZM28資訊網——每日最新資訊28at.com

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

SpringBoot是通過PropertySourceLoader來解析配置文件的。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

load方法的第二個參數就是我們前面提到的資源接口Resource。JZM28資訊網——每日最新資訊28at.com

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

PropertySourceLoader默認有兩個實現,分別用來解析properties和yml格式的配置文件。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

此時,上面的圖就可以優化成這樣。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

三、類型轉換

在上一節介紹Environment時提到了它的getProperty(String key, Class<T> targetType)可以將配置的字符串轉換成對應的類型,那么他是如何轉換的呢?JZM28資訊網——每日最新資訊28at.com

這就跟本文要講的Spring類型轉換機制有關了。JZM28資訊網——每日最新資訊28at.com

1、類型轉換API

Spring類型轉換主要涉及到以下幾個api:JZM28資訊網——每日最新資訊28at.com

  • PropertyEditor
  • Converter
  • GenericConverter
  • ConversionService
  • TypeConverter

接下來我會來詳細介紹這幾個api的原理和他們之間的關系。JZM28資訊網——每日最新資訊28at.com

1.1、PropertyEditor

PropertyEditor并不是Spring提供的api,而是JDK提供的api,他的主要作用其實就是將String類型的字符串轉換成Java對象屬性值。JZM28資訊網——每日最新資訊28at.com

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

就拿項目中常用的@Value來舉例子,當我們通過@Value注解的方式將配置注入到字段時,大致步驟如下圖所示:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

  • 取出@Value配置的key
  • 根據@Value配置的key調用Environment的resolvePlaceholders(String text)方法,解析占位符,找到配置文件中對應的值
  • 調用PropertyEditor將對應的值轉換成注入的屬性字段類型,比如注入的字段類型是數字,那么就會將字符串轉換成數字

在轉換的過程中,Spring會先調用PropertyEditor的setAsText方法將字符串傳入,然后再調用getValue方法獲取轉換后的值。JZM28資訊網——每日最新資訊28at.com

Spring提供了很多PropertyEditor的實現,可以實現字符串到多種類型的轉換。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

在這么多實現中,有一個跟我們前面提到的Resource有關的實現ResourceEditor,它是將字符串轉換成Resource對象。JZM28資訊網——每日最新資訊28at.com

ResourceEditorResourceEditorJZM28資訊網——每日最新資訊28at.com

也就是說,可以直接通過@Value的方式直接注入一個Resource對象,就像下面這樣。JZM28資訊網——每日最新資訊28at.com

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

其實歸根到底,底層也是通過ResourceLoader來加載的,這個結論是不變的。JZM28資訊網——每日最新資訊28at.com

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

1.2、Converter

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

ConverterConverterJZM28資訊網——每日最新資訊28at.com

Converter是個接口,泛型S是被轉換的對象類型,泛型T是需要被轉成的類型。JZM28資訊網——每日最新資訊28at.com

同樣地,Spring也提供了很多Converter的實現。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

這些主要包括日期類型的轉換和String類型轉換成其它的類型。JZM28資訊網——每日最新資訊28at.com

1.3、GenericConverter

GenericConverter也是類型轉換的接口。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

這個接口的主要作用是可以處理帶有泛型類型的轉換,主要的就是面向集合數組轉換操作,從Spring默認提供的實現就可以看出。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

那Converter跟GenericConverter有什么關系呢?JZM28資訊網——每日最新資訊28at.com

這里我舉個例子,假設現在需要將將源集合Collection<String>轉換成目標集合Collection<Date>。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

假設現在有個String轉換成Date類型的Converter,咱就叫StringToDateConverter,那么整個轉換過程如下:JZM28資訊網——每日最新資訊28at.com

  • 首先會找到GenericConverter的一個實現CollectionToCollectionConverter,從名字也可以看出來,是將一個幾個轉換成另一個集合
  • 然后遍歷源集合Collection<String>,取出元素
  • 根據目標集合泛型Date,找到StringToDateConverter,將String轉換成Date,將轉換的Date存到一個新的集合
  • 返回這個新的集合,這樣就實現了集合到集合的轉換

所以通過這就可以看出Converter和GenericConverter其實是依賴關系JZM28資訊網——每日最新資訊28at.com

1.4、ConversionService

對于我們使用者來說,不論是Converter還是GenericConverter,其實都是類型轉換的,并且類型轉換的實現也很多,所以Spring為了方便我們使用Converter還是GenericConverter,提供了一個門面接口ConversionService。JZM28資訊網——每日最新資訊28at.com

ConversionServiceConversionServiceJZM28資訊網——每日最新資訊28at.com

我們可以直接通過ConversionService來進行類型轉換,而不需要面向具體的Converter或者是GenericConverter。JZM28資訊網——每日最新資訊28at.com

ConversionService有一個基本的實現GenericConversionService。JZM28資訊網——每日最新資訊28at.com

GenericConversionServiceGenericConversionServiceJZM28資訊網——每日最新資訊28at.com

同時GenericConversionService還實現了ConverterRegistry的接口。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

ConverterRegistry提供了對Converter和GenericConverter進行增刪改查的方法。JZM28資訊網——每日最新資訊28at.com

圖片ConverterRegistryJZM28資訊網——每日最新資訊28at.com

這樣就可以往ConversionService中添加Converter或者是GenericConverter了,因為最終還是通過Converter和GenericConverter來實現轉換的。JZM28資訊網——每日最新資訊28at.com

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

因為DefaultConversionService和ApplicationConversionService在創建的時候,會添加很多Spring自帶的Converter和GenericConverter,就不需要我們手動添加了。JZM28資訊網——每日最新資訊28at.com

1.5、TypeConverter

TypeConverter其實也是算是一個門面接口,他也定義了轉換方法。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

他是將PropertyEditor和ConversionService進行整合,方便我們同時使用PropertyEditor和ConversionService。JZM28資訊網——每日最新資訊28at.com

convertIfNecessary方法會去調用PropertyEditor和ConversionService進行類型轉換,值得注意的是,優先使用PropertyEditor進行轉換,如果沒有找到對應的PropertyEditor,會使用ConversionService進行轉換。JZM28資訊網——每日最新資訊28at.com

TypeConverter有個簡單的實現SimpleTypeConverter,這里來個簡單的demo。JZM28資訊網——每日最新資訊28at.com

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的實現。JZM28資訊網——每日最新資訊28at.com

小結

到這就講完了類型轉換的常見的幾個api,這里再簡單總結一下:JZM28資訊網——每日最新資訊28at.com

  • PropertyEditor:String轉換成目標類型
  • Converter:用于一個類型轉換成另一個類型
  • GenericConverter:用于處理泛型的轉換,主要用于集合
  • ConversionService:門面接口,內部會調用Converter和GenericConverter
  • TypeConverter:門面接口,內部會調用PropertyEditor和ConversionService

畫張圖來總結他們之間的關系:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

前面在舉@Value的例子時說,類型轉換是根據PropertyEditor來的,其實只說了一半,因為底層實際上是根據TypeConverter來轉換的,所以@Value類型轉換時也能使用ConversionService類轉換,所以那張圖實際上應該這么畫才算對。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

2、Environment中到底是如何進行類型轉換的?

這里我們回到開頭提到的話題,Environment中到底是如何進行類型轉換的,讓我們看看Environment類的接口體系。JZM28資訊網——每日最新資訊28at.com

Environment有個子接口ConfigurableEnvironment中,前面也提到過。JZM28資訊網——每日最新資訊28at.com

它繼承了ConfigurablePropertyResolver接口。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

而ConfigurablePropertyResolver有一個setConversionService方法。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

所以從這可以看出,Environment底層實際上是通過ConversionService實現類型轉換的。JZM28資訊網——每日最新資訊28at.com

這其實也就造成了一個問題,因為ConversionService和PropertyEditor屬于并列關系,那么就會導致Environment無法使用PropertyEditor來進行類型轉換,也就會喪失部分Spring提供的類型轉換功能,就比如無法通過Environment將String轉換成Resource對象,因為Spring沒有實現String轉換成Resource的Converter。JZM28資訊網——每日最新資訊28at.com

當然你可以自己實現一個String轉換成Resource的Converter,然后添加到ConversionService,之后Environment就支持String轉換成Resource了。JZM28資訊網——每日最新資訊28at.com

四、數據綁定

上一節我們講了類型轉換,而既然提到了類型轉換,那么就不得不提到數據綁定了,他們是密不可分的,因為在數據綁定時,往往都會伴隨著類型轉換,數據綁定的意思就是將一些配置屬性跟我們的Bean對象的屬性進行綁定。JZM28資訊網——每日最新資訊28at.com

不知你是否記得,在遠古的ssm時代,我們一般通過xml方式聲明Bean的時候,可以通過<property/>來設置Bean的屬性。JZM28資訊網——每日最新資訊28at.com

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

然后Spring在創建User的過程中,就會給username屬性設置為三友的java日記。JZM28資訊網——每日最新資訊28at.com

這就是數據綁定,將三友的java日記綁定到username這個屬性上。JZM28資訊網——每日最新資訊28at.com

數據綁定的核心api主要包括以下幾個:JZM28資訊網——每日最新資訊28at.com

  • PropertyValues
  • BeanWrapper
  • DataBinder

1、PropertyValues

這里我們先來講一下PropertyValue(注意沒有s)。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

顧明思議,PropertyValue就是就是封裝了屬性名和對應的屬性值,它就是數據綁定時屬性值的來源。JZM28資訊網——每日最新資訊28at.com

以前面的提到的xml創建Bean為例,Spring在啟動的時候會去解析xml中的<property/>標簽,然后將name和value封裝成PropertyValue。JZM28資訊網——每日最新資訊28at.com

當創建User這個Bean的時候,到了屬性綁定的階段的時候,就會取出PropertyValue,設置到User的username屬性上。JZM28資訊網——每日最新資訊28at.com

而PropertyValues,比PropertyValue多了一個s,也就是復數的意思,所以其實PropertyValues本質上就是PropertyValue的一個集合。JZM28資訊網——每日最新資訊28at.com

因為一個Bean可能有多個屬性配置,所以就用PropertyValues來保存。JZM28資訊網——每日最新資訊28at.com

2、BeanWrapper

BeanWrapper其實就數據綁定的核心api了,因為在Spring中涉及到數據綁定都是通過BeanWrapper來完成的,比如前面提到的Bean的屬性的綁定,就是通過BeanWrapper來的。JZM28資訊網——每日最新資訊28at.com

BeanWrapper是一個接口,他有一個唯一的實現BeanWrapperImpl。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

先來個demo。JZM28資訊網——每日最新資訊28at.com

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

結果:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

成功獲取到,說明設置成功。JZM28資訊網——每日最新資訊28at.com

BeanWrapperImpl也間接實現了TypeConverter接口。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

當然底層還是通過前面提到的ConversionService和PropertyEditor實現的。JZM28資訊網——每日最新資訊28at.com

所以當配置的類型跟屬性的類型不同時,就可以對配置的類型進行轉換,然后再綁定到屬性上。JZM28資訊網——每日最新資訊28at.com

這里簡單說一下數據綁定和@Value的異同,因為這兩者看起來好像是一樣的,但實際還是有點區別的。JZM28資訊網——每日最新資訊28at.com

相同點:JZM28資訊網——每日最新資訊28at.com

兩者都會涉及到類型轉換,@Value和數據綁定都會將值轉換成目標屬性對應的類型,并且都是通過TypeConverter來轉換的。JZM28資訊網——每日最新資訊28at.com

不同點:JZM28資訊網——每日最新資訊28at.com

  • 發生時機不同,@Value比數據綁定更早,當@Value都注入完成之后才會發生數據綁定(屬性賦值)。
  • 屬性賦值方式不同,@Value是通過反射來的,而是數據綁定是通過setter方法來的,如果沒有setter方法,屬性是沒辦法綁定的。

3、DataBinder

DataBinder也是用來進行數據綁定的,它的底層也是間接通過BeanWrapper來實現的數據綁定的。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

但是他相比于BeanWrapper多了一些功能,比如在數據綁定之后,可以對數據校驗,比如可以校驗字段的長度等等。JZM28資訊網——每日最新資訊28at.com

說到數據校驗,是不是想到了SpringMVC中的參數校驗,通過@Valid配合一些諸如@NotBlank、@NotNull等注解,實現優雅的參數校驗。JZM28資訊網——每日最新資訊28at.com

其實SpringMVC的參數校驗就是通過DataBinder來的,所以DataBinder其實在SpringMVC中用的比較多,但是在Spring中確用的很少。JZM28資訊網——每日最新資訊28at.com

如果你有興趣,可以翻一下SpringMVC中關于請求參數處理的HandlerMethodArgumentResolver的實現,里面有的實現會用到DataBinder(WebDataBinder)來進行數據請求參數跟實體類的數據綁定、類型轉換、數據校驗等等。JZM28資訊網——每日最新資訊28at.com

不知道你有沒有注意過,平時寫接口的時候,前端傳來的參數String類型的時間字符串無法通過Spring框架本身轉換成Date類型,有部分原因就是前面提到的Spring沒有相關的Converter實現。JZM28資訊網——每日最新資訊28at.com

總的來說,數據綁定在xml配置和SpringMVC中用的比較多的,并且數據綁定也是Spring Bean生命周期中一個很重要的環節。JZM28資訊網——每日最新資訊28at.com

五、泛型處理

Spring為了方便操作和處理泛型類型,提供了一個強大的工具類——ResolvableType。JZM28資訊網——每日最新資訊28at.com

泛型處理其實是一塊相對獨立的東西,因為它就只是一個工具類,只還不過這個工具類在Spring中卻是無處不在!JZM28資訊網——每日最新資訊28at.com

ResolvableType提供了有一套靈活的API,可以在運行時獲取和處理泛型類型等信息。JZM28資訊網——每日最新資訊28at.com

ResolvableTypeResolvableTypeJZM28資訊網——每日最新資訊28at.com

接下來就通過一個案例,來看一看如何通過ResolvableType快速簡單的獲取到泛型的。JZM28資訊網——每日最新資訊28at.com

首先,我聲明了一個MyMap類,繼承HashMap,第一個泛型參數是Integer類型,第二個泛型參數是List類型,List的泛型參數又是String。JZM28資訊網——每日最新資訊28at.com

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

接下來就來演示一下如何獲取到HashMap的泛型參數以及List的泛型參數。JZM28資訊網——每日最新資訊28at.com

第一步,先來通過ResolvableType#forClass方法創建一個MyMap類型對應的ResolvableType。JZM28資訊網——每日最新資訊28at.com

//創建MyMap對應的ResolvableTypeResolvableType myMapType = ResolvableType.forClass(MyMap.class);

因為泛型參數是在父類HashMap中,所以我們得獲取到父類HashMap對應的ResolvableType,通過ResolvableType#getSuperType()方法獲取。JZM28資訊網——每日最新資訊28at.com

//獲取父類HashMap對應的ResolvableTypeResolvableType hashMapType = myMapType.getSuperType();

接下來需要獲取HashMap的泛型參數對應的ResolvableType類型,可以通過ResolvableType#getGeneric(int... indexes)就可以獲取指定位置的泛型參數ResolvableType,方法參數就是指第幾個位置的泛型參數,從0開始。JZM28資訊網——每日最新資訊28at.com

比如獲取第一個位置的對應的ResolvableType類型。JZM28資訊網——每日最新資訊28at.com

//獲取第一個泛型參數對應的ResolvableTypeResolvableType firstGenericType = hashMapType.getGeneric(0);

現在有了第一個泛型參數的ResolvableType類型,只需要通過ResolvableType#resolve()方法就可以獲取到ResolvableType類型對應的class類型,這樣就可以獲取到一個泛型參數的class類型。JZM28資訊網——每日最新資訊28at.com

//獲取第一個泛型參數對應的ResolvableType對應的class類型,也就是Integer的class類型Class<?> firstGenericClass = firstGenericType.resolve();

如果你想獲取到HashMap第二個泛型參數的泛型類型,也就是List泛型類型就可以這么寫。JZM28資訊網——每日最新資訊28at.com

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

從上面的演示下來可以發現,其實每變化一步,其實就是獲取對應泛型或者是父類等等對應的ResolvableType,父類或者是泛型參數又可能有泛型之類的,只需要一步一步獲取就可以了,當需要獲取到具體的class類型的時候,通過ResolvableType#resolve()方法就行了。JZM28資訊網——每日最新資訊28at.com

除了上面提到的通過ResolvableType#forClass方法創建ResolvableType之外,還可以通過一下幾個方法創建:JZM28資訊網——每日最新資訊28at.com

  • forField(Field field):獲取字段類型對應的ResolvableType
  • forMethodReturnType(Method method):獲取方法返回值類型對應的ResolvableType
  • forMethodParameter(Method method, int parameterIndex):獲取方法某個位置方法參數對應的ResolvableType
  • forConstructorParameter(Constructor<?> constructor, int parameterIndex):獲取構造方法某個構造參數對應的ResolvableType

通過上面解釋可以看出,對于一個類方法參數,方法返回值,字段等等都可以獲取到對應的ResolvableTypeJZM28資訊網——每日最新資訊28at.com

六、國際化

國際化(Internationalization,簡稱i18n)也是Spring提供的一個核心功能,它其實也是一塊相對獨立的功能。JZM28資訊網——每日最新資訊28at.com

所謂的國際化,其實理解簡單點就是對于不同的地區國家,輸出的文本內容語言不同。JZM28資訊網——每日最新資訊28at.com

Spring的國際化其實主要是依賴Java中的國際化和文本處理方式。JZM28資訊網——每日最新資訊28at.com

1、Java中的國際化

Locale

Locale是Java提供的一個類,它可以用來標識不同的語言和地區,如en_US表示美國英語,zh_CN表示中國大陸中文等。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

目前Java已經窮舉了很多國家的地區Locale。JZM28資訊網——每日最新資訊28at.com

我們可以使用Locale類獲取系統默認的Locale,也可以手動設置Locale,以適應不同的語言環境。JZM28資訊網——每日最新資訊28at.com

ResourceBundle

ResourceBundle是一個加載本地資源的一個類,他可以根據傳入的Locale不同,加載不同的資源。JZM28資訊網——每日最新資訊28at.com

來個demo:JZM28資訊網——每日最新資訊28at.com

首先準備資源文件,資源文件通常是.properties文件,文件名命名規則如下:JZM28資訊網——每日最新資訊28at.com

basename_lang_country.properties

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

舉個例子,我們看看英語地區的Locale。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

從上圖可以看出,英語Locale的lang為en,country為空字符串,那么此時英語地區對應資源文件就可以命名為:basename_en.properties,由于country為空字符串,可以省略。JZM28資訊網——每日最新資訊28at.com

中國大陸Locale如下圖:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

此時文件就可以命為:basename_zh_CN.properties。JZM28資訊網——每日最新資訊28at.com

好了,現在既然知道了命名規則,我們就創建兩個文件,basename就叫message,一個英語,一個中文,放在classpath路徑下。JZM28資訊網——每日最新資訊28at.com

中文資源文件:message_zh_CN.properties,內容為:JZM28資訊網——每日最新資訊28at.com

name=三友的java日記

英文資源文件:message_en.properties,內容為:JZM28資訊網——每日最新資訊28at.com

name=sanyou's java diary

有了文件之后,就可以通過ResourceBundle#getBundle(String baseName,Locale locale)方法來獲取獲取ResourceBundle。JZM28資訊網——每日最新資訊28at.com

  • 第一個參數baseName就是我們的文件名中的basename,對于我們的demo來說,就是message
  • 第二個參數就是地區,根據地區的不同加載不同地區的文件

測試一下:JZM28資訊網——每日最新資訊28at.com

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

運行結果:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

其實運行結果可以看出,其實是成功獲取了,只不過中文亂碼了,這主要是因為ResourceBundle底層其實編碼是ISO-8859-1,所以會導致亂碼。JZM28資訊網——每日最新資訊28at.com

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

除了這種方式之外,其實還可以繼承ResourceBundle內部一個Control類。JZM28資訊網——每日最新資訊28at.com

ControlControlJZM28資訊網——每日最新資訊28at.com

重寫newBundle方法。JZM28資訊網——每日最新資訊28at.com

newBundlenewBundleJZM28資訊網——每日最新資訊28at.com

newBundle是創建ResourceBundle對應核心方法,重寫的時候你就可以隨心所欲讓它支持其它編碼方式。JZM28資訊網——每日最新資訊28at.com

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

Spring實際上就是通過這種方式擴展,支持不同編碼的,后面也有提到。JZM28資訊網——每日最新資訊28at.com

MessageFormat

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

MessageFormat對于將動態值插入到消息中非常有用,如歡迎消息、錯誤消息等。JZM28資訊網——每日最新資訊28at.com

先來個Demo:JZM28資訊網——每日最新資訊28at.com

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

解釋一下上面這段代碼:JZM28資訊網——每日最新資訊28at.com

  • 你好:{0}其實就是前面提到的消息的模板,{0}就是占位符,中間的0代表消息格式化的時候將提供的參數第一個參數替換占位符的值。
  • 張三就是提供的參數,你可以寫很多個,但是我們的demo只會取第一個參數,因為是{0}。

所以輸出結果為:JZM28資訊網——每日最新資訊28at.com

message = 你好:張三

成功格式化消息。JZM28資訊網——每日最新資訊28at.com

2、Spring國際化

Spring提供了一個國際化接口MessageSource。JZM28資訊網——每日最新資訊28at.com

MessageSourceMessageSourceJZM28資訊網——每日最新資訊28at.com

他有一個基于ResourceBundle + MessageFormat的實現ResourceBundleMessageSource。JZM28資訊網——每日最新資訊28at.com

ResourceBundleMessageSourceResourceBundleMessageSourceJZM28資訊網——每日最新資訊28at.com

他的本質可以在資源文件存儲消息的模板,然后通過MessageFormat來替換占位符,MessageSource的getMessage方法就可以傳遞具體的參數。JZM28資訊網——每日最新資訊28at.com

來個demo:JZM28資訊網——每日最新資訊28at.com

現在模擬登錄歡迎語句,對于不同的人肯定要有不同的名字,所以資源文件需要存模板,需要在不同的資源文件加不同的模板。JZM28資訊網——每日最新資訊28at.com

中文資源文件:message_zh_CN.propertiesJZM28資訊網——每日最新資訊28at.com

welcome=您好:{0}

英文資源文件:message_en.propertiesJZM28資訊網——每日最新資訊28at.com

welcome=hello:{0}

占位符,就是不同人不同名字。JZM28資訊網——每日最新資訊28at.com

測試代碼:JZM28資訊網——每日最新資訊28at.com

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

運行結果:JZM28資訊網——每日最新資訊28at.com

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

成功根據完成不同國家資源的加載和模板消息的格式化。JZM28資訊網——每日最新資訊28at.com

小結

這里來簡單總結一下這一小節說的內容。JZM28資訊網——每日最新資訊28at.com

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

七、BeanFactory

我們知道Spring的核心就是IOC和AOP,而BeanFactory就是大名鼎鼎的IOC容器,他可以幫我們生產對象。JZM28資訊網——每日最新資訊28at.com

1、BeanFactory接口體系

BeanFactory本身是一個接口。JZM28資訊網——每日最新資訊28at.com

BeanFactoryBeanFactoryJZM28資訊網——每日最新資訊28at.com

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

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

  • ListableBeanFactory
  • HierarchicalBeanFactory
  • ConfigurableBeanFactory
  • AutowireCapableBeanFactory

ListableBeanFactory

ListableBeanFactoryListableBeanFactoryJZM28資訊網——每日最新資訊28at.com

從提供的方法可以看出,提供了一些獲取集合的功能,比如有的接口可能有多個實現,通過這些方法就可以獲取這些實現對象的集合。JZM28資訊網——每日最新資訊28at.com

HierarchicalBeanFactory

HierarchicalBeanFactoryHierarchicalBeanFactoryJZM28資訊網——每日最新資訊28at.com

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

ConfigurableBeanFactory

ConfigurableBeanFactoryConfigurableBeanFactoryJZM28資訊網——每日最新資訊28at.com

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

AutowireCapableBeanFactory

圖片圖片JZM28資訊網——每日最新資訊28at.com

提供了自動裝配Bean的實現、屬性填充、初始化、處理獲取依賴注入對象的功能。JZM28資訊網——每日最新資訊28at.com

比如@Autowired最終就會調用AutowireCapableBeanFactory#resolveDependency處理注入的依賴。JZM28資訊網——每日最新資訊28at.com

其實從這里也可以看出,Spring在BeanFactory的接口設計上面還是基于不同的職責進行接口的劃分,其實不僅僅是在BeanFactory,前面提到的那些接口也基本符合這個原則。JZM28資訊網——每日最新資訊28at.com

2、BeanDefinition及其相關組件

BeanDefinition

BeanDefinition是Spring Bean創建環節中很重要的一個東西,它封裝了Bean創建過程中所需要的元信息。JZM28資訊網——每日最新資訊28at.com

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。JZM28資訊網——每日最新資訊28at.com

讀取BeanDefinition

讀取BeanDefinition大致分為以下幾類:JZM28資訊網——每日最新資訊28at.com

  • BeanDefinitionReader
  • ClassPathBeanDefinitionScanner

BeanDefinitionReader

BeanDefinitionReaderBeanDefinitionReaderJZM28資訊網——每日最新資訊28at.com

BeanDefinitionReader可以通過loadBeanDefinitions(Resource resource)方法來加載BeanDefinition,方法參數就是我們前面說的資源,比如可以將Bean定義在xml文件中,這個xml文件就是一個資源。JZM28資訊網——每日最新資訊28at.com

BeanDefinitionReader的相關實現:JZM28資訊網——每日最新資訊28at.com

  • XmlBeanDefinitionReader:讀取xml配置的Bean
  • PropertiesBeanDefinitionReader:讀取properties文件配置的Bean,是的,你沒看錯,Bean可以定義在properties文件配置中
  • AnnotatedBeanDefinitionReader:讀取通過注解定義的Bean,比如@Lazy注解等等,AnnotatedBeanDefinitionReader不是BeanDefinitionReader的實現,但是作用是一樣的

ClassPathBeanDefinitionScannerJZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

這個作用就是掃描指定包下通過@Component及其派生注解(@Service等等)注解定義的Bean,其實就是@ComponentScan注解的底層實現。JZM28資訊網——每日最新資訊28at.com

ClassPathBeanDefinitionScanner這個類其實在很多其它框架中都有使用到,因為這個類可以掃描指定包下,生成BeanDefinition,對于那些需要掃描包來生成BeanDefinition來說,用的很多。JZM28資訊網——每日最新資訊28at.com

比如說常見的MyBatis框架,他的注解@MapperScan可以掃描指定包下的Mapper接口,其實他也是通過繼承ClassPathBeanDefinitionScanner來掃描Mapper接口的。JZM28資訊網——每日最新資訊28at.com

BeanDefinitionRegistryBeanDefinitionRegistryJZM28資訊網——每日最新資訊28at.com

這個從命名就可以看出,是BeanDefinition的注冊中心,也就是用來保存BeanDefinition的。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

提供了BeanDefinition的增刪查的功能。JZM28資訊網——每日最新資訊28at.com

講到這里,就可以用一張圖來把前面提到東西關聯起來。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

  • 通過BeanDefinitionReader或者是ClassPathBeanDefinitionScanner為每一個Bean生成一個BeanDefinition
  • BeanDefinition生成之后,添加到BeanDefinitionRegistry中
  • 當從BeanFactory中獲取Bean時,會從BeanDefinitionRegistry中拿出需要創建的Bean對應的BeanDefinition,根據BeanDefinition的信息來生成Bean
  • 當生成的Bean是單例的時候,Spring會將Bean保存到SingletonBeanRegistry中,也就是平時說的三級緩存中的第一級緩存中,以免重復創建,需要使用的時候直接從SingletonBeanRegistry中查找

3、BeanFactory核心實現

前面提到的BeanFactory體系都是一個接口,那么BeanFactory的實現類是哪個類呢?JZM28資訊網——每日最新資訊28at.com

BeanFactory真正底層的實現類,其實就只有一個,那就是DefaultListableBeanFactory這個類,這個類以及父類真正實現了BeanFactory及其子接口的所有的功能。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

并且接口的實現上可以看出,他也實現了BeanDefinitionRegistry,也就是說,在底層的實現上,其實BeanFactory跟BeanDefinitionRegistry的實現是同一個實現類。JZM28資訊網——每日最新資訊28at.com

上面說了這么多,來個demo。JZM28資訊網——每日最新資訊28at.com

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

簡單說一下上面代碼的意思:JZM28資訊網——每日最新資訊28at.com

  • 創建一個BeanFactory,就是DefaultListableBeanFactory
  • 創建一個AnnotatedBeanDefinitionReader,構造參數是一個BeanDefinitionRegistry,因為BeanDefinitionReader需要把讀出來的BeanDefinition存到BeanDefinitionRegistry中,同時因為DefaultListableBeanFactory實現了BeanDefinitionRegistry,所以直接把beanFactory當做構造參數傳過去
  • 讀取當前類 BeanFactoryDemo 為一個Bean,讓Spring幫我們生成這個Bean
  • 后面就是獲取打印

運行結果:JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

成功獲取到我們注冊的Bean。JZM28資訊網——每日最新資訊28at.com

總結

本節主要講了實現IOC的幾個核心的組件。JZM28資訊網——每日最新資訊28at.com

BeanFactory及其接口體系:JZM28資訊網——每日最新資訊28at.com

  • ListableBeanFactory
  • HierarchicalBeanFactory
  • ConfigurableBeanFactory
  • AutowireCapableBeanFactory

BeanDefinition及其相關組件:JZM28資訊網——每日最新資訊28at.com

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

BeanFactory核心實現:JZM28資訊網——每日最新資訊28at.com

  • DefaultListableBeanFactory:IOC容器,同時實現了BeanDefinitionRegistry接口

八、ApplicationContext

終于講到了ApplicationContext,因為前面說的那么多其實就是為ApplicationContext做鋪墊的。JZM28資訊網——每日最新資訊28at.com

先來看看ApplicationContext的接口。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

你會驚訝地發現,ApplicationContext繼承的幾個接口,除了EnvironmentCapable和ApplicationEventPublisher之外,其余都是前面說的。JZM28資訊網——每日最新資訊28at.com

EnvironmentCapable這個接口比較簡單,提供了獲取Environment的功能。JZM28資訊網——每日最新資訊28at.com

EnvironmentCapableEnvironmentCapableJZM28資訊網——每日最新資訊28at.com

說明了可以從ApplicationContext中獲取到Environment,所以EnvironmentCapable也算是前面說過了。JZM28資訊網——每日最新資訊28at.com

至于ApplicationEventPublisher我們留到下一節說。JZM28資訊網——每日最新資訊28at.com

ApplicationContext也繼承了ListableBeanFactory和HierarchicalBeanFactory,也就說明ApplicationContext其實他也是一個BeanFactory,所以說ApplicationContext是IOC容器的說法也沒什么毛病,但是由于他還繼承了其它接口,功能比BeanFactory多多了。JZM28資訊網——每日最新資訊28at.com

所以,ApplicationContext是一個集萬千功能為一身的接口,一旦你獲取到了ApplicationContext(可以@Autowired注入),你就可以用來獲取Bean、加載資源、獲取環境,還可以國際化一下,屬實是個王炸。JZM28資訊網——每日最新資訊28at.com

雖然ApplicationContext繼承了這些接口,但是ApplicationContext對于接口的實現是通過一種委派的方式,而真正的實現都是我們前面說的那些實現。JZM28資訊網——每日最新資訊28at.com

什么叫委派呢,咱寫一個例子你就知道了。JZM28資訊網——每日最新資訊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);    }    }

如上,其實是一段偽代碼。JZM28資訊網——每日最新資訊28at.com

因為ApplicationContext繼承了ResourcePatternResolver接口,所以我實現了getResources方法,但是真正的實現其實是交給變量中的PathMatchingResourcePatternResolver來實現的,這其實就是委派,不直接實現,而是交給其它真正實現了這個接口的類來處理。JZM28資訊網——每日最新資訊28at.com

同理,ApplicationContext對于BeanFactory接口的實現其實最終也是交由DefaultListableBeanFactory來委派處理的。JZM28資訊網——每日最新資訊28at.com

委派這種方式在Spring內部還是用的非常多的,前面提到的某些接口在的實現上也是通過委派的方式來的。JZM28資訊網——每日最新資訊28at.com

ApplicationContext有一個子接口,ConfigurableApplicationContextJZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

從提供的方法看出,就是可以對ApplicationContext進行配置,比如設置Environment,同時也能設置parent,說明了ApplicationContext也有子父的概念。JZM28資訊網——每日最新資訊28at.com

我們已經看到了很多以Configurable開頭的接口,這就是命名習慣,表示了可配置的意思,提供的都是set、add之類的方法。JZM28資訊網——每日最新資訊28at.com

ApplicationContext的實現很多,但是他有一個非常重要的抽象實現AbstractApplicationContext,因為其它的實現都是繼承這個抽象實現。JZM28資訊網——每日最新資訊28at.com

AbstractApplicationContextAbstractApplicationContextJZM28資訊網——每日最新資訊28at.com

這個類主要是實現了一些繼承的接口方法,通過委派的方式,比如對于BeanFactory接口的實現。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

并且AbstractApplicationContext這個類也實現了一個非常核心的refresh方法。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

所有的ApplicationContext在創建之后必須調用這個refresh方法之后才能使用,至于這個方法干了哪些事,后面有機會再寫一篇文章來著重扒一扒。JZM28資訊網——每日最新資訊28at.com

九、事件

上一小節在說ApplicationContext繼承的接口的時候,我們留下了一個懸念,那就是ApplicationEventPublisher的作用,而ApplicationEventPublisher就跟本節要說的事件有關。JZM28資訊網——每日最新資訊28at.com

Spring事件是一種觀察者模式的實現,他的作用主要是用來解耦合的。JZM28資訊網——每日最新資訊28at.com

當發生了某件事,只要發布一個事件,對這個事件的監聽者(觀察者)就可以對事件進行響應或者處理。JZM28資訊網——每日最新資訊28at.com

舉個例子來說,假設發生了火災,可能需要打119、救人,那么就可以基于事件的模型來實現,只需要打119、救人監聽火災的發生就行了,當發生了火災,通知這些打119、救人去觸發相應的邏輯操作。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

1、什么是Spring Event 事件

Spring Event 事件就是Spring實現了這種事件模型,你只需要基于Spring提供的API進行擴展,就可以輕易地完成事件的發布與訂閱。JZM28資訊網——每日最新資訊28at.com

Spring事件相關api主要有以下幾個:JZM28資訊網——每日最新資訊28at.com

  • ApplicationEvent
  • ApplicationListener
  • ApplicationEventPublisher

ApplicationEvent

ApplicationEventApplicationEventJZM28資訊網——每日最新資訊28at.com

事件的父類,所有具體的事件都得繼承這個類,構造方法的參數是這個事件攜帶的參數,監聽器就可以通過這個參數來進行一些業務操作。JZM28資訊網——每日最新資訊28at.com

ApplicationListener

ApplicationListenerApplicationListenerJZM28資訊網——每日最新資訊28at.com

事件監聽的接口,泛型是需要監聽的事件類型,子類需要實現onApplicationEvent,參數就是監聽的事件類型,onApplicationEvent方法的實現就代表了對事件的處理,當事件發生時,Spring會回調onApplicationEvent方法的實現,傳入發布的事件。JZM28資訊網——每日最新資訊28at.com

ApplicationEventPublisher

ApplicationEventPublisherApplicationEventPublisherJZM28資訊網——每日最新資訊28at.com

上一小節留下來的接口,事件發布器,通過publishEvent方法就可以發布一個事件,然后就可以觸發監聽這個事件的監聽器的回調。JZM28資訊網——每日最新資訊28at.com

ApplicationContext繼承了ApplicationEventPublisher,說明只要有ApplicationContext就可以來發布事件了。JZM28資訊網——每日最新資訊28at.com

話不多說,上代碼

就以上面的火災為例。JZM28資訊網——每日最新資訊28at.com

創建一個火災事件類JZM28資訊網——每日最新資訊28at.com

火災事件類繼承ApplicationEventJZM28資訊網——每日最新資訊28at.com

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

創建火災事件的監聽器JZM28資訊網——每日最新資訊28at.com

打119的火災事件的監聽器:JZM28資訊網——每日最新資訊28at.com

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

救人的火災事件的監聽器:JZM28資訊網——每日最新資訊28at.com

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

事件和對應的監聽都有了,接下來進行測試:JZM28資訊網——每日最新資訊28at.com

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事件JZM28資訊網——每日最新資訊28at.com

運行結果:JZM28資訊網——每日最新資訊28at.com

打119救人

控制臺打印出了結果,觸發了監聽。JZM28資訊網——每日最新資訊28at.com

如果現在需要對火災進行救火,那么只需要去監聽FireEvent,實現救火的邏輯,注入到Spring容器中,就可以了,其余的代碼根本不用動。JZM28資訊網——每日最新資訊28at.com

2、Spring內置的事件

Spring內置的事件很多,這里我羅列幾個:JZM28資訊網——每日最新資訊28at.com

事件類型JZM28資訊網——每日最新資訊28at.com

觸發時機JZM28資訊網——每日最新資訊28at.com

ContextRefreshedEventJZM28資訊網——每日最新資訊28at.com

在調用ConfigurableApplicationContext 接口中的refresh()方法時觸發JZM28資訊網——每日最新資訊28at.com

ContextStartedEventJZM28資訊網——每日最新資訊28at.com

在調用ConfigurableApplicationContext的start()方法時觸發JZM28資訊網——每日最新資訊28at.com

ContextStoppedEventJZM28資訊網——每日最新資訊28at.com

在調用ConfigurableApplicationContext的stop()方法時觸發JZM28資訊網——每日最新資訊28at.com

ContextClosedEventJZM28資訊網——每日最新資訊28at.com

當ApplicationContext被關閉時觸發該事件,也就是調用close()方法觸發JZM28資訊網——每日最新資訊28at.com

在ApplicationContext(Spring容器)啟動的過程中,Spring會發布這些事件,如果你需要這Spring容器啟動的某個時刻進行什么操作,只需要監聽對應的事件即可。JZM28資訊網——每日最新資訊28at.com

3、Spring事件的傳播特性

Spring事件的傳播是什么意思呢?JZM28資訊網——每日最新資訊28at.com

前面提到,ApplicationContext有子父容器的概念,而Spring事件的傳播就是指當通過子容器發布一個事件之后,不僅可以觸發在這個子容器的事件監聽器,還可以觸發在父容器的這個事件的監聽器。JZM28資訊網——每日最新資訊28at.com

上代碼

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關聯起來,最后通過子容器,發布了著火的事件。JZM28資訊網——每日最新資訊28at.com

運行結果:JZM28資訊網——每日最新資訊28at.com

救人打119

從打印的日志,的確可以看出,雖然是子容器發布了著火的事件,但是父容器的監聽器也成功監聽了著火事件。JZM28資訊網——每日最新資訊28at.com

而這種傳播特性,從源碼中也可以看出來:JZM28資訊網——每日最新資訊28at.com

事件傳播源碼事件傳播源碼JZM28資訊網——每日最新資訊28at.com

如果父容器不為空,就會通過父容器再發布一次事件。JZM28資訊網——每日最新資訊28at.com

傳播特性的一個小坑

前面說過,在Spring容器啟動的過程,會發布很多事件,如果你需要有相應的擴展,可以監聽這些事件。JZM28資訊網——每日最新資訊28at.com

但是,不知道你有沒有遇到過這么一個坑,就是在SpringCloud環境下,你監聽這些Spring事件的監聽器會執行很多次,這其實就是跟傳播特性有關。JZM28資訊網——每日最新資訊28at.com

在SpringCloud環境下,為了使像FeignClient和RibbonClient這些不同服務的配置相互隔離,會為每個FeignClient或者是RibbonClient創建一個Spring容器,而這些容器都有一個公共的父容器,那就是SpringBoot項目啟動時創建的容器。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

假設你監聽了容器刷新的ContextRefreshedEvent事件,那么你自己寫的監聽器就在SpringBoot項目啟動時創建的容器中。JZM28資訊網——每日最新資訊28at.com

每個服務的配置容器他也是Spring容器,啟動時也會發布ContextRefreshedEvent,那么由于傳播特性的關系,你的事件監聽器就會觸發執行多次。JZM28資訊網——每日最新資訊28at.com

圖片圖片JZM28資訊網——每日最新資訊28at.com

如何解決這個坑呢?JZM28資訊網——每日最新資訊28at.com

你可以進行判斷這些監聽器有沒有執行過,比如加一個判斷的標志;或者是監聽類似的事件,比如ApplicationStartedEvent事件,這種事件是在SpringBoot啟動中發布的事件,而子容器不是SpringBoot,所以不會多次發這種事件,也就會只執行一次。JZM28資訊網——每日最新資訊28at.com

十、總結

到這到這整篇文章終于寫完了,這里再來簡單地回顧一下本文說的幾個核心功能:JZM28資訊網——每日最新資訊28at.com

  • 資源管理:對資源進行統一的封裝,方便資源讀取和管理
  • 環境:對容器或者是項目的配置進行管理
  • 類型轉換:將一種類型轉換成另一種類型
  • 數據綁定:將數據跟對象的屬性進行綁定,綁定之前涉及到類型轉換
  • 泛型處理:一個操作泛型的工具類,Spring中到處可見
  • 國際化:對Java的國際化進行了統一的封裝
  • BeanFactory:IOC容器
  • ApplicationContext:一個集萬千功能于一身的王炸接口,也可以說是IOC容器
  • 事件:Spring提供的基于觀察者模式實現的解耦合利器

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

最后,我怕你文章看得過于入迷,所以再來重復一遍,如果本篇文章對你有所幫助,還請多多點贊、轉發、在看,非常感謝!!JZM28資訊網——每日最新資訊28at.com

哦,真差點就忘了,本文所有demo代碼都在這了JZM28資訊網——每日最新資訊28at.com

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

參考資料:JZM28資訊網——每日最新資訊28at.com

[1].《極客時間--小馬哥講Spring核心編程思想》JZM28資訊網——每日最新資訊28at.com

[2].https://blog.csdn/zzuhkp/article/details/119455964 JZM28資訊網——每日最新資訊28at.com

[3].https://blog.csdn/zzuhkp/article/details/119455948JZM28資訊網——每日最新資訊28at.com

[4].https://blog.csdn/u010086122/article/details/81566515JZM28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-133-0.html三萬字盤點 Spring 九大核心基礎功能

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 深度探索 Elasticsearch 8.X:function_score 參數解讀與實戰案例分析

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

標簽:
  • 熱門焦點
  • 小米降噪藍牙耳機Necklace分享:聽一首歌 讀懂一個故事

    在今天下午的小米Civi 2新品發布會上,小米還帶來了一款新的降噪藍牙耳機Necklace,我們也在發布結束的第一時間給大家帶來這款耳機的簡單分享。現在大家能見到最多的藍牙耳機
  • CSS單標簽實現轉轉logo

    轉轉品牌升級后更新了全新的Logo,今天我們用純CSS來實現轉轉的新Logo,為了有一定的挑戰性,這里我們只使用一個標簽實現,將最大化的使用CSS能力完成Logo的繪制與動畫效果。新logo
  • 一年經驗在二線城市面試后端的經驗分享

    忠告這篇文章只適合2年內工作經驗、甚至沒有工作經驗的朋友閱讀。如果你是2年以上工作經驗,請果斷劃走,對你沒啥幫助~主人公這篇文章內容來自 「升職加薪」星球星友 的投稿,坐
  • 十個簡單但很有用的Python裝飾器

    裝飾器(Decorators)是Python中一種強大而靈活的功能,用于修改或增強函數或類的行為。裝飾器本質上是一個函數,它接受另一個函數或類作為參數,并返回一個新的函數或類。它們通常用
  • Temu起訴SHEIN,跨境電商戰事升級

    來源 | 伯虎財經(bohuFN)作者 | 陳平安日前據外媒報道,拼多多旗下跨境電商平臺Temu正對競爭對手SHEIN提起新訴訟,訴狀稱Shein&ldquo;利用市場支配力量強迫服裝廠商與之簽訂獨家
  • 微博大門常打開,迎接海外畫師漂洋東渡

    作者:互聯網那些事&ldquo;起猛了,我能看得懂日語了&rdquo;。&ldquo;為什么日本人說話我能聽懂?&rdquo;&ldquo;中文不像中文,日語不像日語,但是我竟然看懂了&rdquo;&hellip;&hell
  • 三星獲批量產iPhone 15全系屏幕:蘋果史上最驚艷直屏

    按照慣例,蘋果將繼續在今年9月舉辦一年一度的秋季新品發布會,有傳言稱發布會將于9月12日舉行,屆時全新的iPhone 15系列將正式與大家見面,不出意外的話
  • “買真退假” 這種“羊毛”不能薅

    □ 法治日報 記者 王春   □ 本報通訊員 胡佳麗  2020年初,還在上大學的小東加入了一個大學生兼職QQ群。群主&ldquo;七王&rdquo;在群里介紹一些刷單賺
  • 外交部:美方應停止在網絡安全問題上不負責任地指責他國

      中國外交部今天(16日)舉行例行記者會。會上,有記者問,美國情報官員稱,他們正在阻攔來自中國以及其他國家的黑客獲取相關科研成果。 中方對此有何評論?對此
Top