Spring IOC 基于Cglib實現含構造函數的類實例化策略

作者:小凱
分享、讓自己和他人都能有所收獲!

一、前言
技術成長,是對場景設計細節不斷的雕刻!

你覺得自己的技術什么時候得到了快速的提高,是CRUD寫的多了以后嗎?想都不要想,絕對不可能!CRUD寫的再多也只是能滿足你作為一個搬磚工具人,敲擊少邏輯流水代碼的速度而已,而編程能力這一塊,除了最開始的從不熟練到熟練以外,就很少再有其他提升了。

那你可能會想什么才是編程能力提升?其實更多的編程能力的提升是你對復雜場景的架構把控以及對每一個技術實現細節點的不斷用具有規模體量的流量沖擊驗證時,是否能保證系統穩定運行從而決定你見識了多少、學到了多少、提升了多少!

最終當你在接一個產品需求時,開始思考程序數據結構的設計、核心功能的算法邏輯實現、整體服務的設計模式使用、系統架構的搭建方式、應用集群的部署結構,那么也就是的編程能力真正提升的時候!

二、目標
這一章節的目標主要是為了解決上一章節我們埋下的坑,那是什么坑呢?其實就是一個關于 Bean 對象在含有構造函數進行實例化的坑。

在上一章節我們擴充了 Bean 容器的功能,把實例化對象交給容器來統一處理,但在我們實例化對象的代碼里并沒有考慮對象類是否含構造函數,也就是說如果我們去實例化一個含有構造函數的對象那么就要拋異常了。

怎么驗證?其實就是把 UserService 添加一個含入參信息的構造函數就可以,如下:

public class UserService {private String name;public UserService(String name) {this.name = name;}  // ...
}

報錯如下:

java.lang.InstantiationException: cn.ittest.springframework.test.bean.UserServiceat java.lang.Class.newInstance(Class.java:427)at cn.ittest.springframework.test.ApiTest.test_newInstance(ApiTest.java:51)...

發生這一現象的主要原因就是因為 beanDefinition.getBeanClass().newInstance(); 實例化方式并沒有考慮構造函數的入參,所以就這個坑就在這等著你了!那么我們的目標就很明顯了,來把這個坑填平!

三、設計
填平這個坑的技術設計主要考慮兩部分,一個是串流程從哪合理的把構造函數的入參信息傳遞到實例化操作里,另外一個是怎么去實例化含有構造函數的對象。

圖 4-1

  • 參考 Spring Bean 容器源碼的實現方式,在 BeanFactory 中添加 Object getBean(String name, Object… args) 接口,這樣就可以在獲取 Bean 時把構造函數的入參信息傳遞進去了。
  • 另外一個核心的內容是使用什么方式來創建含有構造函數的 Bean 對象呢?這里有兩種方式可以選擇,一個是基于 Java 本身自帶的方法 DeclaredConstructor,另外一個是使用 Cglib 來動態創建 Bean 對象。Cglib 是基于字節碼框架 ASM 實現,所以你也可以直接通過 ASM 操作指令碼來創建對象

四、實現
1. 工程結構

small-spring-step-03
└── src├── main│   └── java│       └── cn.ittest.springframework.beans│           ├── factory│           │   ├── config│           │   │   ├── BeanDefinition.java│           │   │   └── SingletonBeanRegistry.java│           │   ├── support│           │   │   ├── AbstractAutowireCapableBeanFactory.java│           │   │   ├── AbstractBeanFactory.java│           │   │   ├── BeanDefinitionRegistry.java│           │   │   ├── CglibSubclassingInstantiationStrategy.java│           │   │   ├── DefaultListableBeanFactory.java│           │   │   ├── DefaultSingletonBeanRegistry.java│           │   │   ├── InstantiationStrategy.java│           │   │   └── SimpleInstantiationStrategy.java│           │   └── BeanFactory.java│           └── BeansException.java└── test└── java└── cn.ittest.springframework.test├── bean│   └── UserService.java└── ApiTest.java

Spring Bean 容器類關系,如圖

在這里插入圖片描述

本章節“填坑”主要是在現有工程中添加 InstantiationStrategy 實例化策略接口,以及補充相應的 getBean 入參信息,讓外部調用時可以傳遞構造函數的入參并順利實例化。

2. 新增 getBean 接口

cn.ittest.springframework.beans.factory.BeanFactorypublic interface BeanFactory {Object getBean(String name) throws BeansException;Object getBean(String name, Object... args) throws BeansException;}
  • BeanFactory 中我們重載了一個含有入參信息 args 的 getBean 方法,這樣就可以方便的傳遞入參給構造函數實例化了。

3. 定義實例化策略接口

cn.ittest.springframework.beans.factory.support.InstantiationStrategypublic interface InstantiationStrategy {Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;}
  • 在實例化接口 instantiate 方法中添加必要的入參信息,包括:beanDefinition、 beanName、ctor、args
  • 其中 Constructor 你可能會有一點陌生,它是 java.lang.reflect 包下的 Constructor 類,里面包含了一些必要的類信息,有這個參數的目的就是為了拿到符合入參信息相對應的構造函數。
  • 而 args 就是一個具體的入參信息了,最終實例化時候會用到。

4. JDK 實例化

cn.ittest.springframework.beans.factory.support.SimpleInstantiationStrategypublic class SimpleInstantiationStrategy implements InstantiationStrategy {@Overridepublic Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {Class clazz = beanDefinition.getBeanClass();try {if (null != ctor) {return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);} else {return clazz.getDeclaredConstructor().newInstance();}} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);}}}
  • 首先通過 beanDefinition 獲取 Class 信息,這個 Class 信息是在 Bean 定義的時候傳遞進去的。
  • 接下來判斷 ctor 是否為空,如果為空則是無構造函數實例化,否則就是需要有構造函數的實例化。
  • 這里我們重點關注有構造函數的實例化,實例化方式為 clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);,把入參信息傳遞給 newInstance 進行實例化。

5. Cglib 實例化

cn.ittest.springframework.beans.factory.support.CglibSubclassingInstantiationStrategypublic class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {@Overridepublic Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(beanDefinition.getBeanClass());enhancer.setCallback(new NoOp() {@Overridepublic int hashCode() {return super.hashCode();}});if (null == ctor) return enhancer.create();return enhancer.create(ctor.getParameterTypes(), args);}}
  • 其實 Cglib 創建有構造函數的 Bean 也非常方便,在這里我們更加簡化的處理了,如果你閱讀 Spring 源碼還會看到 CallbackFilter 等實現,不過我們目前的方式并不會影響創建。

6. 創建策略調用

cn.ittest.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorypublic abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {Object bean = null;try {bean = createBeanInstance(beanDefinition, beanName, args);} catch (Exception e) {throw new BeansException("Instantiation of bean failed", e);}addSingleton(beanName, bean);return bean;}protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {Constructor constructorToUse = null;Class<?> beanClass = beanDefinition.getBeanClass();Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();for (Constructor ctor : declaredConstructors) {if (null != args && ctor.getParameterTypes().length == args.length) {constructorToUse = ctor;break;}}return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);}}
  • 首先在 AbstractAutowireCapableBeanFactory 抽象類中定義了一個創建對象的實例化策略屬性類 InstantiationStrategy instantiationStrategy,這里我們選擇了 Cglib 的實現類。
  • 接下來抽取 createBeanInstance 方法,在這個方法中需要注意 Constructor 代表了你有多少個構造函數,通過 beanClass.getDeclaredConstructors() 方式可以獲取到你所有的構造函數,是一個集合。
  • 接下來就需要循環比對出構造函數集合與入參信息 args 的匹配情況,這里我們對比的方式比較簡單,只是一個數量對比,而實際 Spring 源碼中還需要比對入參類型,否則相同數量不同入參類型的情況,就會拋異常了。

五、測試
1. 事先準備

cn.ittest.springframework.test.bean.UserServicepublic class UserService {private String name;public UserService(String name) {this.name = name;}public void queryUserInfo() {System.out.println("查詢用戶信息:" + name);}@Overridepublic String toString() {final StringBuilder sb = new StringBuilder("");sb.append("").append(name);return sb.toString();}
}
  • 這里唯一多在 UserService 中添加的就是一個有 name 入參的構造函數,方便我們驗證這樣的對象是否能被實例化。

