手寫Spring框架之IOC

簡介

手寫Spring框架第一篇博客(必讀): 手寫Spring框架

本篇博客主要實現兩個功能: Bean容器 和 IOC.

Bean容器

Bean容器也就是Spring容器, 在學習完Spring之後, 如果要我們用一句話來形容Spring, 我們經常會說: Spring是一個容器, 管理着應用中所有bean的裝配和生命週期. 從這句話中就可以看出Spring容器的重要性, Spring容器其實是一個Map映射, 裏面存儲了應用中所有bean的實例, key爲該bean實例的Class對象. Spring有兩種容器, 分別是 BeanFactory 和 ApplicationContext, 二者的區別在於, BeanFactory採用延遲加載策略, 在第一次調用getBean()時才真正裝配該對象. 而 ApplicationContext會在應用啓動時就把所有對象一次性全部裝配好.

handwritten-mvc-framwork 框架的bean容器是一個 ApplicationContext 式的容器.

IOC

IOC的實現思路如下:

  • 首先有一個配置文件定義了應用的基礎包, 也就是Java源碼路徑.
  • 讀取基礎包名, 然後通過類加載器獲取到應用中所有的Class對象, 存儲到一個集合中.
  • 獲取應用中所有Bean (Controller和Service) 的Class對象, 通過反射創建實例, 然後存儲到 Bean容器中.
  • 遍歷Bean容器中的所有Bean, 爲所有帶 @Autowired 註解的屬性注入實例.
  • IOC操作要在應用啓動時就完成, 所以必須寫在靜態代碼塊中.

handwritten-mvc-framwork 實現

定義註解

(1) 處理器註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

(2) 處理器方法註解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

    /**
     * 請求路徑
     * @return
     */
    String value() default "";

    /**
     * 請求方法
     * @return
     */
    RequestMethod method() default RequestMethod.GET;
}

//請求方法枚舉類
public enum RequestMethod {
    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

(3) 依賴注入註解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

(4) 業務類註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

配置文件

handwritten-mvc-framwork 框架是不存在配置文件的, 配置文件是用戶需要自定義一些配置項, 所以創建一個配置文件, 而框架需要做的就是讀取用戶自定義的配置文件, 如果用戶沒有配置, 就使用默認的配置. handwritten-mvc-example 實例中的一個配置文件如下:

#數據源
handwritten.framework.jdbc.driver=com.mysql.jdbc.Driver
handwritten.framework.jdbc.url=jdbc:mysql://localhost:3306/tyshawn_test
handwritten.framework.jdbc.username=root
handwritten.framework.jdbc.password=123

#java源碼路徑
handwritten.framework.app.base_package=com.tyshawn
#jsp頁面路徑
handwritten.framework.app.jsp_path=/WEB-INF/view/
#靜態資源路徑
handwritten.framework.app.asset_path=/asset/

那 handwritten-mvc-framwork 框架如何來加載用戶自定義的配置文件呢?

(1) ConfigConstant 常量接口

首先我們要定義一個名爲 ConfigConstant 的常量接口, 讓它來維護配置文件中相關的配置項名稱, 代碼如下:

public interface ConfigConstant {
    //配置文件的名稱
    String CONFIG_FILE = "handwritten.properties";

    //數據源
    String JDBC_DRIVER = "handwritten.framework.jdbc.driver";
    String JDBC_URL = "handwritten.framework.jdbc.url";
    String JDBC_USERNAME = "handwritten.framework.jdbc.username";
    String JDBC_PASSWORD = "handwritten.framework.jdbc.password";

