從零寫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)) {
                    // 獲取包的物理路徑
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式掃描整個包下的文件 並添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定義一個JarFile
                    JarFile jar;
                    try {
                        // 獲取jar
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        // 從此jar包 得到一個枚舉類
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同樣的進行循環迭代
                        while (entries.hasMoreElements()) {
                            // 獲取jar裏的一個實體 可以是目錄 和一些jar包裏的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/開頭的
                            if (name.charAt(0) == '/') {
                                // 獲取後面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定義的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"結尾 是一個包
                                if (idx != -1) {
                                    // 獲取包名 把"/"替換成"."
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                // 如果可以迭代下去 並且是一個包
                                if ((idx != -1) || recursive) {
                                    // 如果是一個.class文件 而且不是目錄
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        // 去掉後面的".class" 獲取真正的類名
                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

toLowerCaseFirstOne 首字母轉小寫方法

    /**
     * 首字母轉小寫
     * @param s
     * @return
     */
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return Character.toLowerCase(s.charAt(0)) + s.substring(1);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章