1. 程式人生 > >從零寫Spring註解版框架系列 IoC篇 (2)實現 @Component、@Autowired、@Qualifier註解

從零寫Spring註解版框架系列 IoC篇 (2)實現 @Component、@Autowired、@Qualifier註解

本文承接了上一篇文章的思路進行程式碼實現,並搭建起一個基本可用的基於@Component、@Autowired、@Qualifier 註解的 IoC 框架。

專案 Github 地址為:https://github.com/linshenkx/winter-core
相關文章地址:從零寫Spring註解版框架系列 IoC篇 (1) 框架設計

文章目錄

一 結構設計

首先建立一個 Maven 工程 winter-core,這裡我們我們只需要依賴於 commons-lang 工具包,另外還有測試用的 junit 包。再建立各個類如下:
結構圖

核心註解在 annotation 包下,util 包下是 ClassUtil 工具類 和 StringUtil 工具類,真正的核心元件是 BeanFactory 下的 ClassPathAnnotaionApplicationContext,由該類來完成 Bean 掃描和初始化及注入等功能。

二 註解類編寫

註解類本身沒什麼特別的

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-28
 * @Description: 依賴注入的註解,目前只實現 Field 注入
 */
@Target({ElementType.FIELD}
) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; } /** * @version V1.0 * @author: lin_shen * @date: 18-11-28 * @Description: 標記掃描註解,使用該註解的類會被 BeanFactory 收入管理 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { String value() default ""; } /** * @version V1.0 * @author: lin_shen * @date: 18-11-28 * @Description: 指定名稱用的註解,跟@Autowired搭配使用,實現在型別相同的情況下通過指定名識別 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Qualifier { String value() default ""; }

二 ApplicationContext構建

首先來看一下 ClassPathAnnotationApplicationContext 類的總體結構,主要方法的實現放後面說

ApplicationContext構造方法的唯一引數是 packageName , 包名,即掃描包的範圍。構造方法裡主要是呼叫 scanPackage方法對指定範圍進行掃描,將含有 @Component 註解的類收集儲存到 beanDefinationFactory 裡。需要注意的是這個時候收集的是 Class 物件而非 Bean 例項。

除了構造方法的唯一的 public 方法是 getBean(Class type,String beanId,boolean force),對於使用者來說構建完 ApplicationContext 後即可通過此方法來獲取由 IoC 容器管理的例項了。如果 singletonbeanFactory 中已經存在目標單例物件,則直接返回,否則該方法將呼叫 initBean(Class type,String beanId) 完成目標單例的初始化操作,再返回。

也就是說,這個 IoC 容器的工作流程是在初始化的時候完成包掃描工作,將 Bean資訊收集起來,外界使用 getBean 方法獲取例項的時候再進行該Bean的初始化工作,並將單例例項儲存管理,下次索要時直接返回。

public class ClassPathAnnotationApplicationContext {

    /**
     * 掃包範圍
     */
    private String packageName;

    /**
     * 類的快取map,用於避免單例情況下的迴圈引用
     */
    private Map<String,Object> cacheBeanFactory=new ConcurrentHashMap<>();

    /**
     * 用於儲存真正的類資訊
     * 全稱類名(type)+自定義類名(beanId)==> 真類資訊
     */
    private Map<String,Map<String,Class>> beanDefinationFactory;

    /**
     * 由真類資訊的全稱類名確定唯一單例物件
     */
    private Map<String,Object> singletonbeanFactory=new ConcurrentHashMap<>();

    /**
     * 構造方法,傳入基礎掃描包地址
     * @param packageName
     */
    public ClassPathAnnotationApplicationContext(String packageName) {
        this.packageName = packageName;
        scanPackage(packageName);
    }

    /**
     * 獲取指定 Bean 例項
     * @param type 型別
     * @param beanId beanId,即指定名
     * @param force 在該型別只存在一個 Bean 例項的時候是否按照 必須按照 beanId 匹配(如為false則可直接返回唯一例項)
     * @return
     */
    public Object getBean(Class type,String beanId,boolean force){};

    /**
     * 使用反射機制獲取該包下所有的類已經存在bean的註解類
     * @param packageName 掃描包路徑
     */
    private void scanPackage(String packageName) {};

    /**
     * 完成類的初始化
     * @param type 型別
     * @param beanId 指定名
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object initBean(Class type,String beanId)
            throws InstantiationException, IllegalAccessException {};

    /**
     * 取出clazz上Component註解的非預設值,如非Component註解標識類或使用的是預設值則丟擲執行時異常
     * @param clazz
     * @return
     */
    private static String getComponentName(Class clazz){};

三 scanPackage包掃描方法

任務

scanPackage方法的目標是初始化 beanDefinationFactory。

beanDefinationFactory 是巢狀Map結構,其核心 value 是 Class,即類物件,對應Spring中的BeanDIfinition,即Bean描述資訊。其外層 key 是全稱類名 type,內層 key 是 指定名 beanId。需要注意的是 type不一定是 Class的全稱類名。
正如 通過介面和指定名來確定唯一實現類,type代表的除了自身類還可以是超類或者介面的全稱類名。

思路

先使用反射機制獲取目標包下所有的類,再獲取其中標記 @Component 註解的類,對每一個標記 @Component 的類(classInfo)進行如下操作

  1. 獲取 classInfo 的所有非Object父類、實現介面以及自身類作為 superClassList。
  2. 遍歷 superClassList中元素 aClass
    1. 獲取aClass的全稱類名在 beanDefinationFactory 下對應的 Map<String, Class> beanDefinationMap,沒有則構建
    2. 根據@Component裡有無指定值(非預設值)進行不同處理,有則按指定值存入,沒有則按簡單類名首字母小寫傳入。這個過程如果有重複key值則丟擲異常

程式碼

    /**
     * 使用反射機制獲取該包下所有的類已經存在bean的註解類
     * @param packageName 掃描包路徑
     */
    private void scanPackage(String packageName) {
        beanDefinationFactory=new HashMap<>();
        if (StringUtils.isEmpty(packageName)) {
            throw new RuntimeException("掃包地址不能為空!");
        }
        // 使用反射技術獲取當前包下所有的類
        List<Class<?>> classesByPackageName = ClassUtil.getClasses(packageName);
        // 對存在註解的類進行記錄
        for (Class classInfo : classesByPackageName) {
            Component component = (Component) classInfo.getDeclaredAnnotation(Component.class);
            if(component==null){
                continue;
            }
            System.out.println("|classInfo:"+StringUtil.toLowerCaseFirstOne(classInfo.getName()));

            //存入按型別存取的單例BeanFactory(介面和非object父類和自身型別)
            classInfo.getAnnotatedInterfaces();
            Class[] interfaces = classInfo.getInterfaces();
            List<Class> superClassList = ClassUtil.getSuperClassList(classInfo);
            superClassList.addAll(Arrays.asList(interfaces));
            superClassList.add(classInfo);

            System.out.println("superClassListSize:"+superClassList.size());

            for (Class aClass : superClassList) {
                Map<String, Class> beanDefinationMap = beanDefinationFactory.computeIfAbsent(StringUtil.toLowerCaseFirstOne(aClass.getName()), k -> new HashMap<>());

                System.out.println("Type:"+StringUtil.toLowerCaseFirstOne(aClass.getName()));

                if(StringUtils.isNotEmpty(component.value())){
                    //如果component有值則使用該值(對應本類classInfo的資訊)
                    if (beanDefinationMap.get(getComponentName(classInfo))!=null){
                        throw new RuntimeException("出現無法通過name區分的重複型別:"+StringUtil.toLowerCaseFirstOne(aClass.getName())+" "+getComponentName(classInfo));
                    }
                    //存入按指定名存取的單例BeanFactory
                    beanDefinationMap.put(getComponentName(classInfo),classInfo);
                    System.out.println("putName:"+getComponentName(classInfo));
                }else {
                    //如果component沒有值則使用當前型別名
                    if (beanDefinationMap.get(StringUtil.toLowerCaseFirstOne(aClass.getName()))!=null){
                        throw new RuntimeException("出現無法通過name區分的重複型別:"+StringUtil.toLowerCaseFirstOne(aClass.getName()));
                    }
                    beanDefinationMap.put(StringUtil.toLowerCaseFirstOne(aClass.getSimpleName()),classInfo);
                    System.out.println("putType:"+StringUtil.toLowerCaseFirstOne(aClass.getName()));
                }


            }
        }

        for (Map.Entry<String, Map<String, Class>> stringMapEntry : beanDefinationFactory.entrySet()) {
            System.out.println("Type:"+stringMapEntry.getKey());
            stringMapEntry.getValue().keySet().forEach(System.out::println);
        }
        System.out.println("------------");

    }

四 initBean 初始化Bean方法

任務

根據 type(全稱類名)和 beanId (指定名)從 beanDefinationFactory 獲取 真實類資訊,完成例項初始化並放入 singletonbeanFactory中,並返回例項

此方法只負責對類進行初始化,不檢查是否已經有完成初始化的類,檢查是否已經有例項化的類並決定是否直接返回是 getBean 的工作

singletonbeanFactory 和 cacheBeanFactory 都是以真實類的全稱類名為key存放bean例項。

思路

先從 beanDefinationFactory 獲取 真實型別 clazz,如果clazz為null則丟擲異常,然後進入建立流程:

  1. 根據 clazz 判斷 cacheBeanFactory 中例項,如果已經存在(不為null,即沒有完全完成初始化)則意味著在建立bean的過程同一bean又被建立,說明存在迴圈引用,應丟擲異常。如果不存在則將未完成建立的bean放入 cacheBeanFactory,等建立完成的時候再移除。
  2. 獲取 clazz 所有的 Field,遍歷,對標記有 @Autowired 的 Field 根據有無 @Qualifier 進行對 Field 的值進行注入。
    1. 沒有@Qualifier註解:
      設定beanId為域物件的值,採用非強制匹配,在匹配型別僅有一個實現例項時忽略beanId要求
    2. 有@Qualifier
      設定beanId為Qualifier註解的value,採用強制匹配,在匹配型別僅有一個實現例項時如果beanId不匹配仍會報錯
  3. 將 完成初始化的 bean 放入 singletonbeanFactory 進行管理
  4. 移除 cacheBeanFactory 中對應的例項
  5. 返回例項

程式碼

    /**
     * 完成類的初始化
     * @param type 型別
     * @param beanId 指定名
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object initBean(Class type,String beanId)
            throws InstantiationException, IllegalAccessException {

        //如果不存在該類的真類資訊,則丟擲異常
        Class<?> clazz = beanDefinationFactory.get(type.getName()).get(beanId);
        if(clazz==null){
            throw new RuntimeException("沒有找到type為:"+type.getName()+",name為:"+beanId+"的類");
        }

        //進入建立流程
        try {
            //利用cacheBeanFactory識別迴圈引用
            Object targetObject = cacheBeanFactory.get(clazz.getName());
            if(targetObject!=null){
                //在建立bean的過程bean又被建立,說明存在迴圈引用,丟擲異常
                throw new RuntimeException("迴圈引用");
            }else {
                targetObject=clazz.newInstance();
                cacheBeanFactory.put(clazz.getName(),targetObject);
            }

            //正式進入初始化,給@Autowired的field賦值
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Autowired autowired = declaredField.getAnnotation(Autowired.class);
                if(autowired==null){
                    continue;
                }
                //判斷是否有Qualifier註解
                Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                declaredField.setAccessible(true);
                //對該field賦值
                if(qualifier==null){
                    //如果沒有Qualifier註解則設定beanId為域物件的值,採用非強制匹配,在匹配型別僅有一個實現例項時忽略beanId要求
                    declaredField.set(targetObject,getBean(declaredField.getType(),declaredField.getName(),false));
                }else {
                    //如果有Qualifier註解則設定beanId為Qualifier註解的value,採用強制匹配,在匹配型別僅有一個實現例項時如果beanId不匹配仍會報錯
                    declaredField.set(targetObject,getBean(declaredField.getType(),qualifier.value(),true));
                }
            }
            singletonbeanFactory.put(clazz.getName(),targetObject);
            return targetObject;
        }  finally {
            cacheBeanFactory.remove(clazz.getName());
        }

    }

五 getBean 獲取Bean方法

任務

根據 type(全稱類名)和 beanId (指定名)返回例項。如果已經存在則直接返回,不存在則呼叫 initBean 方法再返回。

思路

  1. 先根據 type 從 beanDefinationFactory 獲取該型別下不同名的類資訊Map: beanClassMap,

    1. 如果不存在則丟擲異常。
    2. 如果存在則根據引數 force 判斷是否為強匹配模式
      1. 如果是強匹配模式則檢測 beanClassMap 中是否有對應 beanId
        1. 沒有則丟擲異常
        2. 有則繼續
      2. 如果是不是強匹配模式則檢測 beanClassMap 中是否有對應 beanId,
        1. 沒有的話再判斷 beanClassMap是否只有一個類資訊
          1. 是的話則將其 對應的 beanId 設定給 原beanId
          2. 否則無法分辨,則丟擲異常。否則無法分辨,則丟擲異常。
        2. 有則繼續
  2. 至此則可根據 type和beanId獲取真實類資訊 clazz

  3. 然後嘗試從singletonbeanFactory中獲取例項,如果已經存在則直接返回,不存在則呼叫 initBean 方法再返回。

程式碼


    /**
     * 獲取指定 Bean 例項
     * @param type 型別
     * @param beanId beanId,即指定名
     * @param force 在該型別只存在一個 Bean 例項的時候是否按照 必須按照 beanId 匹配(如為false則可直接返回唯一例項)
     * @return
     */
    public Object getBean(Class type,String beanId,boolean force){

        System.out.println("getBean,type:"+type.getName()+",name:"+beanId);

        Map<String,Class> beanClassMap = beanDefinationFactory.get(type.getName());

        //如果沒有此型別則直接報錯
        if(beanClassMap.isEmpty()){
            throw new RuntimeException("沒有找到型別為:"+type.getName()+"的bean");
        }

        if(force){
            //如果是強匹配則要求beanId必須存在
            if(beanClassMap.get(beanId)==null){
                throw new RuntimeException("沒有找到型別為:"+type.getName()+" 指定名為:"+beanId+"的bean");
            }
        }else {
            //如果不是強匹配則允許beanId不存在,但此時對應型別的bean只能有一個,將beanId修改為僅有的那一個的id
            if(beanClassMap.get(beanId)==null ){
                if(beanClassMap.size()!=1){
                    throw new RuntimeException("無法分辨多個同類不同名物件,型別"+type.getName());
                }else {
                    beanId=beanClassMap.keySet().iterator().next();
                }
            }
        }
        Class targetClass=beanDefinationFactory.get(type.getName()).get(beanId);

        Object targetBean=singletonbeanFactory.get(targetClass.getName());

        if(targetBean!=null){
            return targetBean;
        }

        //不存在則初始化並收入管理
        try {

            System.out.println("初始化type為:"+type.getName()+",name為:"+beanId+"的類");

            targetBean = initBean(type,beanId);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return targetBean;
    }

六 工具類方法

getSuperClassList 父類獲取方法

    /**
     * 獲取clazz的所有父類(非Object)
     * @param clazz
     * @return
     */
    public static List<Class> getSuperClassList(Class clazz){
        List<Class> superClassList=new ArrayList();
        for(Class superClass = clazz.getSuperclass(); ((superClass!=null)&&(!"Object".equals(superClass.getSimpleName()))); superClass=superClass.getSuperclass()){
            superClassList.add(superClass);
        }
        return superClassList;
    }

getClasses 類獲取方法

    /**
     * 從包package中獲取所有的Class
     *
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) {

        // 第一個class類的集合
        List<Class<?>> classes = new ArrayList<>();
        // 是否迴圈迭代
        boolean recursive = true;
        // 獲取包的名字 並進行替換
        String packageDirName = packageName.replace('.', '/');
        // 定義一個列舉的集合 並進行迴圈來處理這個目錄下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 迴圈迭代下去
            while (dirs.hasMoreElements()) {
                // 獲取下一個元素
                URL url = dirs.nextElement();
                // 得到協議的名稱
                String protocol = url.getProtocol();
                // 如果是以檔案的形式儲存在伺服器上
                if ("file".equals(protocol)) 
            
           

相關推薦

Spring註解框架系列 IoC 2實現 @Component@Autowired@Qualifier註解

本文承接了上一篇文章的思路進行程式碼實現,並搭建起一個基本可用的基於@Component、@Autowired、@Qualifier 註解的 IoC 框架。 專案 Github 地址為:https://github.com/linshenkx/winter-core 相關文章地址:從

Spring註解框架系列 IoC 1 框架設計

本文的註解版IoC框架跟其他手寫IoC框架的不同之處在與:在實現了 @Component 和 @Autowired 的同時還實現了@Qualifier,並解決單例模式下迴圈依賴的問題,以上3個註解的使用效果參照 Spring 。 專案 Github 地址為:https://githu

分散式RPC框架 系列 1.0 2RPC-Common模組設計實現

RPC-Common模組提供RPC-Server和RPC-Client的通用物件,封裝統一規則,使RPC Server和RPC Client 可以基於同一協議通訊。主要包含底層通訊的Netty所需的編碼解碼器(RpcEncoder,RpcDecoder),實現自定義協議的傳輸物件(Rpc

開始的畢設--HTML(超文字標記語言)2

前往web 域名 域名是網站的名字,如 bupt.edu.cn 域名由一個集中的權威機構ICANN控制,以確保一次只能有一個人使用某個域名。(收費的) 如果我想獲得bupt.edu.cn的域名,是不是會得到www.bupt.edu.cn呢?看起來所有人都在使用

分散式RPC框架 系列 1.0 5整合測試

本篇將對前面幾篇模組作整合處理,使用spring-boot-starter的形式進行釋出。然後新建 examples 工程模組對其測試使用。 系列文章: 從零寫分散式RPC框架 系列 1.0 (1)架構設計 從零寫分散式RPC框架 系列 1.0 (2)RPC-Common模組設計

分散式RPC框架 系列 1.0 4RPC-Client模組設計實現

RPC-Client模組負責建立 動態代理物件 供 服務消費者 使用,而動態代理物件的方法執行則是通過RPC呼叫RPC-Server的服務實現。即RPC-Client遮蔽了底層的通訊過程,使得服務消費者可以基於介面透明使用服務提供者的服務。 系列文章: 從零寫分散式RPC框架 系

分散式RPC框架 系列 1.0 3RPC-Server模組設計實現

RPC-Server模組負責(1)將@RpcService註解標記的服務和自身資訊註冊到ZK叢集,(2)對外提供RPC服務實現,處理來自RPC-Client的請求。該模組整體的核心類為 RpcServer ,而真正處理請求的核心類是 RpcServerHandler 。另外還有一個 ZK

分散式RPC框架 系列 1.0 1架構設計

本系列文章的目的是搭建出一個基於Netty,Zookeeper和SpringBoot的簡易分散式RPC框架,並且釋出到Maven中央倉庫以 spring-boot-starter 的形式對外提供開箱即用的服務。1.0 版本使用 protobuf 來做序列化,最終

一個Java WEB框架Controller層轉換器

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架Controller層優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架IOC建立

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架框架的演進

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架Dao層優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架Server層 優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github 上一篇地

一個Java WEB框架

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

開始學C++之IO流類庫:檔案的讀二進位制檔案的讀檔案隨機讀

#include <cassert>#include <iostream>#include <fstream>#include <string>using namespace std;struct Test {     int a;     int b; };i

開始——基於角色的權限管理01補充

itl jsp mage logs log sonar class htm -1 此博文較為詳細的介紹從零開始——基於角色的權限管理01文中的兩個部分的流程(解釋代碼)。 1)  index.jsp中提交跳轉action      action的login,獲取jsp頁面傳

開始學習音視頻編程技術 開發環境搭建Qt4.86手動設置環境,主要就是設置g++和qmake,比較透徹,附下載鏈接

路徑 details 分享 baidu 末尾 是我 其中 找到 source 1.先下載安裝Qt 我們使用的版本是4.8。 可以自行百度下載也可以從下面的網盤地址下載: Qt庫和編譯器下載: 鏈接:http://pan.baidu.com/s/1hrUxLIG 密碼

開始使用CodeArt實踐最佳領域驅動開發

using emp 程序集 mman his return main 更新 物理 本章內容還在整理上傳中,你可以等全部更新完畢後再查閱也可以先預覽已上傳的內容。。。。。。 7. 應用層的命令模式   在上個章節裏我們設計並編碼了領域對象Permission,但是目前Perm

開始利用vue-cli搭建簡單音樂網站

路徑 nod .com mman csdn desc blog -a where 最近在學習vue框架,練習了一些例子之後,想著搭建一個vue項目,了解到官方有提供一個vue-cli工具來搭建項目腳手架,嘗試了一下,寫下博客來記錄一下。 一、工具環境 1、node.js 6