嶄露頭角,基于Cglib實現(xiàn)含構造函數(shù)的類實例化策略
本文轉載自微信公眾號「bugstack蟲洞?!梗髡咝「蹈?。轉載本文請聯(lián)系bugstack蟲洞棧公眾號。
目錄
- 一、前言
- 二、目標
- 三、設計
- 四、實現(xiàn)
- 1. 工程結構
- 2. 新增 getBean 接口
- 3. 定義實例化策略接口
- 4. JDK 實例化
- 5. Cglib 實例化
- 6. 創(chuàng)建策略調(diào)用
- 五、測試
- 1. 事先準備
- 2. 測試用例
- 3. 測試結果
- 4. 操作案例
- 六、總結
- 七、系列推薦
一、前言
技術成長,是對場景設計細節(jié)不斷的雕刻!
你覺得自己的技術什么時候得到了快速的提高,是CRUD寫的多了以后嗎?想都不要想,絕對不可能!CRUD寫的再多也只是能滿足你作為一個搬磚工具人,敲擊少邏輯流水代碼的速度而已,而編程能力這一塊,除了最開始的從不熟練到熟練以外,就很少再有其他提升了。
那你可能會想什么才是編程能力提升?其實更多的編程能力的提升是你對復雜場景的架構把控以及對每一個技術實現(xiàn)細節(jié)點的不斷用具有規(guī)模體量的流量沖擊驗證時,是否能保證系統(tǒng)穩(wěn)定運行從而決定你見識了多少、學到了多少、提升了多少!
最終當你在接一個產(chǎn)品需求時,開始思考程序數(shù)據(jù)結構的設計、核心功能的算法邏輯實現(xiàn)、整體服務的設計模式使用、系統(tǒng)架構的搭建方式、應用集群的部署結構,那么也就是的編程能力真正提升的時候!
二、目標
這一章節(jié)的目標主要是為了解決上一章節(jié)我們埋下的坑,那是什么坑呢?其實就是一個關于 Bean 對象在含有構造函數(shù)進行實例化的坑。
在上一章節(jié)我們擴充了 Bean 容器的功能,把實例化對象交給容器來統(tǒng)一處理,但在我們實例化對象的代碼里并沒有考慮對象類是否含構造函數(shù),也就是說如果我們?nèi)嵗粋€含有構造函數(shù)的對象那么就要拋異常了。
怎么驗證?其實就是把 UserService 添加一個含入?yún)⑿畔⒌臉嬙旌瘮?shù)就可以,如下:
- public class UserService {
- private String name;
- public UserService(String name) {
- this.name = name;
- }
- // ...
- }
報錯如下:
- java.lang.InstantiationException: cn.bugstack.springframework.test.bean.UserService
- at java.lang.Class.newInstance(Class.java:427)
- at cn.bugstack.springframework.test.ApiTest.test_newInstance(ApiTest.java:51)
- ...
發(fā)生這一現(xiàn)象的主要原因就是因為 beanDefinition.getBeanClass().newInstance(); 實例化方式并沒有考慮構造函數(shù)的入?yún)?,所以就這個坑就在這等著你了!那么我們的目標就很明顯了,來把這個坑填平!
三、設計
填平這個坑的技術設計主要考慮兩部分,一個是串流程從哪合理的把構造函數(shù)的入?yún)⑿畔鬟f到實例化操作里,另外一個是怎么去實例化含有構造函數(shù)的對象。
圖 4-1
- 參考 Spring Bean 容器源碼的實現(xiàn)方式,在 BeanFactory 中添加 Object getBean(String name, Object... args) 接口,這樣就可以在獲取 Bean 時把構造函數(shù)的入?yún)⑿畔鬟f進去了。
- 另外一個核心的內(nèi)容是使用什么方式來創(chuàng)建含有構造函數(shù)的 Bean 對象呢?這里有兩種方式可以選擇,一個是基于 Java 本身自帶的方法 DeclaredConstructor,另外一個是使用 Cglib 來動態(tài)創(chuàng)建 Bean 對象。Cglib 是基于字節(jié)碼框架 ASM 實現(xiàn),所以你也可以直接通過 ASM 操作指令碼來創(chuàng)建對象
四、實現(xiàn)
1. 工程結構
- small-spring-step-03
- └── src
- ├── main
- │ └── java
- │ └── cn.bugstack.springframework.beans
- │ ├── factory
- │ │ ├── factory
- │ │ │ ├── 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.bugstack.springframework.test
- ├── bean
- │ └── UserService.java
- └── ApiTest.java
工程源碼:公眾號「bugstack蟲洞?!梗貜停篠pring 專欄,獲取完整源碼
Spring Bean 容器類關系,如圖 4-2
圖 4-2
本章節(jié)“填坑”主要是在現(xiàn)有工程中添加 InstantiationStrategy 實例化策略接口,以及補充相應的 getBean 入?yún)⑿畔?,讓外部調(diào)用時可以傳遞構造函數(shù)的入?yún)⒉㈨樌麑嵗?/p>
2. 新增 getBean 接口
- public interface BeanFactory {
- Object getBean(String name) throws BeansException;
- Object getBean(String name, Object... args) throws BeansException;
- }
BeanFactory 中我們重載了一個含有入?yún)⑿畔?args 的 getBean 方法,這樣就可以方便的傳遞入?yún)⒔o構造函數(shù)實例化了。
3. 定義實例化策略接口
cn.bugstack.springframework.beans.factory.support.InstantiationStrategy
- public interface InstantiationStrategy {
- Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;
- }
- 在實例化接口 instantiate 方法中添加必要的入?yún)⑿畔ⅲǎ篵eanDefinition、 beanName、ctor、args
- 其中 Constructor 你可能會有一點陌生,它是 java.lang.reflect 包下的 Constructor 類,里面包含了一些必要的類信息,有這個參數(shù)的目的就是為了拿到符合入?yún)⑿畔⑾鄬臉嬙旌瘮?shù)。
- 而 args 就是一個具體的入?yún)⑿畔⒘耍罱K實例化時候會用到。
4. JDK 實例化
cn.bugstack.springframework.beans.factory.support.SimpleInstantiationStrategy
- public class SimpleInstantiationStrategy implements InstantiationStrategy {
- @Override
- public 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 是否為空,如果為空則是無構造函數(shù)實例化,否則就是需要有構造函數(shù)的實例化。
這里我們重點關注有構造函數(shù)的實例化,實例化方式為 clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);,把入?yún)⑿畔鬟f給 newInstance 進行實例化。
5. Cglib 實例化
cn.bugstack.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy
- public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {
- @Override
- public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(beanDefinition.getBeanClass());
- enhancer.setCallback(new NoOp() {
- @Override
- public int hashCode() {
- return super.hashCode();
- }
- });
- if (null == ctor) return enhancer.create();
- return enhancer.create(ctor.getParameterTypes(), args);
- }
- }
其實 Cglib 創(chuàng)建有構造函數(shù)的 Bean 也非常方便,在這里我們更加簡化的處理了,如果你閱讀 Spring 源碼還會看到 CallbackFilter 等實現(xiàn),不過我們目前的方式并不會影響創(chuàng)建。
6. 創(chuàng)建策略調(diào)用
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
- public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {
- private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
- @Override
- protected 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 抽象類中定義了一個創(chuàng)建對象的實例化策略屬性類 InstantiationStrategy instantiationStrategy,這里我們選擇了 Cglib 的實現(xiàn)類。
接下來抽取 createBeanInstance 方法,在這個方法中需要注意 Constructor 代表了你有多少個構造函數(shù),通過 beanClass.getDeclaredConstructors() 方式可以獲取到你所有的構造函數(shù),是一個集合。
接下來就需要循環(huán)比對出構造函數(shù)集合與入?yún)⑿畔?args 的匹配情況,這里我們對比的方式比較簡單,只是一個數(shù)量對比,而實際 Spring 源碼中還需要比對入?yún)㈩愋停駝t相同數(shù)量不同入?yún)㈩愋偷那闆r,就會拋異常了。
五、測試
1. 事先準備
cn.bugstack.springframework.test.bean.UserService
- public class UserService {
- private String name;
- public UserService(String name) {
- this.name = name;
- }
- public void queryUserInfo() {
- System.out.println("查詢用戶信息:" + name);
- }
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder("");
- sb.append("").append(name);
- return sb.toString();
- }
- }
這里唯一多在 UserService 中添加的就是一個有 name 入?yún)⒌臉嬙旌瘮?shù),方便我們驗證這樣的對象是否能被實例化。
2. 測試用例
cn.bugstack.springframework.test.ApiTest
- @Test
- public void test_BeanFactory() {
- // 1.初始化 BeanFactory
- DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- // 2. 注入bean
- BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
- beanFactory.registerBeanDefinition("userService", beanDefinition);
- // 3.獲取bean
- UserService userService = (UserService) beanFactory.getBean("userService", "小傅哥");
- userService.queryUserInfo();
- }
在此次的單元測試中除了包括;Bean 工廠、注冊 Bean、獲取 Bean,三個步驟,還額外增加了一次對象的獲取和調(diào)用。這里主要測試驗證單例對象的是否正確的存放到了緩存中。
此外與上一章節(jié)測試過程中不同的是,我們把 UserService.class 傳遞給了 BeanDefinition 而不是像上一章節(jié)那樣直接 new UserService() 操作。
3. 測試結果
查詢用戶信息:小傅哥
Process finished with exit code 0
- 從測試結果來看,最大的變化就是可以滿足帶有構造函數(shù)的對象,可以被實例化了。
- 你可以嘗試分別使用兩種不同的實例化策略,來進行實例化。SimpleInstantiationStrategy、CglibSubclassingInstantiationStrategy
4. 操作案例
這里我們再把幾種不同方式的實例化操作,放到單元測試中,方便大家比對學習。
4.1 無構造函數(shù)
- @Test
- public void test_newInstance() throws IllegalAccessException, InstantiationException {
- UserService userService = UserService.class.newInstance();
- System.out.println(userService);
- }
這種方式的實例化也是我們在上一章節(jié)實現(xiàn) Spring Bean 容器時直接使用的方式
4.2 驗證有構造函數(shù)實例化
- @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);
- }
從最簡單的操作來看,如果有構造函數(shù)的類需要實例化時,則需要使用 getDeclaredConstructor 獲取構造函數(shù),之后在通過傳遞參數(shù)進行實例化。
4.3 獲取構造函數(shù)信息
- @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);
這個案例中其實最核心的點在于獲取一個類中所有的構造函數(shù),其實也就是這個方法的使用 beanClass.getDeclaredConstructors()
4.4 Cglib 實例化
- @Test
- public void test_cglib() {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(UserService.class);
- enhancer.setCallback(new NoOp() {
- @Override
- public int hashCode() {
- return super.hashCode();
- }
- });
- Object obj = enhancer.create(new Class[]{String.class}, new Object[]{"小傅哥"});
- System.out.println(obj);
- }
此案例演示使用非常簡單,但關于 Cglib 在 Spring 容器中的使用非常多,也可以深入的學習一下 Cglib 的擴展知識。
六、總結
本章節(jié)的主要以完善實例化操作,增加 InstantiationStrategy 實例化策略接口,并新增了兩個實例化類。這部分類的名稱與實現(xiàn)方式基本是 Spring 框架的一個縮小版,大家在學習過程中也可以從 Spring 源碼找到對應的代碼。
從我們不斷的完善增加需求可以看到的,當你的代碼結構設計的較為合理的時候,就可以非常容易且方便的進行擴展不同屬性的類職責,而不會因為需求的增加導致類結構混亂。所以在我們自己業(yè)務需求實現(xiàn)的過程中,也要盡可能的去考慮一個良好的擴展性以及拆分好類的職責。
動手是學習起來最快的方式,不要讓眼睛是感覺看會了,但上手操作就廢了。也希望有需要的讀者可以親手操作一下,把你的想法也融入到可落地實現(xiàn)的代碼里,看看想的和做的是否一致。