    //java源碼地址
    String APP_BASE_PACKAGE = "handwritten.framework.app.base_package";
    //jsp頁面路徑
    String APP_JSP_PATH = "handwritten.framework.app.jsp_path";
    //靜態資源路徑
    String APP_ASSET_PATH = "handwritten.framework.app.asset_path";
}

(2) PropsUtil 工具類

然後使用 PropsUtil 工具類來讀取屬性文件

public final class PropsUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);

    /**
     * 加載屬性文件
     */
    public static Properties loadProps(String fileName) {
        Properties props = null;
        InputStream is = null;
        try {
            is = ClassUtil.getClassLoader().getResourceAsStream(fileName);
            if (is == null) {
                throw new FileNotFoundException(fileName + " file is not found");
            }
            props = new Properties();
            props.load(is);
        } catch (IOException e) {
            LOGGER.error("load properties file failure", e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    LOGGER.error("close input stream failure", e);
                }
            }
        }
        return props;
    }

    /**
     * 獲取 String 類型的屬性值(默認值爲空字符串)
     */
    public static String getString(Properties props, String key) {
        return getString(props, key, "");
    }

    /**
     * 獲取 String 類型的屬性值(可指定默認值)
     */
    public static String getString(Properties props, String key, String defaultValue) {
        String value = defaultValue;
        if (props.containsKey(key)) {
            value = props.getProperty(key);
        }
        return value;
    }

    /**
     * 獲取 int 類型的屬性值(默認值爲 0)
     */
    public static int getInt(Properties props, String key) {
        return getInt(props, key, 0);
    }

    /**
     * 獲取 int 類型的屬性值(可指定默認值)
     */
    public static int getInt(Properties props, String key, int defaultValue) {
        int value = defaultValue;
        if (props.containsKey(key)) {
            value = Integer.parseInt(props.getProperty(key));
        }
        return value;
    }

    /**
     * 獲取 boolean 類型屬性(默認值爲 false)
     */
    public static boolean getBoolean(Properties props, String key) {
        return getBoolean(props, key, false);
    }

    /**
     * 獲取 boolean 類型屬性(可指定默認值)
     */
    public static boolean getBoolean(Properties props, String key, boolean defaultValue) {
        boolean value = defaultValue;
        if (props.containsKey(key)) {
            value = Boolean.parseBoolean(props.getProperty(key));
        }
        return value;
    }
}

(3) ConfigHelper 助手類

最後藉助 PropsUtil 工具類來實現 ConfigHelper 助手類, 框架通過 ConfigHelper 類就可以加載用戶自定義的配置文件了, 從代碼中可以看到, 部分配置項擁有默認值, 當用戶沒有自定義時將會使用默認配置.

public final class ConfigHelper {

    /**
     * 加載配置文件的屬性
     */
    private static final Properties CONFIG_PROPS = PropsUtil.loadProps(ConfigConstant.CONFIG_FILE);

    /**
     * 獲取 JDBC 驅動
     */
    public static String getJdbcDriver() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_DRIVER);
    }

    /**
     * 獲取 JDBC URL
     */
    public static String getJdbcUrl() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_URL);
    }

    /**
     * 獲取 JDBC 用戶名
     */
    public static String getJdbcUsername() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_USERNAME);
    }

    /**
     * 獲取 JDBC 密碼
     */
    public static String getJdbcPassword() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_PASSWORD);
    }

    /**
     * 獲取應用基礎包名
     */
    public static String getAppBasePackage() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.APP_BASE_PACKAGE);
    }

    /**
     * 獲取應用 JSP 路徑
     */
    public static String getAppJspPath() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.APP_JSP_PATH, "/WEB-INF/view/");
    }

    /**
     * 獲取應用靜態資源路徑
     */
    public static String getAppAssetPath() {
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.APP_ASSET_PATH, "/asset/");
    }

    /**
     * 根據屬性名獲取 String 類型的屬性值
     */
    public static String getString(String key) {
        return PropsUtil.getString(CONFIG_PROPS, key);
    }

    /**
     * 根據屬性名獲取 int 類型的屬性值
     */
    public static int getInt(String key) {
        return PropsUtil.getInt(CONFIG_PROPS, key);
    }

    /**
     * 根據屬性名獲取 boolean 類型的屬性值
     */
    public static boolean getBoolean(String key) {
        return PropsUtil.getBoolean(CONFIG_PROPS, key);
    }
}

Class對象集合

