Summer——從頭開始寫一個簡易的Spring框架


Summer——從頭開始寫一個簡易的Spring框架

              

​ 參考Spring框架實現一個簡易類似的Java框架。計划陸續實現IOCAOP、以及數據訪問模塊和事務控制模塊。項目持續維護中...歡迎Star!Thanks~~~

​ 本項目參考自Spring,但又做出了自己的創新,最大的創新點在於解決循環依賴的問題,引入了自己的解決方案,具體方法可以參考更新日志中Version 0.5(Pre-release)的更新記錄。

項目計划:

  • [x] IOC容器
  • [x] AOP切面
  • [x] 對外的擴展接口
  • [ ] 數據訪問集成模塊(JDBC、事務控制)

​ 關於對IOC和AOP功能為什么要使用(why),以及應該如何使用(how)請移步使用文檔,要了解每個版本更新的內容請移步更新日志

​ 關於如何將自己的框架適配到summer,請查看如何適配summer

運行環境

JDK 8

項目主要技術棧

注解、反射

為什么要使用

IOC

​ IOC——控制反轉,旨在將對象之間的依賴關系和對象的創建過程都交由一個第三方的角色來管理,這個第三方就是IOC容器。對象創建的控制權發生了反轉,通過IOC我們不必再自己去控制對象如何創建。

​ 我們設想一個平時的很常見的一個設計場景,很多人開發一個項目都喜歡dao、service、controller一把梭(只是做一個引子,並無涵蓋所有人或者不認同此分層設計的意思),而這里面:

  • dao(Data Access Object)意為“數據訪問對象”,指的是直接操作對象,一般為直接和數據庫的交互層。

  • service層表示服務層,不只對數據庫的簡單操作,還需要包含一些邏輯處理,比如判斷要插入的數據是否合法,控制事務保持一致性等等。

  • controller層表示控制層,這層負責根據用戶的業務需求調用相應服務來完成業務。

​ 以上三層相互協作,當一個用戶請求發過來后,我們controller拿到用戶請求,根據請求的業務需求調用不同的service來相互配合完成任務,而service又要調用相應的dao來對數據庫進行數據的CRUD,以完成最終的業務需求。

​ 而上述聽起來很容易的幾句話,就是三層之間相互配合,但是當業務逐漸復雜起來,假如一個controller需要十幾二十個service對象,而一個service又需要十幾二十個dao對象,這樣一個controller對象所依賴的對象是十分多的。又或是某一個對象的創建過程非常復雜,通常需要很多個步驟才能創建完成,這種情況下如果使用傳統的自己new對象的方式,那對象之間的依賴關系將會十分復雜,很難管理起來。所以就有了IOC這個第三方容器來幫助管理對象之間依賴。

AOP

​ AOP——面向切面編程,不同於面向對象(OOP),AOP更加關注的是一個方法的切面。假如我們此時有一個日志記錄的需求,日志需要加入到一段業務中去,如果業務開始階段、結束階段和拋出異常的時候要加入相應的日志記錄,如果我們直接在業務方法中添加日志,那么日志和核心的業務代碼就會耦合在一起,代碼可讀性差,不方便日后維護。AOP可以使我們無侵入地將日志的記錄穿插在業務過程中。雖然日志記錄的代碼和核心業務的代碼不在一個地方,但它們確實一起穿插執行了。

​ AOP的核心其實還是使用代理來實現,我們對一段業務函數進行代理, 在代理的類中,對它執行的開始返回異常結束階段分別做切入,插入日志的記錄,真正執行的時候我們執行的並不是執行的真實對象的方法,而是代理對象的方法,實際上被切的類在IOC中保存的並不是實際的對象,而是代理的對象。

​ 項目初期采用了JDK的動態代理來實現此功能,所以強制要求被切的方法的類需要實現一個接口。

​ ps: 2021/04/10 完成了對cglib方式的添加,現在對於實現了接口的類采用JDK的動態代理,對於沒有實現接口的類采用cglib。

​ 面向切面編程有三個必需的元素:切面類(切面類中有切面方法,或者稱為通知方法)、目標方法(被切的方法)、以及切入點。

如何使用

​ 倉庫地址: https://github.com/vfdxvffd/Summer

​ 下載最新的jar包 ,將其導入項目中,即可使用,目錄結構如下圖,藍色框內為summer的核心代碼,ch包下為logback日志依賴,net.sf.cglib下為cglib動態代理的依賴,org.slf4j下為slf4j的日志門面依賴。

Version 1.0(Release)

本次更新測試了之前的版本的穩定性,以及增加了框架的擴展性,開放出別的框架適配的接口。

  • 對之前的pre-release版本進行了多次測試以確保穩定性。
  • 框架對外開放Extension接口,該接口中的方法在ioc容器構造的多個階段進行了切入,增加了框架的可擴展性。
  • 如何將自己的框架適配到Summer,請看如何適配summer或者可以提issues

Version 0.5(Pre-release)

一次重大更新

bug描述:循環依賴的問題復現出來

​ 因為之前v0.1更新中引入的一個解決bug的方法導致了這個重大的bug,這次通過設置二級緩存來解決循環依賴的問題,具體bug的產生原因詳情可見更新日志,更新日志對這次bug的出現原因以及解決方法做了詳細的說明。

bug解決:

​ 針對目前掌握的代理方面的知識,對之前的做法做出一些調整。設置二級緩存,一級緩存一個(即真正的ioc容器),二級緩存兩個,都是負責存放實例化但未初始化的對象,但一個是存放原對象,另一個負責存放代理對象,二級緩存的示意圖如下:

將ioc容器的構造過程分為四步來進行:

  1. 遍歷包,找到所有需要被IOC管理的類,封裝成BeanDefinition
  2. 根據第一步獲取到的BeanDefinition實例化那些單例且非延遲加載的對象,並將其加入到二級緩存的earlyRealObjects
  3. 對第二步得到的earlyRealObjects中的對象進行檢查,看是否需要設置代理,如果需要則對其進行代理,並將代理對象加入到二級緩存中的earlyProxyObjects中(並不刪除earlyRealObjects中對應的真正的對象)。
  4. 對第二步得到的earlyRealObjects中的對象進行注入工作(即開始進行初始化),檢查每個對象的每個域,如果標注了@Autowired注解且值為null,則對其進行注入工作,現在一級緩存中查找,如果有直接取出為其注入,如果沒有檢查二級緩存的earlyProxyObjects,如果有則取出為其注入,如果沒有則接着檢查二級緩存的earlyRealObjects,找到后為其注入,此時如果還沒有則說明這個域對應的bean是非單例(prototype)模式或者懶加載模式的,則為其實例化並設置代理(如果需要),並初始化,然后注入其中。如果是非ioc容器管理的域,則直接注入null,也可以考慮改為拋出異常給用戶提示。

Version 0.4(Pre-release)

本次更新加入了新功能,修改了一個已知的bug

  • 本次更新引入CGLib依賴,增加動態代理的方式,對於實現了接口的方法采用JDK動態代理來實現切面功能,對於沒有實現接口的類采用CGLib來實現切面。

  • 修改bug,之前版本中的判斷當前類是否已經完成了實例對象全部的創建注入工作的方法,判斷沒有包含所有情況。

    bug描述:對於一個沒有任何域需要代理的對象,進行注入工作的時候會由於沒有域需要注入,從而直接判斷其已經完成注入,而跳過了代理階段。

Version 0.3(Pre-release)

  • 本次更新引入了日志依賴,增加了對ioc構造過程中的日志記錄

  • 對於標注了@Aspect注解的類自動將其加入IOC容器中,不用再重復標注注解

Version 0.2(Pre-release)

本次更新加入了一些新功能,修復了一些bug

  1. 更新功能:

    • aop增加了一種切入方式,目前有以下切入方式

      @Before@AfterReturning@AfterThrowing@After

      以上對應的切入時機如下:

      try {
          @Before
          fun.invoke();
          @AfterReturning
      } catch (Throwable t) {
          @AfterThrowing
      } finally {
          @After
      }
      
    • 切面方法可以通過JoinPoint類獲取被切的方法的參數、方法名、返回值類型。對於@AfterReturning的切入方式可以獲取返回值,類型為Object,而@AfterThrowing可以獲取拋出的異常,類型為Throwable

  2. 修復了重復代理的bug

    bug描述:當一個待注入bean中有超過一個需要注入的域(帶有注解@Autowired且未完成賦值),如果對它中的方法進行切面,這時切面方法會重復執行

Version 0.1(Pre-release)

​ 本次更新主要修復了一些bug,以及優化了代碼的結構

  1. 修復對於注入對象的切面方法失效的bug

    bug描述:在controller中注入service,但是如果有對於service的切面方法,則切面方法無法被調用

  2. 修復延遲加載的對象注入失敗的bug

    bug描述:對於標注了延遲加載的類注入時會發生異常

  3. 修復對非單例的bean注入失敗的bug

    bug描述:對於標注了非單例的類注入時會發生異常,且會調用多次構造函數的問題

  4. 增加核心代碼的注釋。

  5. 優化代碼結構,重構了大部分冗余的代碼塊

  6. 抽取可重用方法。

Version 0.0(Pre-release)

  1. 完成IOC容器的初步搭建

  2. 完成AOP功能的簡單使用(還需修改)

  3. 支持@Component@Autowired@Qualifier@Value@Repository@Service@Controller注解的使用

    • @Component(同@Respository、@Service、@Controller):標注在類上,將此類注冊到ioc容器中
    • @Autowired:自動注入ioc容器中的對象
    • @Qualifier:自動注入ioc中對象的時候指定beanName,如不指定則按照beanType注入
    • @Value:指定將類注入到容器是基本類型(包括包裝類)字段的值
  4. 支持根據beanNamebeanType獲取ioc中的對象

  5. 自定義類型轉化異常,@Value接受String類型,如果傳入的值並不能正確轉化,就拋出DataConversionException異常。

  6. 增加單例模式與非單例模式的配置注解@Scope,以及增加延遲加載的配置注解@Lazy

  7. 可以使用接口來接受IOC中返回的對象

  8. AOP可以對方法進行@Before@After@AfterThrowing的切面,需要配置方法的全方法名

  9. AOP使用JDK的動態代理,set可以不添加,內部實現是直接通過設置域的可訪問屬性,然后直接設置值

  10. 后續計划:

    • 支持根據xml配置ioc容器中的對象
    • 對於運行過程可能發生的異常使其盡可能可控,且明確的拋出或處理
    • 對於AOP可選擇性的加入CGLIB代理
    • 對於AOP一些已注入對象的代理失效bug進行修復(已定位)
    • etc... for more...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM