手寫Web框架之實(shí)現(xiàn)IOC/DI

image

介紹

嘗試寫一個(gè)自己的Web框架,先模仿,再探索走出自己的道路,最終超越??蚣苡玫亩嗔?,但是寫框架是第一次,而且模仿的還是Java生態(tài)中的基石——Spring。先了解其思路,一步一步去實(shí)現(xiàn),或許性能不如正主,但是可以一點(diǎn)點(diǎn)靠近。學(xué)習(xí)然后模仿是通往大牛道路的第一步。

一.注解

  1. Autowired注解是自動(dòng)注入,作用是將實(shí)例注入到帶有@Autowired注解的變量中。
  2. Component注解是組件,作用是框架會(huì)掃描所有帶@Component的class并進(jìn)行實(shí)例化。
  3. ComponentSacn注解是掃描路徑,作用是設(shè)置框架要掃描的包路徑。
  4. Scope注解是單例/原型標(biāo)識(shí),作用是不加默認(rèn)是單例Bean,加了@Scope("prototype")后將是原型Bean(多例模式)。

1.1 Autowired

package xyz.hcworld.jubilant.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName: Autowired
 * @Author: 張紅塵
 * @Date: 2021-06-15
 * @Version: 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Autowired {

    /**
     * @return 組件名
     */
    String value() default "";

}

1.2 Component

package xyz.hcworld.jubilant.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注冊(cè)Bean組件
 *
 * @ClassName: Component
 * @Author: 張紅塵
 * @Date: 2021-06-13
 * @Version: 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Component {
    /**
     * @return 組件名
     */
    String value() default "";

}

1.3 ComponentSacn

package xyz.hcworld.jubilant.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 掃描配置文件注解
 *
 * @ClassName: ComponentSacn
 * @Author: 張紅塵
 * @Date: 2021-06-13
 * @Version: 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScan {
    /**
     * @return 掃描路徑
     */
    String value() default "";
}

1.4 Scope

package xyz.hcworld.jubilant.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 單例Bean/原型Bean標(biāo)識(shí)
 * @ClassName: Scope
 * @Author: 張紅塵
 * @Date: 2021-06-15
 * @Version: 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Scope {

    String value();
}

二、Bean操作

  1. BeanDefinition類是建模對(duì)象,通過建模對(duì)象創(chuàng)建實(shí)例。
  2. BeanNameAware接口是bean名的Aware回調(diào)接口,繼承接口可以獲取bean的benaName。
  3. BeanPostProcessor接口是初始化前后的增強(qiáng)接口,繼承接口可以對(duì)bean進(jìn)行動(dòng)態(tài)增強(qiáng),可以使用JDK增強(qiáng)或者CGLib增強(qiáng)。(可以通過這個(gè)接口實(shí)現(xiàn)AOP)。
  4. InitializingBean接口是應(yīng)用級(jí)的初始化接口,繼承該接口后框架實(shí)例化Bean后會(huì)對(duì)Bean進(jìn)行初始化操作。

2.1 BeanDefinition

package xyz.hcworld.jubilant.beans;

/**
 * bean的配置信息
 * @ClassName: BeanDefinition
 * @Author: 張紅塵
 * @Date: 2021-06-15
 * @Version: 1.0
 */