在完成了第一步加載配置文件之後, 我們接下來的第二步就是將應用中所有的Class對象都存儲到一個集合中.

(1) ClassUtil 工具類

ClassUtil 工具類可以通過加載全限定類名得到Class類, 以及獲取指定包名下的所有Class類.

public final class ClassUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);

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

    /**
     * 加載類
     * @param className 類名
     * @param isInitialized 是否初始化
     * @return
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            LOGGER.error("load class failure", e);
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * 加載類(默認將初始化類)
     */
    public static Class<?> loadClass(String className) {
        return loadClass(className, true);
    }

    /**
     * 獲取指定包名下的所有類
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath().replaceAll("%20", " ");
                        addClass(classSet, packagePath, packageName);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        if (jarURLConnection != null) {
                            JarFile jarFile = jarURLConnection.getJarFile();
                            if (jarFile != null) {
                                Enumeration<JarEntry> jarEntries = jarFile.entries();
                                while (jarEntries.hasMoreElements()) {
                                    JarEntry jarEntry = jarEntries.nextElement();
                                    String jarEntryName = jarEntry.getName();
                                    if (jarEntryName.endsWith(".class")) {
                                        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                        doAddClass(classSet, className);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("get class set failure", e);
            throw new RuntimeException(e);
        }
        return classSet;
    }

    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
        File[] files = new File(packagePath).listFiles(new FileFilter() {
            public boolean accept(File file) {
                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("."));
                if (StringUtils.isNotEmpty(packageName)) {
                    className = packageName + "." + className;
                }
                doAddClass(classSet, className);
            } else {
                String subPackagePath = fileName;
                if (StringUtils.isNotEmpty(packagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtils.isNotEmpty(packageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet, subPackagePath, subPackageName);
            }
        }
    }

    private static void doAddClass(Set<Class<?>> classSet, String className) {
        Class<?> cls = loadClass(className, false);
        classSet.add(cls);
    }
}

(2) ClassHelper 助手類

藉助 ClassUtil 來實現 ClassHelper 助手類, 這個類的功能很重要, 大家需要仔細看一下. ClassHelper 助手類在自身被加載的時候通過 ConfigHelper 助手類獲取應用的基礎包名, 然後通過 ClassUtil 工具類來獲取基礎包名下所有類, 存儲到 CLASS_SET 集合中. 除此之外, 其他的方法在後面的代碼中會經常被使用到.

public final class ClassHelper {

    /**
     * 定義類集合(存放基礎包名下的所有類)
     */
    private static final Set<Class<?>> CLASS_SET;

    static {
        //獲取基礎包名
        String basePackage = ConfigHelper.getAppBasePackage();
        //獲取基礎包名下所有類
        CLASS_SET = ClassUtil.getClassSet(basePackage);
    }

    /**
     * 獲取基礎包名下的所有類
     */
    public static Set<Class<?>> getClassSet() {
        return CLASS_SET;
    }

    /**
     * 獲取基礎包名下所有 Service 類
     */
    public static Set<Class<?>> getServiceClassSet() {
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
            if (cls.isAnnotationPresent(Service.class)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 獲取基礎包名下所有 Controller 類
     */
    public static Set<Class<?>> getControllerClassSet() {
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
            if (cls.isAnnotationPresent(Controller.class)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 獲取基礎包名下所有 Bean 類(包括:Controller、Service)
     */
    public static Set<Class<?>> getBeanClassSet() {
        Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
        beanClassSet.addAll(getServiceClassSet());
        beanClassSet.addAll(getControllerClassSet());
        return beanClassSet;
    }

    /**
     * 獲取基礎包名下某父類的所有子類 或某接口的所有實現類
     */
    public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
            //isAssignableFrom() 指 superClass 和 cls 是否相同或 superClass 是否是 cls 的父類/接口
            if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 獲取基礎包名下帶有某註解的所有類
     */
    public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
            if (cls.isAnnotationPresent(annotationClass)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }
}

Bean容器

在將應用中所有的Class對象都存儲到 CLASS_SET 集合中之後, 我們就可以來構建Bean容器了.

(1) ReflectionUtil 工具類

我們需要一個反射工具類, 進行各種反射操作.

public final class ReflectionUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);

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

    /**
     * 創建實例(根據類名)
     */
    public static Object newInstance(String className) {
        Class<?> cls = ClassUtil.loadClass(className);
        return newInstance(cls);
    }

    /**
     * 調用方法
     */
    public static Object invokeMethod(Object obj, Method method, Object... args) {
        Object result;
        try {
            method.setAccessible(true);
            result = method.invoke(obj, args);
        } catch (Exception e) {
            LOGGER.error("invoke method failure", e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 設置成員變量的值
     */
    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 failure", e);
            throw new RuntimeException(e);
        }
    }
}

(2) Bean容器助手類

BeanHelper 在類加載時就會創建一個Bean容器 BEAN_MAP, 然後獲取到應用中所有bean的Class對象, 再通過反射創建bean實例, 儲存到 BEAN_MAP 中.

public final class BeanHelper {

    /**
     * BEAN_MAP相當於一個Spring容器, 擁有應用所有bean的實例
     */
    private static final Map<Class<?>, Object> BEAN_MAP = new HashMap<Class<?>, Object>();

    static {
        //獲取應用中的所有bean
        Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
        //將bean實例化, 並放入bean容器中
        for (Class<?> beanClass : beanClassSet) {
            Object obj = ReflectionUtil.newInstance(beanClass);
            BEAN_MAP.put(beanClass, obj);
        }
    }

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

    /**
     * 獲取 Bean 實例
     */
    @SuppressWarnings("unchecked")
    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 功能

最後就是實現 IOC 了, 我們需要做的就是遍歷Bean容器中的所有bean, 爲所有帶 @Autowired 註解的屬性注入實例. 這個實例從Bean容器中獲取.

public final class IocHelper {

    /**
     * 遍歷bean容器所有bean的屬性, 爲所有帶@Autowired註解的屬性注入實例
     */
    static {
        //遍歷bean容器裏的所有bean
        Map<Class<?>, Object> beanMap = BeanHelper.getBeanMap();
        if (MapUtils.isNotEmpty(beanMap)) {
            for (Map.Entry<Class<?>, Object> beanEntry : beanMap.entrySet()) {
                //bean的class類
                Class<?> beanClass = beanEntry.getKey();
                //bean的實例
                Object beanInstance = beanEntry.getValue();
                //暴力反射獲取屬性
                Field[] beanFields = beanClass.getDeclaredFields();
                //遍歷bean的屬性
                if (ArrayUtils.isNotEmpty(beanFields)) {
                    for (Field beanField : beanFields) {
                        //判斷屬性是否帶Autowired註解
                        if (beanField.isAnnotationPresent(Autowired.class)) {
                            //屬性類型
                            Class<?> beanFieldClass = beanField.getType();
                            //如果beanFieldClass是接口, 就獲取接口對應的實現類
                            beanFieldClass = findImplementClass(beanFieldClass);
                            //獲取Class類對應的實例
                            Object beanFieldInstance = beanMap.get(beanFieldClass);
                            if (beanFieldInstance != null) {
                                ReflectionUtil.setField(beanInstance, beanField, beanFieldInstance);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 獲取接口對應的實現類
     */
    public static Class<?> findImplementClass(Class<?> interfaceClass) {
        Class<?> implementClass = interfaceClass;
        //接口對應的所有實現類
        Set<Class<?>> classSetBySuper = ClassHelper.getClassSetBySuper(interfaceClass);
        if (CollectionUtils.isNotEmpty(classSetBySuper)) {
            //獲取第一個實現類
            implementClass = classSetBySuper.iterator().next();
        }
        return implementClass;
    }
}

以上就是Bean容器和IOC功能的全部內容, 當應用啓動後, 就會生成Bean容器, 並實現IOC功能, 所有那些加了 @Autowired 註解的屬性, 別看他們在代碼中只是一個聲明, 其實在應用啓動後他們都有實例啦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章