2. 測試用例

cn.ittest.springframework.test.ApiTest@Test
public void test_BeanFactory() {// 1.初始化 BeanFactoryDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 2. 注入beanBeanDefinition beanDefinition = new BeanDefinition(UserService.class);beanFactory.registerBeanDefinition("userService", beanDefinition);// 3.獲取beanUserService userService = (UserService) beanFactory.getBean("userService", "小哥");userService.queryUserInfo();
}
  • 在此次的單元測試中,依然包括包括三個核心步驟;初始化 BeanFactory 工廠、注冊 Bean、獲取 Bean,此外與上一章節不同的是,在獲取 Bean 對象時候,傳遞了一個參數名稱為“小哥”的入參信息,這個信息的傳遞將會幫我們創建出含有 String 類型構造函數的 UserService 類,而不會再出現初始化報錯的問題。

3. 測試結果

查詢用戶信息:小哥Process finished with exit code 0
  • 從測試結果來看,最大的變化就是可以滿足帶有構造函數的對象,可以被實例化了。
  • 你可以嘗試分別使用兩種不同的實例化策略,來進行實例化。SimpleInstantiationStrategy、CglibSubclassingInstantiationStrategy

4. 操作案例
這里我們再把幾種不同方式的實例化操作,放到單元測試中,方便大家比對學習。

4.1 無構造函數

@Test
public void test_newInstance() throws IllegalAccessException, InstantiationException {UserService userService = UserService.class.newInstance();System.out.println(userService);
}
  • 這種方式的實例化也是我們在上一章節實現 Spring Bean 容器時直接使用的方式

4.2 驗證有構造函數實例化

@Test
public void test_constructor() throws Exception {Class<UserService> userServiceClass = UserService.class;Constructor<UserService> declaredConstructor = userServiceClass.getDeclaredConstructor(String.class);UserService userService = declaredConstructor.newInstance("小哥");System.out.println(userService);
}
  • 從最簡單的操作來看,如果有構造函數的類需要實例化時,則需要使用 getDeclaredConstructor 獲取構造函數,之后在通過傳遞參數進行實例化。

4.3 獲取構造函數信息

@Test
public void test_parameterTypes() throws Exception {Class<UserService> beanClass = UserService.class;Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();Constructor<?> constructor = declaredConstructors[0];Constructor<UserService> declaredConstructor = beanClass.getDeclaredConstructor(constructor.getParameterTypes());UserService userService = declaredConstructor.newInstance("小哥");System.out.println(userService);
  • 這個案例中其實最核心的點在于獲取一個類中所有的構造函數,其實也就是這個方法的使用 beanClass.getDeclaredConstructors()

4.4 Cglib 實例化

@Test
public void test_cglib() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class);enhancer.setCallback(new NoOp() {@Overridepublic int hashCode() {return super.hashCode();}});Object obj = enhancer.create(new Class[]{String.class}, new Object[]{"小哥"});System.out.println(obj);
}
  • 此案例演示使用非常簡單,但關于 Cglib 在 Spring 容器中的使用非常多,也可以深入的學習一下 Cglib 的擴展知識。

六、總結

  • 本章節的主要以完善實例化操作,增加 InstantiationStrategy 實例化策略接口,并新增了兩個實例化類。這部分類的名稱與實現方式基本是 Spring 框架的一個縮小版,大家在學習過程中也可以從 Spring 源碼找到對應的代碼。
  • 從我們不斷的完善增加需求可以看到的,當你的代碼結構設計的較為合理的時候,就可以非常容易且方便的進行擴展不同屬性的類職責,而不會因為需求的增加導致類結構混亂。所以在我們自己業務需求實現的過程中,也要盡可能的去考慮一個良好的擴展性以及拆分好類的職責。
  • 動手是學習起來最快的方式,不要讓眼睛是感覺看會了,但上手操作就廢了。也希望有需要的讀者可以親手操作一下,把你的想法也融入到可落地實現的代碼里,看看想的和做的是否一致。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91370.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91370.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91370.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

