天天看点

MVC框架IOC容器与DI的实现(《架构探险》的学习记录)

    mvc模式是现在流行的一种软件设计,将表现层,业务层和控制层划分开来,使得软件结构更加清晰,各层间的耦合度降低,便于各开发单元协同工作。通过ioc容器,进一步实现各层之间的依赖的解耦,可以大大提高程序可配置性,灵活性,便于扩展和维护。

    如何理解并创建一个ioc容器,实现其依赖注入的特性呢?一个ioc容器实际上是一个保存着所有依赖项的集合,也就是所谓的be an的集合,DI的特性其实是容器中依赖项间的关系,即各bean之间的持有关系。容器本身并不知道自己要管理哪些bean,也无法事先将bean准备好,这就需要规定规则,指定容器要管理的bean,以及这些bean存在的位置,通过配置文件来指定这些规则。容器需要有读取配置文件的能力,然后根据配置文件进行bean的管理操作。

public final class ConfigUtil {

    /**
     * @Description  获得基础包路径
     * @Param       []
     * @Return      java.lang.String
     * @Author      takou
     * @Date        2018/4/6
     * @Time        上午11:37
     */
    public static String getBasePackage() {
        return PropUtil.getString(ConfigConstant.BASE_PACKAGE,"org.smartweb");
    }

    /**
     * @Description 获得jsp文件路径
     * @Param       []
     * @Return      java.lang.String
     * @Author      takou
     * @Date        2018/4/6
     * @Time        上午11:37
     */
    public static String getJspPath() {
        return PropUtil.getString(ConfigConstant.JSP_PATH,"/WEB-INF/view/");
    }

    /**
     * @Description 获得资源文件路径
     * @Param       []
     * @Return      java.lang.String
     * @Author      takou
     * @Date        2018/4/6
     * @Time        上午11:38
     */
    public static String getAsset() {
        return PropUtil.getString(ConfigConstant.ASSET,"/asset");
    }

}
           

    框架启动后,容器通过配置文件指定的基础包名,运行时获得包名下的所有要管理的bean,加入到容器中。此时就需要java的运行时技术:反射了。反射的前提首先要获得类的class文件,通过类加载器加载class文件,然后通过反射创建类的实例bean。

