1. 程式人生 > >spring IOC容器自自實現

spring IOC容器自自實現

流程

元件一、配置檔案載入器 

public final class PropsUtil {
    /**
     * 獲取檔案流,轉成properti map記憶體
     */
    public  static Properties loadProps(String fileName){
		InputStream inputStream=Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
		Properties properties=new Properties();
        properties.load(inputStream);
	}
	
	 /**
     * 通過key 獲取對應value
     */
    public static String getPropertie(Properties properties,String key){
        String value="";
        if (properties.contains(key)){
            value=properties.getProperty(key);
        }
        return value;
    }
}

元件二、類載入器

/**
 * @author zhongailing
 * @version V1.0
 * @Description: 通過url載入對應類  如同scan配置 傳入最基礎的包名,通過包名裝配初始化所有bean,
 * @date Created in 2018-11-5 15:51
 * 1.根據包名獲取(檔案路徑),get類.class檔案
 * 2.傳入loadClass方法,返回類
 */
public class ClassLoadHandler {

    /**
     * 獲取類載入期
     * @return
     */
    public static ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 通過className載入類
     * @param className
     * @param isInitialized
     * @return
     */
    public static Class<?> loadClass(String className,Boolean isInitialized){
        Class<?> cls = null;
        try {
            //isInitialized標識是否初始化(執行靜態程式碼塊,new物件)
            cls=Class.forName(className,isInitialized,getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return cls;
    }

    /**
     * 獲取該包名下的所有類
     * @param packageName
     * @return
     */
    public static Set<Class<?>> getClassSet(String packageName){
        /**
         * 1. 獲取指定包名下的的所有類
         * 2. 根據包名將其轉換為檔案路徑
         * 3. 讀取class檔案或jar包
         * 4. 獲取指定的類名去載入類
         */
        Set<Class<?>> classSet = new HashSet<>();
        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
            while (urls.hasMoreElements()){
                URL url = urls.nextElement();
                String protocol = url.getProtocol(); //獲取此 URL 的協議名稱。
                if(protocol.equals("file")){
                    // %20 表示file協議?
                    String packagePath = url.getPath().replaceAll("%20", "");
                    addClass(classSet,packagePath,packageName);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classSet;
    }

    /**
     * 如果是檔案,就根據包名 和 檔名 組成類的全限定名稱,然後 載入類
     * @param classSet
     * @param packagePath 檔案(夾)的絕對路徑
     * @param packageName 和當前檔案(夾) 對應的包名
     */
    public static  void addClass(Set<Class<?>> classSet,String packagePath,String packageName){

        File[] files = new File(packagePath).listFiles(file -> {
            // 只需要 檔案並且是.class的檔案,或則是目錄 都返回true
            return file.isFile() && file.getName().endsWith(".class") || file.isDirectory();
        });

        for (File file : files) {
            String fileName = file.getName();
            if(file.isFile()){ // 是指定的檔案 就獲取到全限定類名 然後裝載它
                String className = fileName.substring(0, fileName.lastIndexOf(".")); // 把.class後最擷取掉
                if(StringUtils.isNotBlank(packageName)){
                    className = packageName + "." + className; // 根據包名 + 檔名 得到這個類的全限定名稱,
                }
                Class<?> cls = loadClass(className, false);
                classSet.add(cls);
            }else { // 是檔案 就遞迴自己. 獲取 資料夾的絕對路徑,和 當前資料夾對應的 限定包名.方便 檔案裡面直接使用
                String subPackagePath= fileName;
                if(StringUtils.isNotBlank(subPackagePath)){
                    subPackagePath = packagePath + "/" + subPackagePath; // 第一次:由基礎包名 得到絕對路徑,再加上當前資料夾名稱 = 當前資料夾的絕對路徑
                }
                subPackagePath = file.getAbsolutePath(); // 該方法獲得檔案的絕對路徑.和上面的程式碼效果是一致的
                String subPackageName = fileName;
                if(StringUtils.isNotBlank(subPackageName)){
                    subPackageName = packageName + "." + subPackageName; // 第一次: 基礎包名 加資料夾名稱 組合成 當前包名 +
                }
                addClass(classSet,subPackagePath, subPackageName);
            }
        }
    }

    public static void main(String[] args) {
        getClassSet("java/ioc");
    }

增加按照註解區分@[email protected] 不同bean的kv(class-obj)結構

/**
 * @author zhongailing
 * @version V1.0
 * @Description: 在裝配的class中區分出@[email protected]對應的bean
 * @date Created in 2018-11-5 16:15
 */
public final class AnnotationMapHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);
    private static final Set<Class<?>> CLASS_SET;
    static{
        LOGGER.debug("是否被載入了兩次");
        CLASS_SET = ClassLoadHandler.getClassSet(PropsUtil.getPropertie(null,"scan-package-name"));
    }

    public static Set<Class<?>> getClassSet() {
        return CLASS_SET;
    }

    /**
     * 獲取所有Services型別的註解類
     * @return
     */
    public static Set<Class<?>> getServiceClassSet(){
        Set<Class<?>> serviceClassSet = new HashSet<>();
        for (Class<?> c : CLASS_SET) {
            if(c.isAnnotationPresent(Services.class)){ // 是否存在 services註解
                serviceClassSet.add(c);
            };
        }
        return serviceClassSet;
    }

    /**
     * 獲取所有Controller型別的註解類
     * @return
     */
    public static Set<Class<?>> getControllerClassSet(){
        Set<Class<?>> controllerClassSet = new HashSet<>();
        for (Class<?> c : CLASS_SET) {
            if(c.isAnnotationPresent(Controller.class)){
                controllerClassSet.add(c);
            };
        }
        return controllerClassSet;
    }

    /**
     * 獲取所有的 bean : controller 和 Services 註解的類
     * 類似 component =所有controller、service、repository bean
     * @return
     */
    public static Set<Class<?>> getBeanClassSet(){
        Set<Class<?>> beanClassSet = new HashSet<>();
        beanClassSet.addAll(getControllerClassSet());
        beanClassSet.addAll(getServiceClassSet());
        return beanClassSet;
    }

元件三、bean容器

/**
 * 維護靜態map<class型別,Object物件> 通過類名獲取bean物件
 * bean初始化 即載入包下所有class,add到map中
 */
public class BeanContainer {
    private static final Map<Class<?>, Object> BEAN_MAP = new HashMap<>();

    static {
        Set<Class<?>> beanClassSet = AnnotationMapHandler.getClassSet();
        for (Class<?> c : beanClassSet) {
            BEAN_MAP.put(c, ReflectionHandler.newInstance(c));  //建立所有class的例項(自己定義的註解類)
        }
    }

    /**
     * bean 容器
     **/
    public static Map<Class<?>, Object> getBeanMap() {
        return BEAN_MAP;
    }

    /**
     * 獲取bean
     **/
    public static <T> T getBean(Class<T> cls) {
        if (!BEAN_MAP.containsKey(cls)) {
            throw new RuntimeException("can not get bean by class : " + cls);
        }
        return (T) BEAN_MAP.get(cls);
    }

    /**
     * 新增bean 例項
     **/
    public static void setBean(Class<?> cls, Object obj) {
        BEAN_MAP.put(cls, obj);
    }
}

IOC 反射將bean 通過setField 設定到使用bean的成員變數中

/**
 * @author zhongailing
 * @version V1.0
 * @Description: 通過遍歷BeanContainer裡的所有cls,初始化對應bean例項,然後通過反射setField把注入的bean set到類中成為其成員變數。
 * @date Created in 2018-11-5 16:37
 */
public class IocHandler {
    static {
        Map<Class<?>, Object> beanMap = BeanContainer.getBeanMap(); //框架需要管理的bean對映
        if (MapUtils.isNotEmpty(beanMap)) {
            for (Map.Entry<Class<?>, Object> ent : beanMap.entrySet()) {
                Class<?> beanCls = ent.getKey();
                Object beanInstance = ent.getValue();
                // 獲取該class所有的成員屬性,包括公共、保護、預設(包)訪問和私有欄位,但不包括繼承的欄位
                Field[] beanFields = beanCls.getDeclaredFields();
                for (Field beanField : beanFields) {
                    //判斷 該欄位是否 包含 inject註解
                    if (beanField.isAnnotationPresent(Inject.class)) {
                        Class<?> beanFieldType = beanField.getType(); //宣告型別 成員變數的Class
                        Object beanFieldInstance = beanMap.get(beanFieldType); // 獲取該型別的例項
                        if (beanFieldInstance != null) {
                            ReflectionHandler.setField(beanInstance, beanField, beanFieldInstance); // 把對應的成員變數屬性 賦值
                        }
                    }
                }
            }
        }
    }
}

反射處理類

/**
 * @author zhongailing
 * @version V1.0
 * @Description: 通過ClassLoaderHandler 載入的class類來初始化物件
 * java反射 clazz.newInstance
 * @date Created in 2018-11-5 16:23
 */
public class ReflectionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);

    /** 建立例項 **/
    public static Object newInstance(Class<?> cls){
        Object ins = null;
        try {
            ins = cls.newInstance();
        } catch (Exception e) {
            LOGGER.error("new instance failure");
            throw new RuntimeException(e);
        }
        return ins;
    }

    public static Object newInstance(String className){
        Object ins = null;
        try {
            Class<?> cls = Class.forName(className);
            ins = cls.newInstance();
        } catch (Exception e) {
            LOGGER.error("new instance failure");
            throw new RuntimeException(e);
        }
        return ins;
    }

    /**
     * 呼叫方法
     * @param obj 例項
     * @param method 方法
     * @param args 引數
     * @return
     */
    public static Object invokeMethod(Object obj, Method method, Object...args){
        method.setAccessible(true); //取消訪問許可權檢查
        Object result = null;
        try {
            result = method.invoke(obj, args);
        } catch (Exception e) {
            LOGGER.error("invoe method fail",e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 呼叫不帶引數的方法
     * @param obj
     * @param method
     * @return
     */
    public static Object invokeMethod(Object obj,Method method){
        method.setAccessible(true); //取消訪問許可權檢查
        Object result = null;
        try {
            result = method.invoke(obj);
        } catch (Exception e) {
            LOGGER.error("invoe method fail",e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 設定成員變數
     * @param obj
     * @param field
     * @param value
     */
    public static void setField(Object obj, Field field, Object value){
        try {
            field.setAccessible(true); //取消訪問許可權檢查
            field.set(obj,value);
        } catch (Exception e) {
            LOGGER.error("set Field fail",e);
            throw new RuntimeException(e);
        }
    }
}

最後流程總結,別人家的圖一個意思

這裡寫圖片描述

這裡寫圖片描述