composer 常用命令

### 設置鏡像源全局設置composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/當個項目設置composer config repo.packagist composer https://mirrors.aliyun.com/composer/恢復官方源composer config -g --unset repos.packagist### 常用源阿里云…

【python】Python爬蟲入門教程:使用requests庫

Python爬蟲入門教程&#xff1a;使用requests庫 爬蟲是數據獲取的重要手段&#xff0c;下面我將通過一個完整的示例&#xff0c;教你如何使用Python的requests庫編寫一個簡單的爬蟲。我們將以爬取豆瓣電影Top250為例。 【python】網絡爬蟲教程 - 教你用python爬取豆瓣電影 Top…

OpenCV圖像縮放:resize

圖像縮放是圖像處理中的基礎操作之一。無論是圖像預處理、數據增強還是圖像金字塔構建&#xff0c;cv::resize 都是我們最常用的函數之一。但你是否注意到&#xff0c;在 OpenCV 中同時還存在一個名為 cv::Mat::resize 的方法&#xff1f;這兩個函數雖然名字類似&#xff0c;但…

汽車、航空航天、適用工業虛擬裝配解決方案

一、現狀在制造業數字化轉型浪潮中&#xff0c;傳統裝配過程仍面臨諸多挑戰&#xff1a;物理樣機試錯成本高、裝配周期冗長、工藝優化依賴經驗、跨部門協作效率低下……如何打破“試錯-返工”的惡性循環&#xff1f;目前總裝工藝通過DELMIA、NX、Creo等工程軟件進行工藝裝配驗證…

頁面跳轉和前端路由的區別

傳統方式&#xff1a;通過改變瀏覽器地址欄的 URL 來實現window.location.href /new-page<a href"/new-page">跳轉到新頁面</a>會導致整個頁面重新加載會觸發瀏覽器向服務器發送新的請求頁面狀態不會保留&#xff0c;所有資源重新加載可以避免新上線的內…

C/C++核心知識點詳解

C/C核心知識點詳解 1. 變量的聲明與定義&#xff1a;內存分配的本質區別 核心概念 在C/C中&#xff0c;變量的聲明和定義是兩個完全不同的概念&#xff1a; 聲明&#xff08;Declaration&#xff09;&#xff1a;告訴編譯器變量的名稱和類型&#xff0c;但不分配內存空間定義&a…

物聯網發展:從概念到應用的演變歷程

物聯網的發展歷程是一部技術革新與社會需求共同驅動的進化史&#xff0c;其演變可劃分為概念萌芽、技術積累、應用拓展和智能融合四個階段&#xff0c;每個階段均以關鍵技術突破或社會需求變革為標志&#xff0c;最終形成萬物互聯的智能生態。以下是具體演變歷程&#xff1a;一…

一個人開發一個App(數據庫)

后端要保存數據&#xff0c;我還是選擇了關系型數據庫Mysql, 因為其它的不熟悉。 flutter端這次我選擇的是ObjectBox&#xff0c;以前都是直接用的sqlite3&#xff0c;看對比ObjectBox效率比sqlite3高許多&#xff0c;這次前端為了用戶體驗&#xff0c;我需要緩存數據&#xff…

天銘科技×藍卓 | “1+2+N”打造AI驅動的汽車零部件行業智能工廠

7月24日&#xff0c;杭州天銘科技股份有限公司&#xff08;簡稱 “天銘科技”&#xff09;與藍卓數字科技有限公司&#xff08;簡稱 “藍卓”&#xff09;簽訂全面戰略合作協議。天銘科技董事長張松、副總經理艾鴻冰&#xff0c;藍卓副董事長譚彰等領導出席簽約儀式&#xff0c…

技術復盤報告:Vue表格中多行文本字段數據保存丟失問題

1. 問題背景 在一個基于 Vue 2.0 和 ElementUI 的復雜數據維護頁面中&#xff0c;用戶報告了一個偶發但嚴重的問題&#xff1a;在表格中編輯一個多行文本&#xff08;textarea&#xff09;字段時&#xff0c;輸入的內容有時會在點擊“保存”后丟失。 具體表現&#xff1a; 前端…

