# 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 ![](https://tomasp.net/blog/2015/library-frameworks/diagram.png) - Library Reuse: 開發者控制主流程依需求使用library提供的function - Framework Reuse: 框架決定主流程,開發者依需求實作callback function ![](https://nikestech.files.wordpress.com/2018/05/framework-vs-library.png?w=640) - 主流程控制權由開發者移轉到框架=>所以是主流程控制權倒置(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 ![](https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/images/container-magic.png) 當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單獨開發。 開發完再一次套用到有此需求的商務流程。 ![截圖 2023-11-25 下午4.30.50](https://hackmd.io/_uploads/r1IAxNyHa.png) ### 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 pattern](https://refactoring.guru/images/patterns/content/proxy/proxy.png?id=efece4647fb11e3f7539291796327666) 一種結構設計模式,創造一個代理者(proxy)和其他物件互動。由於代理者會掌握被代理者流程,可以在被代理者流程執行前後插入其他流程,適合用來身份驗證或是埋入log等操作。 1. 靜態代理(較簡單): 被代理者切出一個共用介面,讓代理者(proxy)跟被代理者實作同一個介面,並讓代理者擁有被代理者控制權。 ![UML](https://refactoring.guru/images/patterns/diagrams/proxy/structure.png?id=f2478a82a84e1a1e512a8414bf1abd1c) 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)