public class BeanDefinition {
    /**
     * 類的類型
     */
    private Class<?> clazz;
    /**
     * 作用域 是否是懶加載
     */
    private String scope;


    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

2.2 BeanNameAware

package xyz.hcworld.jubilant.beans;

/**
 * bean名的Aware回調(diào)接口
 * @ClassName: BeanNameAware
 * @Author: 張紅塵
 * @Date: 2021-06-16
 * @Version: 1.0
 */
public interface BeanNameAware {
    /**
     * 名字
     * @param name
     */
    void setBeanName(String name);

}

2.3 BeanPostProcessor

/**
 * 后置處理器(bean初始化前后的增強(qiáng)處理)
 *
 * @ClassName: BeanPostProcessor
 * @Author: 張紅塵
 * @Date: 2021-06-16
 * @Version: 1.0
 */
public interface BeanPostProcessor {
    /**
     * bean初始化前調(diào)用
     *
     * @param bean     bean本身
     * @param beanName bean的名字
     * @return
     */
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    /**
     * bean初始化后調(diào)用
     *
     * @param bean     bean本身
     * @param beanName bean的名字
     * @return
     */
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

2.4 InitializingBean

package xyz.hcworld.jubilant.beans;

/**
 * 初始化機(jī)制
 * @ClassName: Initializingbean
 * @Author: 張紅塵
 * @Date: 2021-06-16
 * @Version: 1.0
 */
public interface InitializingBean {
    /**
     * 初始化
     * @throws Exception
     */
    void afterPropertiesSet() throws Exception;
}

三.容器類

  1. Search類是搜索class的類,分別對(duì)jar與非jar兩種情況的class進(jìn)行掃描。
  2. JubilantApplicationContext是核心容器類,ioc是整個(gè)流程都在當(dāng)前容器進(jìn)行。流程如下:獲取所有class的路徑->掃描所有帶@Component的class->生成所有帶@Component的class的BeanDefinition對(duì)象-->存入BeanDefinitionMap-->遍歷BeanDefinitionMap生成所有單例Bean存入單例池。

3.1 掃描class模塊

package xyz.hcworld.jubilant.core;

import java.io.File;
import java.io.IOException;
import java.net.URL;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @ClassName: Search
 * @Author: 張紅塵
 * @Date: 2021-06-18
 * @Version: 1.0
 */
public class Search {


    /**
     * 獲取某包下(包括該包的所有子包)所有類
     *
     * @param packageName 包名
     * @return 類的完整名稱
     */
    List<String> getClassName(String packageName) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        URL url = loader.getResource(packageName.replace(".", "/"));
        if (url == null) {
            return new ArrayList<>();
        }
        return "file".equals(url.getProtocol())?
                getClassNameByFile(url.getPath(), null, packageName.replace(".", System.getProperty("file.separator")))
                :getClassNameByJar(url.getPath());
    }

    /**
     * 從項(xiàng)目文件獲取某包下所有類
     *
     * @param filePath  文件路徑
     * @param className 類名集合
     * @return 類的完整名稱
     */
    private static List<String> getClassNameByFile(String filePath, List<String> className, String packageName) {
        List<String> myClassName = new ArrayList<>();
        File file = new File(filePath);
        File[] childFiles = file.listFiles();
        if (childFiles == null) {
            return myClassName;
        }
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, packageName));
                continue;
            }
            String childFilePath = childFile.getPath();
            if (childFilePath.endsWith(".class")) {
                //截取路徑
                childFilePath = childFilePath.substring(childFilePath.indexOf(packageName), childFilePath.lastIndexOf("."));
                //將反斜杠轉(zhuǎn)成.
                myClassName.add(childFilePath.replace(System.getProperty("file.separator"), "."));
            }
        }
        return myClassName;
    }

    /**
     * 從jar獲取某包下所有類
     *
     * @param jarPath jar文件路徑
     * @return 類的完整名稱
     */
    private static List<String> getClassNameByJar(String jarPath) {
        List<String> myClassName = new ArrayList<>();
        try (JarFile jarFile = new JarFile(System.getProperty("user.dir") + System.getProperty("file.separator") + System.getProperty("java.class.path"))) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String entryName = jarEntry.getName();
                if (entryName.startsWith(jarPath) && entryName.endsWith(".class")) {
                    entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf(".class"));
                    myClassName.add(entryName);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return myClassName;
    }
}

3.2 容器化

package xyz.hcworld.jubilant.core;