/**
     * @Description  获得类加载器
     * @Param       []
     * @Return      java.lang.ClassLoader
     * @Author      takou
     * @Date        2018/4/4
     * @Time        下午8:19
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * @Description  加载指定类名的类
     * @Param       [className, isInitialized]
     * @Return      java.lang.Class
     * @Author      takou
     * @Date        2018/4/4
     * @Time        下午8:19
     */
    public static Class loadClass(String className, boolean isInitialized) {

        Class<?> cls = null;
        try {
             cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            LOGGER.error(className + " load failure",e);
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * @Description 获得所有的class文件放入集合
     * @Param       [packageName]
     * @Return      java.util.Set<java.lang.Class<?>>
     * @Author      takou
     * @Date        2018/4/6
     * @Time        上午11:47
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        //根据包名获得类文件存放的位置
        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
            if (urls != null) {
                for (;urls.hasMoreElements();) {
                    URL url = urls.nextElement();
                    String protocol = url.getProtocol();
                    if ("file".equalsIgnoreCase(protocol)) {
                        String filePath = url.getPath().replaceAll("%20"," ");
                        //根据类文件的路径加载其下的所有类文件
                        addClass(filePath,packageName,classSet);
                    } else if ("jar".equalsIgnoreCase(protocol)) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        if (jarURLConnection != null) {
                            JarFile jarFile = jarURLConnection.getJarFile();
                            Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
                            if (jarEntryEnumeration != null) {
                                for (;jarEntryEnumeration.hasMoreElements();) {
                                    JarEntry jarEntry = jarEntryEnumeration.nextElement();
                                    String jarEntryName =  jarEntry.getName();
                                    if (jarEntryName.endsWith(".class")) {
                                        String className = jarEntryName.substring(0,jarEntryName.lastIndexOf("."))
                                                .replaceAll("/",".");
                                        doAddClass(className,classSet);
                                    }
                                }
                            }
                        }

                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error("get resources failure by " + packageName,e);
            throw new RuntimeException(e);
        }
        return classSet;
    }

    /**
     * @Description 根据类文件获得类全限定名
     * @Param       [packagePath, packageName, classSet]
     * @Return      void
     * @Author      takou
     * @Date        2018/4/6
     * @Time        上午11:48
     */
    public static void addClass(String packagePath, String packageName, Set<Class<?>> classSet) {
        File[] files = new File(packagePath).listFiles(new FileFilter() {
            public boolean accept(File pathname) {
                return (pathname.isFile() && pathname.getName().endsWith(".class")) || pathname.isDirectory();
            }
        });
        for (File f : files) {
            String fileName = f.getName();
            if (f.isFile()) {
                String className = fileName.substring(0,fileName.lastIndexOf("."));
                className = packageName + "." + className;
                doAddClass(className,classSet);
            } else {
                String subPackagePath = fileName;
                if (StringUtils.isNotEmpty(subPackagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtils.isNotEmpty(subPackageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(subPackagePath,subPackageName,classSet);
            }

        }

    }

    /**
     * @Description 实例bean,放入容器
     * @Param       [className, set]
     * @Return      void
     * @Author      takou
     * @Date        2018/4/6
     * @Time        上午11:49
     */
    public static void doAddClass(String className, Set<Class<?>> set) {
        set.add(loadClass(className,false));
    }
           

    类文件的获取,既包括了单个的.class文件,同时也会有打包后的jar文件,由基础包名替换分割符后获得相对文件路径后,递归的检查该目录下的所有目录,找到.class文件或者jar文件,实例化并加入容器。但是在通过扫描基础包路径的方式会加载一些无关的class文件进入内存,会有一些不准确,在这种方式下,所有的bean实际上都是单例的,因为只有一次,且只实例化了一个。

    依赖注入(采用注解方式)的实现,就是将容器中的bean建立起本来的依赖关系,即为bean的依赖属性设值。可以在第一次或取bean时进行注入,也可在容器创建时就注入。在使用getbean()获得bean时,若容器还没有该bean实例则进行实例化,并对其依赖项进行注入,检查bean的所有属性,有依赖注入注解标示的,获得依赖项实例,为属性赋值,其过程是一个递归的过程。

/**
     * @Description 初始化bean容器
     * @Param       []
     * @Return      void
     * @Author      takou
     * @Date        2018/4/6
     * @Time        下午12:11
     */
    private static void initBeanSet() {
        Set<Class<?>> set = ClassUtil.getClassSet(ConfigUtil.getBasePackage());
        for (Class cls : set) {
            if (cls.isAnnotationPresent(Controller.class)) {
                controllerSet.add(cls);
            } else if (cls.isAnnotationPresent(Service.class)) {
                serviceSet.add(cls);
            }
        }
        beanSet.addAll(controllerSet);
        beanSet.addAll(serviceSet);
    }

    /**
     * @Description 从容器中获得bean
     * @Param       [cls]
     * @Return      T
     * @Author      takou
     * @Date        2018/4/6
     * @Time        下午12:11
     */
    public static <T> T getBean(Class<?> cls) {
        T bean = null;
        if (beanSet.contains(cls) && beanMap.containsKey(cls)) {
            bean = (T) beanMap.get(cls);
        } else if (beanSet.contains(cls) && !beanMap.containsKey(cls)) {
            bean = initBean(cls);
        }
        return bean;
    }

    /**
     * @Description 实例化bean
     * @Param       [cls]
     * @Return      T
     * @Author      takou
     * @Date        2018/4/6
     * @Time        下午12:12
     */
    private static <T> T initBean(Class<?> cls) {
        T obj = (T) ClassUtil.newInstance(cls);
        beanMap.put(cls,obj);
        initDependency(cls,obj);
        return obj;
    }

    /**
     * @Description bean的依赖注入
     * @Param       [cls, obj]
     * @Return      void
     * @Author      takou
     * @Date        2018/4/6
     * @Time        下午12:12
     */
    private static <T> void initDependency(Class<?> cls, T obj) {
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(AutoInject.class)) {
                Object bean = getBean(field.getType());
                if (obj == null) {
                    bean = initBean(field.getType());
                }
                field.setAccessible(true);
                try {
                    field.set(obj,bean);
                } catch (IllegalAccessException e) {
                    LOGGER.error("illegal access",e);
                    throw new RuntimeException(e);
                }
            }
        }
    }
           

所有加载过程基于静态块完成,因此在容器启动时,只需加载容器的类,在执行容器类的静态块时,完成所有操作。

/**
 * @PROJECT_NAME smartweb
 * @PACKAGE_NAME org.smartweb.container
 * @USER takou
 * @CREATE 2018/4/5
 **/
public final class BeanContainerBuilder {

    private static Class<?>[] containers = new Class<?>[]{BeanContainer.class,WebController.class};

    /**
     * @Description 构建bean容器
     * @Param       []
     * @Return      void
     * @Author      takou
     * @Date        2018/4/6
     * @Time        下午12:17
     */
    public static void buildWebContainer() {
        for (Class<?> cls : containers) {
            ClassUtil.loadClass(cls.getName(),true);
        }
    }
}
           

spring采用的xml配置方式,对依赖进行配置,能更灵活的进行依赖项的变更和注入,只不过进行框架配置的工作量就更大了,同时spring也提供了纯注解的开发方式,开发简便,但失去了一些灵活性。

    去掉程序中的new关键字,通过配置实现功能的插拔式装配,让功能扩展和变更不再困难,整个就像拼接过程,更加简洁轻快。

继续阅读