# Spring核心理論基礎
- 控制權轉置 IoC (Inversion of Control)
- 依賴注入 DI (Dependency Injection)
- 剖面導向程式設計AOP (Aspect Oriented Programming)
## Framework Reuse
### Template Method (Design Pattern)
核心概念:
- 共用的主流程寫在父類別 (template method)
- 可變動(客製化)部分挖空變成抽象方式(hook method)
Framework reuse 便是套用這個概念
### Framework Reuse vs Library Reuse

- Library Reuse: 開發者控制主流程依需求使用library提供的function
- Framework Reuse: 框架決定主流程,開發者依需求實作callback function

- 主流程控制權由開發者移轉到框架=>所以是主流程控制權倒置(IoC)
### Application Framework
- 定義:可重用的半製品,客製化後成為一個可用的系統
- High level (完成度高 彈性低 特定使用): ERP framework, Insurance framework
- Low level (完成度低,彈性高 可開發任意應用): Spring framework, Hibernate
- 好處:
- 共同領域抽象化機制
- 節省開發時間 (不用從底層實作)
- **應用程式整體有一致性** (每個人風格寫法不同,日後解讀維護困難)
- Template Method:
- 內含主流程的父類別=>framework
- 實作hook method子類別=>element,可抽換不同element
## Dependency Injection
將物件控制權從主程式反轉給Spring Container,由它負責物件的建立,銷毀並管理物件間的關聯性
### Dependency
- 狹隘的dependency
```mermaid
classDiagram
classK ..> classL : Dependency
```
- 廣義的dependency
```mermaid
classDiagram
classA --|> classB : Inheritance
classM ..|> classN : Realization
classG --> classH : Association
classE --o classF : Aggregation
classC --* classD : Composition
classK ..> classL : Dependency
```
- 依賴方式:
- parameter
- local variable
- global variable
參考:[快速搞懂 UML 類別圖 6 種關係線( Dependency、Association、Aggregation、Composition、Generalization、Realization )](https://dongstudio.medium.com/%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-uml-%E9%A1%9E%E5%88%A5%E5%9C%96-6-%E7%A8%AE%E9%97%9C%E4%BF%82%E7%B7%9A-dependency-association-aggregation-composition-generalization-realization-bf0670370c3d)
### Dependency 對程式碼可重用性的影響
- Dependency和程式碼可重用性是反比關係
- Liskov代換法則:降低Dependency的手法
- 將Client與實作類別中間用Interface隔開,去除Client與實作類別的依賴性
- **Interface保持簡單及穩定**
### Spring 如何降低依賴性
- 提倡以POJO為基礎程式設計
>POJO (Plain Old Java Object): 普通的Java物件,不受任何特殊條件限制
- Spring在執行時期會作為元件的中間人將元件間依賴關係注入
- Spring透過AOP將特定的技術邏輯(非功能面需求 ex: log, transaction)注入元件中,而不是透過傳統的繼承方式(EJB)
### 物件生成與相依關係建立
- 生成:使用new建立物件
- 建立關係:透過setter或是construct建立關係
```java=
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAdress(from));
msg.setSubset(subset);
msg.setText("My Message");
msg.setHeader("X-Mailer", "abc");
msg.sendDate(new Date());
transport.send(msg);
```
- 可以透過Factory將物件生成過程抽象化並封裝,將生成的責任委派給Factory
```java=
Message msg = myMessageFactory.create(session);
transport.send(msg);
```
將這種模式推展到極致,可以設計一種泛用的Factory專門用來生成並組合物件,也就是**輕量級容器**
### 輕量級容器與IoC
在使用輕量級容器,實質上就是物件生成主控權從程式倒置給容器。所以有人會稱之為IoC Container。
- frame reuse IoC: 主流程主控權倒置
- IoC container:物件生成主控權倒置
[為了避免混淆主流程主控權與物件生成主控權倒置,建議使用DI(Dependency Injection)指稱IoC container](https://martinfowler.com/articles/injection.html)
### DI 類型
1. setter
2. constructor
3. interface
Spring只有支援 **setter** 和 **constructor injection**
因為interface injection有較強的侵入性,後續要移植到其他framework會付出更多成本修改。
參考:[interface-injection-example](https://stackoverflow.com/questions/10248000/spring-interface-injection-example)
## Spring 如何實現 DI
### Bean, Bean 容器 與Bean設定檔
1. Bean: Spring frameworkm元件(廣義一般class)
2. Bean設定檔:對Bean屬性或行為做設定(現在都用annotation)
3. Bean 容器:管理Bean生命週期及建立關聯
### Bean vs JavaBean
#### JavaBean:
- 規定:
1. 具有一個無參數建構子
2. 使用getter,setter存取private variable且命名須為Camel命名慣例
3. 若要支援遠端傳送或永續性(DB)必須為可序列化(Serializable)
```java=
//Serializable => marker interface:沒有定義任何虛實作方法
public class Product implements Serializable{
private int productId;
public void setProductId(int id){
productId = id;
}
public int getProductId(){
return productId;
}
}
```
- 缺點
- 無法一次設多個屬性
- 在JDK1.4前無法存取巢狀屬性
- **要取得Bean事件的類別必須實作java.beans.PropertyChangeSupport(產生Dependency)**
#### Bean
- 承襲JavaBean優點可以從外部改變屬性及行為 (ex: 從Bean設定檔set屬性)
- 沒有任何規範,任何POJO都可以設為Bean
### 開發Spring應用程式標準步驟
1. 規劃與設計Bean(使用OOAD物件導向分析設計)
2. 寫出Bean設定檔
3. 寫出使用Bean的Client端程式
### Bean容器
- 負責管理1~多個Bean
- init時會讀入Bean設定檔
- 維護Bean的註冊表格(Map),一筆Bean紀錄=>BeanDefinition

當Client需要時可用id,Class向容器取得Bean實例來使用
### Bean 設定檔
- 由3個部分組成:
1. XML宣告區段
2. 標頭宣告區段
3. Bean宣告區段
- xml結構與型別:
- DTD (比較舊,沒有data type)
- XML Schema (有data type) <- 盡可能採用
### Bean 設定檔放置路徑
```mermaid
flowchart LR
src---spring---unit2---Product.java
unit2---beans-config.xml
```
編譯後,beans-config.xml會被複製到預設Class Path: bin/spring/unit2/跟Product.class放在一起
:::info
通常團隊合作開發會拆功能套件,每一個功能套件會維護一個bean config file並放在同一個套件資料夾方便管理
:::
### 取得Bean元件
- BeanFactory(已被標注要淘汰)
```java=
Resource resource = new ClassPathResource("beans-config.xml");
BeanFactor bf = new XmlBeanFactory(resource);
Product product = (Product)bf.getBean("product");
```
- ApplicationContext (推薦使用)
```java=
ApplicationContext context= new ClassPathXmlApplicationContext("beans-config.xml");
Product product = (Product)context.getBean("product");
```
如果有多個bean config檔要載入
```java=
String[] configs = {"beans-config.xml","beans-config2.xml"};
ApplicationContext context= new ClassPathXmlApplicationContext(configs);
```
:::warning
:warning: 設定檔裡面Bean的id不可以重複,否則容器無法知道要生成哪一個。
- IDE可以檢查同一個設定檔內是否有重複id
- 但IDE無法檢查多個設定檔內是否有重複id,必須依靠專案會議大家先對齊
:::
### 黏合用程式碼(Glue Code)
- 開發者自行建構BeanFactory或ApplicationContext並取得Bean的程式稱為**黏合用程式碼(Glue Code)**
- Spring 應用程式只有在黏合用程式碼(Glue Code)所在的類別才會相依於Spring Framework
```java=
/* Product.java */
public class Product{
private String productId;
private String name;
...
}
/* Client.java */
public class Client{
public static void main(String[] args){
//glue code
ApplicationContext context= new ClassPathXmlApplicationContext("beans-config.xml");
Product product = (Product)context.getBean("product");
...
}
}
```
### ApplicationContext vs BeanFactory
一般情況下建議使用ApplicationContext作為Bean容器
- ApplicationContext包含BeanFactory功能
- 具有去除Glue Code功能
:::info
Web Application 用 ContextLoaderListener或ContextLoaderServlet在初始化載入ApplicationContext,開發者不用寫glue code
```xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- or use the ContextLoaderServlet instead of the above listener
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->
```
:::
### Bean實體化
```xml
<bean id="myProduct" class="myPackage.Product"/>
```
- id: Bean元件的唯一識別名稱,在同一個Bean Container不同Bean,**id不可重複**
- class: 指定元件類別,必須包含完整套件名稱的類別全稱(fully qualified name)
### 設定Bean屬性
透過 property標籤要求容器經由對應的setter方式設值
- 基本型別直接設值(int, double, boolean, String)
Product.java:
```java=
class Product {
private String productId;
public void setProductId(String productId){
this.productId = productId;
}
}
```
beans_config.xml:
```xml=
<bean id = "myProduct" class="Product">
<property name="productId" value="10001" />
</bean>
```
- 如果是設定參考型別物件必須透過依賴注入的技術達成
- setter
- constructor
### Setter Injection
```mermaid
classDiagram
ProductViewer --> Product : Association
```
ProductViewer.java
```java=
class ProductViewer{
private Product product;
public void setProduct(Product product){
this.product = product;
}
}
```
beans_config.xml:
```xml=
<bean id = "myProduct" class="Product">
<property name="productId" value="10001" />
</bean>
<bean id = "myProductViewer" class="ProductViewer">
<property name="product" ref="myProduct" />
</bean>
```
:::info
p-namespace:在標題加上xmlns:p="http://www.springframework.org/schema/p" 就可以使用p-namespace簡便語法。(可以在IDE透過設定方式加入,不要自己key)
```xml!
<bean id="coderTanu" class="com.gfg.scripter.Coder">
<property name="id" value=100/>
<property name="name" value="Tanu Jain"/>
<property name="qualification" value="B.Tech - CSE"/>
<property name="dob" value="27-07-1996"/>
</bean>
<bean id="coderTanu" class="com.gfg.scripter.Coder" p:id=100 p:name="Tanu Jain" p:qualification="B.Tech - CSE" p:dob="27-07-1996"/>
```
:::
### Constructor Injection
相較於association,aggreation的關係更緊密,需要在建構子就建立關聯。
```mermaid
classDiagram
ProductViewer --> Product : Aggreation
```
ProductViewer.java
```java=
class ProductViewer{
private Product product;
public ProductViewer(Product product){
this.product = product;
}
}
```
beans_config.xml:
```xml=
<bean id = "myProduct" class="Product">
<property name="productId" value="10001" />
</bean>
<bean id = "myProductViewer" class="ProductViewer">
<constructor-arg ref="myProduct" />
</bean>
```
:::info
- 多個參數
```xml
<bean id = "myProductViewer" class="ProductViewer">
<constructor-arg ref="product1" />
<constructor-arg ref="product2" />
</bean>
```
- 多個**有序**參數
```xml
<bean id = "myProductViewer" class="ProductViewer">
<constructor-arg index="1" ref="product1" />
<constructor-arg index="2" ref="product2" />
</bean>
```
:::
### Bean Scope
- Singleton
- 確保在整個容器中只有一個實體,適合放共用資料
```mermaid
flowchart LR;
singleton[singleton bean]
b1[bean1]
b2[bean2]
b3[bean3]
subgraph Bean容器
singleton
end
b1 & b2 & b3 -->|ref| singleton
```
- xml:
```xml
<bean id = "myProductViewer" scope="singleton" class="ProductViewer">
...
</bean>
```
- **預設scope**
- Prototype
- 每一個getBean() 要求都會送一個Bean元件的copy給Client端
```mermaid
flowchart LR;
b1[bean1]
b2[bean2]
b3[bean3]
subgraph Bean容器
bean
copy1
copy2
copy3
end
b1 -->|ref| copy1
b2 -->|ref| copy2
b3 -->|ref| copy3
```
- xml:
```xml
<bean id = "myProductViewer" scope="prototype" class="ProductViewer">
...
</bean>
```
由於prototype會建立新的instance如果是跟DB連線等資源相關,需要注意效能是否會被影響。
:::info
不同singelton定義:
1. GoF Singleton(C++):在整個作業系統只有一個實例
```mermaid
flowchart TB;
subgraph OS
singelton
end
```
2. Java Singleton:在整個JVM上只有一個實例
```mermaid
flowchart TB;
subgraph OS
subgraph JVM1
singleton1
end
subgraph JVM2
singleton2
end
end
```
5. Spring Singleton:在一個IoC容器只有一個實例
```mermaid
flowchart LR;
subgraph OS
subgraph JVM1
subgraph IoC container1
singleton1
end
subgraph IoC container2
singleton2
end
end
end
```
:::
:::info
除了預設的singleton和prototype之外,針對web service有其他的scope。
詳見:[Bean Scopes](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html)
[scope相關開發問題](https://medium.com/@HsuanChenLin/spring-annotation-scope-362b43705595)
:::
### 注入Array
```java
public class Order{
private Product[] lineItems;
public void setLineItems(Product[] products){
this.lineItems = products;
}
}
```
```xml
<bean id="order" class="Order">
<property name="lineItems">
<list>
<ref bean="product1"/>
<ref bean="product2"/>
</list>
</property>
</bean>
```
### 注入List
```java
public class Order{
private List<Product> lineItems;
public void setLineItems(List<Product> products){
this.lineItems = products;
}
}
```
```xml
<bean id="order" class="Order">
<property name="lineItems">
<list>
<ref bean="product1"/>
<ref bean="product2"/>
</list>
</property>
</bean>
```
### 注入Set
```java
public class Order{
private Set<Product> lineItems;
public void setLineItems(Set<Product> products){
this.lineItems = products;
}
}
```
```xml
<bean id = "product1" class="Product">
...
</bean>
<bean id = "product2" class="Product">
...
</bean>
<bean id="order" class="Order">
<property name="lineItems">
<set>
<ref bean="product1"/>
<ref bean="product2"/>
</set>
</property>
</bean>
```
:::info
Product需要實作hashcode跟equal才能在新增到set時判斷是否重複
參考:[equals() & hashCode() in Java](https://medium.com/joe-tsai/equals-hashcode-4480f4580be4)
:::
### 注入Properties
```xml
<props>
<prop key="id" value="john" />
</props>
```
### 注入Map
| key | value|
| -------- | -------- |
| name | "John" |
| product | product1 |
```xml
<bean id = "product1" class="Product">
...
</bean>
<bean id="order" class="Order">
<property name="lineItems">
<map>
<entry>
<key>
<value>name</value>
</key>
<value>John</value>
</entry>
<entry>
<key>
<value>product</value>
</key>
<ref bean="product1"/>
</entry>
</map>
</property>
</bean>
```
簡寫:
```xml
<bean id = "product1" class="Product">
...
</bean>
<bean id="order" class="Order">
<property name="lineItems">
<map>
<entry key="name" value="John" />
<entry key="product" value-ref="product1"/>
</map>
</property>
</bean>
```
### Factory Method
- 適用:無法透過xml設定bean(ex: 需要讀取DB或API)可以用程式解決。
```java=
public class Product{
public static Product getInstance(){
//特有的Product實體建構邏輯
}
}
```
```xml!
<bean id="product" class="Product" factory-method="getInstance"/>
```
### Factory Bean
使用工廠模式產生Object
```java=
public class ProductFactory{
public static Product getInstance(){
//特有的Product實體建構邏輯
}
}
```
```xml!
<bean id="productFactory" class="ProductFactory" />
<bean id="product" class="Product"
factory-bean="productFactory"
factory-method="getInstance"/>
```
---
## AOP
### 開發應用程式流程
```mermaid
flowchart LR;
U[Mental model]
R[Requirement model]
A[Architecture model & AOP]
D[Design model]
S[Solution model]
I[Implementation]
T[Testing]
Dev[Deployment]
M[Maintain]
U-->R
R-->|NFR|A
R-->|FR|D
A-->S
D-->S
S-->I-->T-->Dev-->M
```
FR: functional requirement
NFR: non functional requirement
從require model到solution model可以用OOAD方式設計系統
### 使用AOP動機
AOP要解決的是**商務邏輯跟非商務邏輯程式混雜的問題**
系統開發過程中往往要考量許多跟主要業務流程無關的橫切面考量(Cross-Cutting Concerns)
- 交易(transaction)
- 安全性
- 例外處理
- 記錄檔(log)
但若沒有仔細切分,容易讓程式碼中的商務流程跟系統考量交雜在一起,後續**維護困難**
### 將剖面(Aspect)單獨考量
基於重用的原則,將這些橫切面的處理邏輯抽出成一個Aspect單獨開發。
開發完再一次套用到有此需求的商務流程。

### Aspect-Oriented Programming (AOP)
- 分別專注在商務領域問題跟橫切面問題開發,個別完成後再進行縫合(Weaving)
- 透過AOP的思維,Aspect可以獨立於應用程式之外
- 必要時透過設定,可介入應用程式提供服務
- 不需要時,也可以將Aspect直接抽離,應用程式本身不需要修改程式碼。
- 因為命名關係,有些人會將AOP與OOP(Object-Oriented Programming)拿來比較。但它們並不互相抵觸是相輔相成的兩個設計模型。
### AOP 觀念術語
- Aspect
- 代表一個Cross-Cutting Concerns
- 由Pointcut與Advice二類的元件組成
- Advise
- Aspect的具體實作
- Join Points
- Advise在切入商務流程的點稱之為Joint Point
- 實作上來說就是Advice在應用程式裡**被執行的時機點**
- Spring只支援**類別方法**的Join Points
- Spring**不支援Field**的Join Points,因為Sping開發人員認為這樣會破壞物件的封裝性。
- Pointcut
- 用來定義一群Joint Points
- 當呼叫的方法符合Pointcut的表示式時,系統會自動將Advice與方法縫合
- Target
- 一個Advice被應用的對象或目標物件
- Weave
- Advice被應用到物件上的過程稱之為縫合(Weave)
- 在AOP中縫合的時間點:
1. 編譯時期
2. 類別載入時期
3. 執行時期(大部分應用屬於此類)
### Pointcut宣告語法範例
```
execution(
modifiers-pattern? //修飾子:(public, protected, default)
return-type-pattern? //回傳型別
declaring-type-pattern?//指定套件,類別
name-pattern(parameter-pattern)?//方法
throws-pattern?//例外
)
```
- execution(public * *(\.\.))
- 符合任何的公開方法
- execution(\* hello\*(\.\.))
- 符合任何名稱是hello開頭的方法
- execution(\* somepackage.service.MyService\.\*(\.\.))
- 符合MyService任何方法
- execution(\* somepackage.service\.\*\.\*(\.\.))
- 符合somapackage.service套件中任何方法
- execution(\* somepackage\.\.\*.\*(\.\.))
- 符合somepackage套件或子套件中任何方法
### Advice介入Joint Point時機
- Before advice
- Advice會在joint point執行之前被觸發
```java
public void before(JointPoint jointpoint){
...
}
```
- After returning advice
- 如果joint point所在方被成功執行並return,advice會緊接著執行
```java
public void afterReturning(JointPoint jointpoint){
...
}
```
- After advice
- 無論是否成功return,advice都會緊接著執行
```java
public void after(JointPoint jointpoint){
...
}
```
- After throwing
- 方法拋出例外時執行
```java
public void afterThrowing(JointPoint jointpoint){
...
}
```
- **Around**
- 從joint point觸發前到return後的過程完全掌握
- 適合用在交易,觀測
- 跟其他advice參數和回傳型別不同
```Java
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//方法執行前
Object result = proceedingJoinPoint.proceed();
//方法執行後
return result;
}
```
因為要掌握完整流程需要暫時取得物件控制權,執行後要把物件(Object)回傳IoC Container,讓容器繼續管理物件。所以如果回傳null,Spring會無法正常執行發出error
### 使用XML設定Spring AOP
- 寫作Advice類別
- 在bean設定檔加入AOP命名空間
- 在bean設定檔宣告AOP相關設定
### 寫作Advice類別
```java=
public class LogAdvice{
...
public void before(JointPoint jointPoint){
...(要插入的邏輯)
}
}
```
### 在bean設定檔加入AOP命名空間
可以使用IDE自動產生
```xml!
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
</beans>
</xml>
```
### 在bean設定檔宣告AOP相關設定
```xml!
<bean id="logAdvice" class="mypackage.LogAdvice" />
<bean id="product" class="mypackage.Product" />
<aop:config>
<aop:aspect id="logging" ref="logAdvice">
<aop:before pointcut="execution(*mypackage.Product.*(..))" method="before"
</aop:aspect>
</aop:config>
```
在Product任何類別方法執行前插入LogAdvice.before的方法
### 改成Annotation
1. 在Bean設定檔中加入AOP命名空間
2. 在Bean設定檔中加入\<aop:aspectj-autoproxy>
3. 寫作Target類別並加入Annotations
### 在Bean設定檔中加入\<aop:aspectj-autoproxy>
修改前:
```xml!
<bean id="logAdvice" class="mypackage.LogAdvice" />
<bean id="product" class="mypackage.Product" />
<aop:config>
<aop:aspect id="logging" ref="logAdvice">
<aop:before pointcut="execution(*mypackage.Product.*(..))" method="before"
</aop:aspect>
</aop:config>
```
修改後,透過Annotion讓系統自行加入Advice:
```xml!
<bean id="logAdvice" class="mypackage.LogAdvice" />
<bean id="product" class="mypackage.Product" />
<aop:aspectj-autoproxy>
```
### 寫作Target類別並加入Annotations
```java=
@Aspect
public class LogAdvice{
...
@Before("excution(* mypackage.Product.*(..))")
public void before(JointPoint jointPoint){
...(要插入的邏輯)
}
}
```
:::info
小秘訣:
pointcut語法容易key錯,可以考慮將常用的pointcut語法放到一個類別去管理。
```java
//管理pointcut類別
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
}
//定義Targer類別
@Aspect
public class LogAdvice{
...
@Before(pointcut = "inWebLayer()")
public void before(JointPoint jointPoint){
...(要插入的邏輯)
}
}
```
:::
### Aspect vs Filter
Aspect跟Web Application的Filter相似
- Filter
web.xml用URL pattern指定哪些URL的request 會套上Filter
- Aspect
beans-config.xml指定哪些方法會觸發Advice
### AOP & Proxy Design Pattern
有注意到 \<aop:aspectj-autoproxy\>裡出現的aspectj和proxy嗎?
- Spring 從Spring 2.0開始支援IBM的AspectJ並受到啟發,在語法操作上盡量和AspectJ一致。
- 而proxy則是暗示AOP是實作Proxy Design Pattern
### 什麼是Proxy Design Pattern

一種結構設計模式,創造一個代理者(proxy)和其他物件互動。由於代理者會掌握被代理者流程,可以在被代理者流程執行前後插入其他流程,適合用來身份驗證或是埋入log等操作。
1. 靜態代理(較簡單):
被代理者切出一個共用介面,讓代理者(proxy)跟被代理者實作同一個介面,並讓代理者擁有被代理者控制權。

2. 動態代理:
代理者不限定只代理特定類別,通常由底層系統自動產生代理物件。
Spring AOP支援3種代理方式:
1. 執行時期產生代理物件
- Java Dynamic Proxy
- cglib(Code Generation Library)
2. 編譯或類別載入時期產生代理物件
- AspectJ
## 參考連結
[SPF doc 核心概念](https://docs.spring.io/spring-framework/reference/core.html)
[AOP參考](https://openhome.cc/Gossip/SpringGossip)
[關於AspectJ](https://openhome.cc/Gossip/Spring/AspectJ.html)
[從動態代理到Sping AOP](https://www.ithome.com.tw/voice/127388)
[Spring AOP - Java Dynamic Proxy and CGLib](https://www.tpisoftware.com/tpu/articleDetails/457)