import xyz.hcworld.jubilant.beans.BeanDefinition;
import xyz.hcworld.jubilant.beans.BeanNameAware;
import xyz.hcworld.jubilant.beans.BeanPostProcessor;
import xyz.hcworld.jubilant.beans.InitializingBean;
import xyz.hcworld.jubilant.annotation.Autowired;
import xyz.hcworld.jubilant.annotation.Component;
import xyz.hcworld.jubilant.annotation.ComponentScan;
import xyz.hcworld.jubilant.annotation.Scope;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 容器類
 *
 * @ClassName: WinterApplicationContext
 * @Author: 張紅塵
 * @Date: 2021-06-13
 * @Version: 1.0
 */
public class JubilantApplicationContext {
    /**
     * 配置文件
     */
    private Class configClass;

    /**
     * 單例池,保存單例對(duì)象
     */
    private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    /**
     * 所有Bean的配置數(shù)據(jù)的配置池
     */
    private final ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    /**
     * 初始化bean的類
     */
    private final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    /**
     * 構(gòu)造方法
     *
     * @param configClass 配置類
     */
    public JubilantApplicationContext(Class<?> configClass) {
        this.configClass = configClass;
    // 判斷是否有ComponentScan注解,如果沒有就結(jié)束不往下走
    if (!configClass.isAnnotationPresent(ComponentScan.class)) {
            return;
    }
    // 解析配置類
    // 對(duì)ComponentScan注解解析 -->掃描路徑 -->掃描-->生成BeanDefinition對(duì)象-->存入BeanDefinitionMap
    scan(configClass);
        // Aspect類先實(shí)例化
    for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            if (!entry.getValue().getClazz().isAnnotationPresent(Aspect.class)) {
                continue;
            }
            Object bean = createBean(entry.getKey(), entry.getValue());
            singletonObjects.put(entry.getKey(), bean);
    }

        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            // 創(chuàng)建所有的單例bean,已經(jīng)實(shí)例化的就不需要實(shí)例化了
            if ("singleton".equals(entry.getValue().getScope()) && !singletonObjects.containsKey(entry.getKey())) {
                Object bean = createBean(entry.getKey(), entry.getValue());
                singletonObjects.put(entry.getKey(), bean);
            }
        }

    }

    /**
     * 掃描
     *
     * @param configClass
     */
    private void scan(Class<?> configClass) {

        ComponentScan componentScanAnnotation = configClass.getDeclaredAnnotation(ComponentScan.class);
        // 掃描路徑(不填路徑默認(rèn)獲取Application文件所在的包) xyz.hcworld
        String path = componentScanAnnotation.value().isEmpty() ? configClass.getPackage().getName() : componentScanAnnotation.value();
        Search search = new Search();
        //獲取文件夾或者jar下的所有類名
        List<String> classNames = search.getClassName(path);
        if (classNames == null) {
            return;
        }
        ClassLoader classLoader = JubilantApplicationContext.class.getClassLoader();
        try {
            for (String className : classNames) {
                Class<?> clazz = classLoader.loadClass(className);
                // 如果沒有Component代表這不是一個(gè)bean結(jié)束本次循環(huán)
                if (!clazz.isAnnotationPresent(Component.class)) {
                    continue;
                }
                // 創(chuàng)建bean對(duì)象
                // 解析類,判斷當(dāng)前Bean是單例還是原型(prototype)Bean 生成BeanDefinition對(duì)象

                // 統(tǒng)一處理方式:BeanDefinition
                Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                // 獲取Bean的名字
                String beanName = componentAnnotation.value();
                //如果沒有自定義beanName則以類名(首字母小寫)為beanName
                if (beanName.isEmpty()) {
                    String[] name = className.split("\\.");
                    StringBuilder nameBuffer = new StringBuilder(name[name.length - 1]);
                    beanName = nameBuffer.replace(0, 1, Character.toString(nameBuffer.charAt(0)).toLowerCase()).toString();
                }

                // bean的配置信息
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setClazz(clazz);
                // 判斷Scope是否存在,存在則是自定義配置,不存在則為單例
                beanDefinition.setScope(
                        clazz.isAnnotationPresent(Scope.class) ?
                                clazz.getDeclaredAnnotation(Scope.class).value() : "singleton");
                // 存進(jìn)配置池
                beanDefinitionMap.put(beanName, beanDefinition);
                // 將實(shí)現(xiàn)BeanPostProcessor(初始化bean)接口的類存到初始化配置池中
                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                    BeanPostProcessor beanPostProcessor = (BeanPostProcessor) createBean(beanName, beanDefinitionMap.get(beanName));
                    beanPostProcessorList.add(beanPostProcessor);
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根據(jù)配置創(chuàng)建出一個(gè)bean對(duì)象(反射)
     *
     * @param beanDefinition 自定義配置
     * @return bean對(duì)象
     */
    public Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class<?> clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            // 依賴注入
            for (Field declaredField : clazz.getDeclaredFields()) {
                // 沒有Autowired就不注入
                if (!declaredField.isAnnotationPresent(Autowired.class)) {
                    continue;
                }
                String[] name = declaredField.getType().getName().split("\\.");
                StringBuilder nameBuffer = new StringBuilder(name[name.length - 1]);
                nameBuffer.replace(0, 1, Character.toString(nameBuffer.charAt(0)).toLowerCase());
                // 通過反射注入屬性
                Object bean = getBean(nameBuffer.toString());
                if (bean == null) {
                    throw new NullPointerException();
                }
                // 當(dāng)變量為private時(shí)需要忽略訪問修飾符的檢查
                declaredField.setAccessible(true);
                declaredField.set(instance, bean);
            }
            // Aware回調(diào)
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            //初始化前的增強(qiáng)
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }

            // 初始化
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }
            // BeanPostProcessor 外部擴(kuò)展機(jī)制(Bean的前后置處理)
            //初始化后的增強(qiáng)
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }

            return instance;
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 獲取Bean對(duì)象
     *
     * @param beanName bean名字
     * @return bean對(duì)象
     */
    public Object getBean(String beanName) {
        // 判斷bean是否存在,不存在拋出npe異常
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        // 判斷是否是單例,是返回單例對(duì)象,不是創(chuàng)建原型對(duì)象
        return "singleton".equals(beanDefinition.getScope()) ?
                singletonObjects.get(beanName) : createBean(beanName, beanDefinition);
    }

    /**
     * 獲取文件制定包下的所有class文件(不管有多少層)
     *
     * @param file      目錄
     * @param filePaths 臨時(shí)存儲(chǔ)
     * @return 所有class文件的絕對(duì)路徑
     */
    private Set<File> getFilePath(File file, Set<File> filePaths) {
        File[] files = file.listFiles();
        if (files == null || files.length == 0) {
            return new HashSet<>();
        }
        for (File file1 : files) {
            if (file1.isDirectory()) {
                //遞歸調(diào)用
                getFilePath(file1, filePaths);
                continue;
            }
            //保存文件路徑到集合中
            filePaths.add(file1);
        }
        return filePaths;
    }
}

四、結(jié)果

ioc.png

未來發(fā)展

展望

  • 第一步,通過模仿Spring實(shí)現(xiàn)IOC
  • 第二步,通過模仿Spring實(shí)現(xiàn)AOP
  • 第三步,實(shí)現(xiàn)MVC框架

說明

ioc 的代碼是跟著B站的某教程敲的。但是修改了許多不合理的部分,如掃描class部分,原教程打包后無法運(yùn)行,因?yàn)楂@取不了絕對(duì)路徑。修改了既能打包jar后運(yùn)行又能在ide中運(yùn)行。以及原本喪心病狂的if-else-for嵌套改成了防御性的if判斷,去除了else。
先模仿在摸索自己的道路。:doge:

不足

筆者水平有限也非大廠的大牛,如有不對(duì)之處請(qǐng)指正。以及部分硬編碼問題純粹是筆者懶,還沒來得及改,自行修改即可。

地址

GitHub:https://github.com/z875479694h/jubilant

Gitee:https://gitee.com/hcworld/jubilant

聯(lián)系方式

公眾號(hào):青山有錄
Github:https://github.com/z875479694h
博客:https://www.hcworld.xyz

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容