#C語言——學習攻略:深挖指針路線(四)--字符指針變量,數組指針變量,二維數組傳參的本質,函數指針變量,函數指針數組

&#x1f31f;菜鳥主頁&#xff1a;晨非辰的主頁 &#x1f440;學習專欄&#xff1a;《C語言學習》 &#x1f4aa;學習階段&#xff1a;C語言方向初學者 ?名言欣賞&#xff1a;"暴力解法是上帝給的&#xff0c;優化解法是魔鬼教的。" 目錄 1. 字符指針變量 1.1 使…

SpringBoot收尾+myBatis plus

一、數據傳遞返回值為:字符串package com.apesource.springboot_web_04.controller;import com.apesource.springboot_web_04.pojo.Emp; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;/*** 返回值為:字符…

基于 Spring Boot 實現動態路由加載:從數據庫到前端菜單的完整方案

在后臺管理系統中&#xff0c;不同用戶角色往往擁有不同的操作權限&#xff0c;對應的菜單展示也需動態調整。動態路由加載正是解決這一問題的核心方案 —— 根據登錄用戶的權限&#xff0c;從數據庫查詢其可訪問的菜單&#xff0c;封裝成前端所需的路由結構并返回。本文將詳細…

VitePress學習-自定義主題

VitePress-自定義主題 代碼倉庫 基礎了解 初始化項目的時候選擇 custom theme 運行后會發現頁面挺丑的。 如果想要用默認主題怎么辦呢&#xff0c;修改Layout。 使用默認主題的Layout <script setup lang"ts"> import { useData } from vitepress; impo…

【GEO從入門到精通】生成式引擎與其他 AI 技術的關系

2.1.3 生成式引擎與其他 AI 技術的關系生成式引擎作為人工智能領域的創新力量&#xff0c;與其他 AI 技術緊密相連&#xff0c;相互促進&#xff0c;共同推動 生成式引擎優化&#xff08;GEO&#xff09; 的發展。這些技術使生成式引擎能夠為消費者提供更加個性化和精準的內容。…

JAVAEE--4.多線程案例

設計模式1.單例模式1.1餓漢模式1.2懶漢模式(單線程版)1.3懶漢模式(多線程版本)1.4懶漢模式(多線程版本進階版)2.阻塞隊列3.定時器4.線程池1.單例模式設計模式是"軟性約束",不是強制的,可以遵守也可以不遵守,按照設計模式寫代碼使代碼不會太差框架是"硬性約束&qu…

量化感知訓練(QAT)流程

WHAT&#xff1a;量化感知訓練&#xff08;Quantization-Aware Training, QAT&#xff09; 是一種在模型訓練階段引入量化誤差的技術。它的核心思想是&#xff1a;通過在前向傳播時插入“偽量化節點”引入量化誤差&#xff0c;將權重和激活模擬為低精度&#xff08;如 int8&…

docker 用于將鏡像打包為 tar 文件

docker save 是 Docker 中用于將鏡像打包為 tar 文件的命令&#xff0c;常用于鏡像的備份、遷移或離線傳輸。以下是其核心用法和注意事項&#xff1a;一、基本語法bashdocker save [選項] IMAGE [IMAGE...] > 文件名.tar # 或 docker save -o 文件名.tar IMAGE [IMAGE...]IM…

設計模式(六)創建型:單例模式詳解

設計模式&#xff08;六&#xff09;創建型&#xff1a;單例模式詳解單例模式&#xff08;Singleton Pattern&#xff09;是 GoF 23 種設計模式中最簡單卻最常被誤用的創建型模式。其核心價值在于確保一個類在整個應用程序生命周期中僅存在一個實例&#xff0c;并提供一個全局訪…

PostgreSQL AND OR 操作符詳解

PostgreSQL AND & OR 操作符詳解 在數據庫查詢中,AND 和 OR 是兩種常見的邏輯操作符,用于組合多個查詢條件。PostgreSQL 作為一款功能強大的開源關系型數據庫管理系統,同樣支持這些操作符。本文將詳細介紹 PostgreSQL 中的 AND 和 OR 操作符,并探討它們在查詢中的應用…