點選「 IT碼徒 」, 關註,置頂 公眾號
每日技術幹貨,第一時間送達!
1
基礎概念
1.1. 不同版本的 Spring Framework 有哪些主要功能?
Version | Feature |
---|---|
Spring 2.5 | 釋出於 2007 年。這是第一個支持註解的版本。 |
Spring 3.0 | 釋出於 2009 年。它完全利用了 Java5 中的改進,並為 JEE6 提供了支持。 |
Spring 4.0 | 釋出於 2013 年。這是第一個完全支持 JAVA8 的版本。 |
1.2. 什麽是 Spring Framework?
Spring 是一個開源套用框架,旨在降低應用程式開發的復雜度。
它是輕量級、松散耦合的。
它具有分層體系結構,允許使用者選擇元件,同時還為 J2EE 應用程式開發提供了一個有凝聚力的框架。
它可以整合其他框架,如 Structs、Hibernate、EJB 等,所以又稱為框架的框架。
1.3. 列舉 Spring Framework 的優點。
由於 Spring Frameworks 的分層架構,使用者可以自由選擇自己需要的元件。
Spring Framework 支持 POJO(Plain Old Java Object) 編程,從而具備持續整合和可測試性。
由於依賴註入和控制反轉,JDBC 得以簡化。
它是開源免費的。
1.4. Spring Framework 有哪些不同的功能?
輕量級 - Spring 在程式碼量和透明度方面都很輕便。
IOC - 控制反轉
AOP - 面向切面編程可以將套用業務邏輯和系統服務分離,以實作高內聚。
容器 - Spring 負責建立和管理物件(Bean)的生命周期和配置。
MVC - 對 web 套用提供了高度可配置性,其他框架的整合也十分方便。
事務管理 - 提供了用於事務管理的通用抽象層。Spring 的事務支持也可用於容器較少的環境。
JDBC 異常 - Spring 的 JDBC 抽象層提供了一個異常階層,簡化了錯誤處理策略。
1.5. Spring Framework 中有多少個模組,它們分別是什麽?
Spring 核心容器 – 該層基本上是 Spring Framework 的核心。它包含以下模組:
Spring Core
Spring Bean
SpEL (Spring Expression Language)
Spring Context
數據存取/整合 – 該層提供與資料庫互動的支持。它包含以下模組:
JDBC (Java DataBase Connectivity)
ORM (Object Relational Mapping)
OXM (Object XML Mappers)
JMS (Java Messaging Service)
Transaction
Web – 該層提供了建立 Web 應用程式的支持。它包含以下模組:
Web
Web – Servlet
Web – Socket
Web – Portlet
AOP – 該層支持面向切面編程
Instrumentation – 該層為類檢測和類載入器實作提供支持。
Test – 該層為使用 JUnit 和 TestNG 進行測試提供支持。
幾個雜項模組 :
Messaging – 該模組為 STOMP 提供支持。它還支持註解編程模型,該模型用於從 WebSocket 客戶端路由和處理 STOMP 訊息
Aspects – 該模組為與 AspectJ 的整合提供支持。
1.6. 什麽是 Spring 配置檔?
Spring 配置檔是 XML 檔。該檔主要包含類資訊。它描述了這些類是如何配置以及相互引入的。但是,XML 配置檔冗長且更加幹凈。如果沒有正確規劃和編寫,那麽在大計畫中管理變得非常困難。
1.7. Spring 應用程式有哪些不同元件?
Spring 套用一般有以下元件:
介面 - 定義功能。
Bean 類 - 它包含內容,setter 和 getter 方法,函式等。
Spring 面向切面編程 (AOP) - 提供面向切面編程的功能。
Bean 配置檔 - 包含類的資訊以及如何配置它們。
使用者程式 - 它使用介面。
1.8. 使用 Spring 有哪些方式?
使用 Spring 有以下方式:
作為一個成熟的 Spring Web 應用程式。
作為第三方 Web 框架,使用 Spring Frameworks 中間層。
用於遠端使用。
作為企業級 Java Bean,它可以包裝現有的 POJO(Plain Old Java Objects)。
2
依賴註入(Ioc)
2.1. 什麽是 Spring IOC 容器?
Spring 框架的核心是 Spring 容器。容器建立物件,將它們裝配在一起,配置它們並管理它們的完整生命周期。Spring 容器使用依賴註入來管理組成應用程式的元件。容器透過讀取提供的配置後設資料來接收物件進行例項化,配置和組裝的指令。該後設資料可以透過 XML,Java 註解或 Java 程式碼提供。
2.2. 什麽是依賴註入?
在依賴註入中,您不必建立物件,但必須描述如何建立它們。您不是直接在程式碼中將元件和服務連線在一起,而是描述配置檔中哪些元件需要哪些服務。由 IoC 容器將它們裝配在一起。
2.3. 可以透過多少種方式完成依賴註入?
通常,依賴註入可以透過三種方式完成,即:
建構函式註入
setter 註入
介面註入
在 Spring Framework 中,僅使用建構函式和 setter 註入。
2.4. 區分建構函式註入和 setter 註入
建構函式註入 | setter 註入 |
---|---|
沒有部份註入 | 有部份註入 |
不會覆蓋 setter 內容 | 會覆蓋 setter 內容 |
任意修改都會建立一個新例項 | 任意修改不會建立一個新例項 |
適用於設定很多內容 | 適用於設定少量內容 |
2.5. spring 中有多少種 IOC 容器?
BeanFactory - BeanFactory 就像一個包含 bean 集合的工廠類。它會在客戶端要求時例項化 bean。
ApplicationContext - ApplicationContext 介面擴充套件了 BeanFactory 介面。它在 BeanFactory 基礎上提供了一些額外的功能。
2.6. 區分 BeanFactory 和 ApplicationContext
BeanFactory | ApplicationContext |
---|---|
它使用懶載入 | 它使用即時載入 |
它使用語法顯式提供資源物件 | 它自己建立和管理資源物件 |
不支持國際化 | 支持國際化 |
不支持基於依賴的註解 | 支持基於依賴的註解 |
2.7. 列舉 IoC 的一些好處
IoC 的一些好處是:
它將最小化應用程式中的程式碼量。
它將使您的應用程式易於測試,因為它不需要單元測試用例中的任何單例或 JNDI 尋找機制。
它以最小的影響和最少的侵入機制促進松耦合。
它支持即時的例項化和延遲載入服務。
2.8. Spring IoC 的實作機制
Spring 中的 IoC 的實作原理就是工廠模式加反射機制。
範例:
interfaceFruit{
publicabstractvoideat();
}
classAppleimplementsFruit{
publicvoideat(){
System.out.println("Apple");
}
}
classOrangeimplementsFruit{
publicvoideat(){
System.out.println("Orange");
}
}
classFactory{
publicstatic Fruit getInstance(String className){
Fruit f=null;
try {
f=(Fruit) class.forName( className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
classClient{
publicstaticvoidmain(String[] a){
Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
if(f!=null){
f.eat();
}
}
}
3
Beans
3.1. 什麽是 spring bean?
它們是構成使用者應用程式主幹的物件。
Bean 由 Spring IoC 容器管理。
它們由 Spring IoC 容器例項化,配置,裝配和管理。
Bean 是基於使用者提供給容器的配置後設資料建立。
3.2. spring 提供了哪些配置方式?
基於 xml 配置
bean 所需的依賴項和服務在 XML 格式的配置檔中指定。這些配置檔通常包含許多 bean 定義和特定於應用程式的配置選項。它們通常以 bean 標簽開頭。例如:
<bean id="studentbean" class="org.edureka.firstSpring.StudentBean">
<propertyname="name"value="Edureka"></property>
</bean>
基於註解配置
您可以透過在相關的類,方法或欄位聲明上使用註解,將 bean 配置為元件類本身,而不是使用 XML 來描述 bean 裝配。預設情況下,Spring 容器中未開啟註解裝配。因此,您需要在使用它之前在 Spring 配置檔中啟用它。例如:
<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
基於 Java API 配置
Spring 的 Java 配置是透過使用 @Bean 和 @Configuration 來實作。
@Bean 註解扮演與 <bean /> 元素相同的角色。
@Configuration 類允許透過簡單地呼叫同一個類中的其他 @Bean 方法來定義 bean 間依賴關系。
例如:
@Configuration
public classStudentConfig{
@Bean
public StudentBean myStudent(){
returnnew StudentBean();
}
}
3.3. spring 支持集中 bean scope?
Spring bean 支持 5 種 scope:
Singleton - 每個 Spring IoC 容器僅有一個單例項。
Prototype - 每次請求都會產生一個新的例項。
Request - 每一次 HTTP 請求都會產生一個新的例項,並且該 bean 僅在當前 HTTP 請求內有效。
Session - 每一次 HTTP 請求都會產生一個新的 bean,同時該 bean 僅在當前 HTTP session 內有效。
Global-session - 類似於標準的 HTTP Session 作用域,不過它僅僅在基於 portlet 的 web 套用中才有意義。Portlet 規範定義了全域 Session 的概念,它被所有構成某個 portlet web 套用的各種不同的 portlet 所共享。在 global session 作用域中定義的 bean 被限定於全域 portlet Session 的生命周期範圍內。如果你在 web 中使用 global session 作用域來標識 bean,那麽 web 會自動當成 session 型別來使用。
僅當使用者使用支持 Web 的 ApplicationContext 時,最後三個才可用。更多spring內容
3.4. spring bean 容器的生命周期是什麽樣的?
spring bean 容器的生命周期流程如下:
Spring 容器根據配置中的 bean 定義中例項化 bean
Spring 使用依賴註入填充所有內容,如 bean 中所定義的配置。
如果 bean 實作 BeanNameAware 介面,則工廠透過傳遞 bean 的 ID 來呼叫 setBeanName()。
如果 bean 實作 BeanFactoryAware 介面,工廠透過傳遞自身的例項來呼叫 setBeanFactory()。
如果存在與 bean 關聯的任何 BeanPostProcessors,則呼叫 preProcessBeforeInitialization() 方法。
如果為 bean 指定了 init 方法( <bean>的 init-method 內容),那麽將呼叫它。
最後,如果存在與 bean 關聯的任何 BeanPostProcessors,則將呼叫 postProcessAfterInitialization() 方法。
如果 bean 實作 DisposableBean 介面,當 spring 容器關閉時,會呼叫 destory()。
如果為 bean 指定了 destroy 方法( <bean>的 destroy-method 內容),那麽將呼叫它。
3.5. 什麽是 spring 的內部 bean?
只有將 bean 用作另一個 bean 的內容時,才能將 bean 聲明為內部 bean。為了定義 bean,Spring 的基於 XML 的配置後設資料在 <property> 或 <constructor-arg> 中提供了 <bean> 元素的使用。內部 bean 總是匿名的,它們總是作為原型。
例如,假設我們有一個 Student 類,其中參照了 Person 類。這裏我們將只建立一個 Person 類例項並在 Student 中使用它。
Student.java
public class Student {
private Person person;
//Setters and Getters
}
public class Person {
private String name;
private String address;
//Setters and Getters
}
bean.xml
<beanid=「StudentBean" class="com.edureka.Student">
<propertyname="person">
<!--This is inner bean -->
<bean class="com.edureka.Person">
<propertyname="name"value=「Scott"></property>
<propertyname="address"value=「Bangalore"></property>
</bean>
</property>
</bean>
3.6. 什麽是 spring 裝配
當 bean 在 Spring 容器中組合在一起時,它被稱為裝配或 bean 裝配。Spring 容器需要知道需要什麽 bean 以及容器應該如何使用依賴註入來將 bean 繫結在一起,同時裝配 bean。
3.7. 自動裝配有哪些方式?
Spring 容器能夠自動裝配 bean。也就是說,可以透過檢查 BeanFactory 的內容讓 Spring 自動解析 bean 的協作者。
自動裝配的不同模式:
no - 這是預設設定,表示沒有自動裝配。應使用顯式 bean 參照進行裝配。
byName - 它根據 bean 的名稱註入物件依賴項。它匹配並裝配其內容與 XML 檔中由相同名稱定義的 bean。
byType - 它根據型別註入物件依賴項。如果內容的型別與 XML 檔中的一個 bean 名稱匹配,則匹配並裝配內容。
建構函式 - 它透過呼叫類的建構函式來註入依賴項。它有大量的參數。
autodetect - 首先容器嘗試透過建構函式使用 autowire 裝配,如果不能,則嘗試透過 byType 自動裝配。
3.8. 自動裝配有什麽局限?
覆蓋的可能性 - 您始終可以使用<constructor-arg> 和 <property>設定指定依賴項,這將覆蓋自動裝配。
基本後設資料型別 - 簡單內容(如原數據型別,字串和類)無法自動裝配。
令人困惑的性質 - 總是喜歡使用明確的裝配,因為自動裝配不太精確。
4
註解
4.1. 你用過哪些重要的 Spring 註解?
@Controller - 用於 Spring MVC 計畫中的控制器類。
@Service - 用於服務類。
@RequestMapping - 用於在控制器處理常式方法中配置 URI 對映。
@ResponseBody - 用於發送 Object 作為響應,通常用於發送 XML 或 JSON 數據作為響應。
@PathVariable - 用於將動態值從 URI 對映到處理常式方法參數。
@Autowired - 用於在 spring bean 中自動裝配依賴項。
@Qualifier - 使用 @Autowired 註解,以避免在存在多個 bean 型別例項時出現混淆。
@Scope - 用於配置 spring bean 的範圍。
@Configuration,@ComponentScan 和 @Bean - 用於基於 java 的配置。
@Aspect,@Before,@After,@Around,@Pointcut - 用於切面編程(AOP)。
4.2. 如何在 spring 中啟動註解裝配?
預設情況下,Spring 容器中未開啟註解裝配。因此,要使用基於註解裝配,我們必須透過配置 <context:annotation-config /> 元素在 Spring 配置檔中啟用它。
4.3. @Component, @Controller, @Repository, @Service 有何區別?
@Component :這將 java 類標記為 bean。它是任何 Spring 管理元件的通用構造型。spring 的元件掃描機制現在可以將其拾取並將其拉入應用程式環境中。
@Controller :這將一個類標記為 Spring Web MVC 控制器。標有它的 Bean 會自動匯入到 IoC 容器中。
@Service :此註解是元件註解的特化。它不會對 @Component 註解提供任何其他行為。您可以在服務層類中使用 @Service 而不是 @Component,因為它以更好的方式指定了意圖。
@Repository :這個註解是具有類似用途和功能的 @Component 註解的特化。它為 DAO 提供了額外的好處。它將 DAO 匯入 IoC 容器,並使未經檢查的異常有資格轉換為 Spring DataAccessException。
4.4. @Required 註解有什麽用?
@Required 套用於 bean 內容 setter 方法。此註解僅指示必須在配置時使用 bean 定義中的顯式內容值或使用自動裝配填充受影響的 bean 內容。如果尚未填充受影響的 bean 內容,則容器將丟擲 BeanInitializationException。
範例:
public classEmployee {
private String name;
@Required
publicvoidsetName(String name){
this.name=name;
}
publicstringgetName(){
return name;
}
}
4.5. @Autowired 註解有什麽用?
@Autowired 可以更準確地控制應該在何處以及如何進行自動裝配。此註解用於在 setter 方法,建構函式,具有任意名稱或多個參數的內容或方法上自動裝配 bean。預設情況下,它是型別驅動的註入。
public classEmployee {
private String name;
@Autowired
publicvoidsetName(String name){
this.name=name;
}
publicstringgetName(){
return name;
}
}
4.6. @Qualifier 註解有什麽用?
當您建立多個相同型別的 bean 並希望僅使用內容裝配其中一個 bean 時,您可以使用@Qualifier 註解和 @Autowired 透過指定應該裝配哪個確切的 bean 來消除歧義。
例如,這裏我們分別有兩個類,Employee 和 EmpAccount。在 EmpAccount 中,使用@Qualifier 指定了必須裝配 id 為 emp1 的 bean。
Employee.java
public classEmployee{
private String name;
@Autowired
publicvoidsetName(String name){
this.name=name;
}
public string getName(){
return name;
}
}
EmpAccount.java
public classEmpAccount{
private Employee emp;
@Autowired
@Qualifier(emp1)
publicvoidshowName(){
System.out.println(「Employee name : 」+emp.getName);
}
}
4.7. @RequestMapping 註解有什麽用?
@RequestMapping 註解用於將特定 HTTP 請求方法對映到將處理相應請求的控制器中的特定類/方法。此註解可套用於兩個級別:
類級別:對映請求的 URL
方法級別:對映 URL 以及 HTTP 請求方法
5
數據存取
5.1. spring DAO 有什麽用?
Spring DAO 使得 JDBC,Hibernate 或 JDO 這樣的數據存取技術更容易以一種統一的方式工作。這使得使用者容易在永續性技術之間切換。它還允許您在編寫程式碼時,無需考慮捕獲每種技術不同的異常。
5.2. 列舉 Spring DAO 丟擲的異常
5.3. spring JDBC API 中存在哪些類?
JdbcTemplate
SimpleJdbcTemplate
NamedParameterJdbcTemplate
SimpleJdbcInsert
SimpleJdbcCall
5.4. 使用 Spring 存取 Hibernate 的方法有哪些?
我們可以透過兩種方式使用 Spring 存取 Hibernate:
使用 Hibernate 樣版和回呼進行控制反轉
擴充套件 HibernateDAOSupport 並套用 AOP 攔截器節點
5.5. 列舉 spring 支持的事務管理型別
Spring 支持兩種型別的事務管理:
程式化事務管理 :在此過程中,在編程的幫助下管理事務。它為您提供極大的靈活性,但維護起來非常困難。
聲明式事務管理 :在此,事務管理與業務代分碼離。僅使用註解或基於 XML 的配置來管理事務。
5.6. Spring 支持哪些 ORM 框架
Hibernate
iBatis
JPA
JDO
OJB
6
AOP
6.1. 什麽是 AOP?
AOP(Aspect-Oriented Programming), 即 面向切面編程 , 它與 OOP( Object-Oriented Programming, 物件導向編程) 相輔相成, 提供了與 OOP 不同的抽象軟體結構的視角.
在 OOP 中, 我們以類( class)作為我們的基本單元, 而 AOP 中的基本單元是 Aspect(切面)
6.2. AOP 中的 Aspect、Advice、Pointcut、JointPoint 和 Advice 參數分別是什麽?
Aspect - Aspect 是一個實作交叉問題的類,例如事務管理。方面可以是配置的普通類,然後在 Spring Bean 配置檔中配置,或者我們可以使用 Spring AspectJ 支持使用 @Aspect 註解將類別宣告為 Aspect。
Advice - Advice 是針對特定 JoinPoint 采取的操作。在編程方面,它們是在應用程式中達到具有匹配切入點的特定 JoinPoint 時執行的方法。您可以將 Advice 視為 Spring 攔截器(Interceptor)或 Servlet 過濾器(filter)。
Advice Arguments - 我們可以在 advice 方法中傳遞參數。我們可以在切入點中使用 args() 運算式來套用於與參數模式匹配的任何方法。如果我們使用它,那麽我們需要在確定參數型別的 advice 方法中使用相同的名稱。
Pointcut - Pointcut 是與 JoinPoint 匹配的正規表式,用於確定是否需要執行 Advice。Pointcut 使用與 JoinPoint 匹配的不同型別的運算式。Spring 框架使用 AspectJ Pointcut 運算式語言來確定將套用通知方法的 JoinPoint。
JoinPoint - JoinPoint 是應用程式中的特定點,例如方法執行,例外處理,更改物件變量值等。在 Spring AOP 中,JoinPoint 始終是方法的執行器。
6.3. 什麽是通知(Advice)?
特定 JoinPoint 處的 Aspect 所采取的動作稱為 Advice。Spring AOP 使用一個 Advice 作為攔截器,在 JoinPoint 「周圍」維護一系列的攔截器。
6.4. 有哪些型別的通知(Advice)?
Before - 這些型別的 Advice 在 joinpoint 方法之前執行,並使用 @Before 註解標記進行配置。
After Returning - 這些型別的 Advice 在連線點方法正常執行後執行,並使用@AfterReturning 註解標記進行配置。
After Throwing - 這些型別的 Advice 僅在 joinpoint 方法透過丟擲異常結束並使用 @AfterThrowing 註解標記配置時執行。
After (finally) - 這些型別的 Advice 在連線點方法之後執行,無論方法結束是正常還是異常返回,並使用 @After 註解標記進行配置。
Around - 這些型別的 Advice 在連線點之前和之後執行,並使用 @Around 註解標記進行配置。
6.5. 指出在 spring aop 中 concern 和 cross-cutting concern 的不同之處
concern 是我們想要在應用程式的特定模組中定義的行為。它可以定義為我們想要實作的功能。
cross-cutting concern 是一個適用於整個套用的行為,這會影響整個應用程式。例如,日誌記錄,安全性和數據傳輸是應用程式幾乎每個模組都需要關註的問題,因此它們是跨領域的問題。
6.6. AOP 有哪些實作方式?
實作 AOP 的技術,主要分為兩大類:
靜態代理 - 指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時增強;
編譯時編織(特殊編譯器實作)
類載入時編織(特殊的類載入器實作)。
動態代理 - 在執行時在記憶體中「臨時」生成 AOP 動態代理類,因此也被稱為執行時增強。
JDK 動態代理
CGLIB
6.7. Spring AOP and AspectJ AOP 有什麽區別?
Spring AOP 基於動態代理方式實作;AspectJ 基於靜態代理方式實作。
Spring AOP 僅支持方法級別的 PointCut;提供了完全的 AOP 支持,它還支持內容級別的 PointCut。
6.8. 如何理解 Spring 中的代理?
將 Advice 套用於目標物件後建立的物件稱為代理。在客戶端物件的情況下,目標物件和代理物件是相同的。
Advice + Target Object = Proxy
6.9. 什麽是編織(Weaving)?
為了建立一個 advice 物件而連結一個 aspect 和其它套用型別或物件,稱為編織(Weaving)。在 Spring AOP 中,編織在執行時執行。請參考下圖:
7
MVC
7.1. Spring MVC 框架有什麽用?
Spring Web MVC 框架提供 模型-檢視-控制器 架構和隨時可用的元件,用於開發靈活且松散耦合的 Web 應用程式。MVC 模式有助於分離應用程式的不同方面,如輸入邏輯,業務邏輯和 UI 邏輯,同時在所有這些元素之間提供松散耦合。
7.2. 描述一下 DispatcherServlet 的工作流程
DispatcherServlet 的工作流程可以用一幅圖來說明:
向伺服器發送 HTTP 請求,請求被前端控制器 DispatcherServlet 捕獲。
DispatcherServlet 根據 -servlet.xml 中的配置對請求的 URL 進行解析,得到請求資源識別元(URI)。然後根據該 URI,呼叫 HandlerMapping 獲得該 Handler 配置的所有相關的物件(包括 Handler 物件以及 Handler 物件對應的攔截器),最後以HandlerExecutionChain 物件的形式返回。
DispatcherServlet 根據獲得的Handler,選擇一個合適的 HandlerAdapter。(附註:如果成功獲得HandlerAdapter後,此時將開始執行攔截器的 preHandler(…)方法)。
提取Request中的模型數據,填充Handler入參,開始執行Handler(Controller)。在填充Handler的入參過程中,根據你的配置,Spring 將幫你做一些額外的工作:
HttpMessageConveter:將請求訊息(如 Json、xml 等數據)轉換成一個物件,將物件轉換為指定的響應資訊。
數據轉換:對請求訊息進行數據轉換。如String轉換成Integer、Double等。
數據根式化:對請求訊息進行數據格式化。如將字串轉換成格式化數位或格式化日期等。
數據驗證:驗證數據的有效性(長度、格式等),驗證結果儲存到BindingResult或Error中。
Handler(Controller)執行完成後,向 DispatcherServlet 返回一個 ModelAndView 物件;
根據返回的ModelAndView,選擇一個適合的 ViewResolver(必須是已經註冊到 Spring 容器中的ViewResolver)返回給DispatcherServlet。
ViewResolver 結合Model和View,來渲染檢視。
檢視負責將渲染結果返回給客戶端。
7.3. 介紹一下 WebApplicationContext
WebApplicationContext 是 ApplicationContext 的擴充套件。它具有 Web 應用程式所需的一些額外功能。它與普通的 ApplicationContext 在解析主題和決定與哪個 servlet 關聯的能力方面有所不同。
來源:靜默虛空
連結:juejin.im/post/5cbda379f265da03ae74c282
— END —
PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。
往期推薦