第一章:前言

       现在用Java做Web开发已经无法避免使用Spring框架了,Spring框架给开发者提供了很多功能,其中就包含了IOC。今天就学习学习IOC的思想,然后做一个简单的Demo。

       首先由两个概念:IOC和DI。IOC(Inversion of Control ,控制反转)和DI(Dependency Inject,依赖注入)。

       假设现在有两个类A和B,A需要引用B。在传统的设计模式中,我们在编写A的时候会new一个B的实例出来供A调用,是程序主动去创建对象。IOC的设计思想是,单独做一个容器,在程序运行时创建一个B的实例对象,A的程序里面在容器里面去请求B的实例而不是去创建实例,将引用对象交与容器控制。这样相对于传统设计,可以有效减少对象的创建。这个是我理解的IOC思想。

       还是上面提到的A和B,B是A的一个属性,这是添加一个类C,C的程序里面需要引用A,按照上面的思想,在程序启动时,IOC容器要创建A的实例,因为A需要引用B的实例所以在实例化A时容器需要将B的实例注入到A的实例里面。其中A,B,C之间的关系是体现在IOC容器的配置中,当程序启动时,容器根据配置文件去实例每个实例,并为每个实例注入需要的资源。这是对DI的理解

第二章:制作简单的IOC Demo

       第一步:计划

使用HashMap作为IOC容器,用注解作为配置文件,在程序启动时根据配置创建实例对象;创建具有引用关系的两个类。

       第二步:创建注解

/**
 *
需要IOC容器实例化的注解
 
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
    String name(); //对象实例之后的名称
}

/**
 *
请求IOC容器中对象的注解
 
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Require {
    String name(); //需要注入的实例的注解
}

      上面的注解,Bean表示需要IOC托管的类,name表示这个类在容器中的名称;Require表示需要从IOC容器获取的属性,name对应属性对应实例在IOC中的名词;所以在IOC容器创建实例时,名称不能重复,类之间不能循环引用,找不到属性对应的实例会报错。

       第三步:创建IOC容器

/**
 *
使用一个HashMap作为一个IOC容器
 
*/
public class IOCMap {
    private HashMap<String, Object> map;
    public IOCMap() {
        this(new String[0]);
    }

    public IOCMap(String[] packages) {
        map = new HashMap<>();
        //开始扫描包
       
if (packages.length == 0) {
            String path = this.getClass().getPackage().getName();
            packages = new String[]{path};
        }
        //获取到包下面的类
       
Set<Class> set = new HashSet<>();
        for (String path : packages) {
            set.addAll(Utils.getClasses(path));
        }
        setBeans(map, set, true);
    }

    private void setBeans(HashMap<String, Object> map, Set<Class> clazzs, boolean first) {
        if (clazzs.isEmpty()) return;
        Set<Class> nextSet = new HashSet<>();
        for (Class clazz : clazzs) {
            Bean beanAnno = (Bean) clazz.getDeclaredAnnotation(Bean.class);
            if (beanAnno != null) {
                String name = beanAnno.name();
                if (map.containsKey(name) && map.get(name) != null) {
                    throw new RuntimeException("实例名称配置重复:" + name);
                }
                Field[] fields = clazz.getDeclaredFields();
                boolean enough = true;
                //判断当前类的属性依赖
               
for (Field field : fields) {
                    Require declaredAnno = field.getDeclaredAnnotation(Require.class);
                    if (declaredAnno != null) {
                        String name1 = declaredAnno.name();
                        if (!first && !map.containsKey(name1)) {
                            throw new RuntimeException("没有这个实例:" + name1);
                        }
                        if (map.get(name1) == null) {
                            nextSet.add(clazz);
                            enough = false;
                            break;
                        }
                    }
                }
                if (enough) {
                    Object object;
                    try {
                        Constructor constructor = clazz.getDeclaredConstructor();
                        object = constructor.newInstance();
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException(clazz.getName() + ": 无参构造函数出错");
                    }
                    for (Field field : fields) {
                        Require declaredAnno = field.getDeclaredAnnotation(Require.class);
                        String name1 = declaredAnno.name();
                        field.setAccessible(true);
                        try {
                            field.set(object, map.get(name1));
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                    map.put(name, object);
                } else if (!map.containsKey(name)) {
                    map.put(name, null);
                }
            }
        }
        //当一次处理没有减少Class数,则判断引用重复
       
if (nextSet.size() == clazzs.size()) {
            throw new RuntimeException("引用关系出现循环");
        }
        setBeans(map, nextSet, false);
    }

    public Object getBean(String name) {
        return map.get(name);
    }
}
/**
 *
工具类
 
*/
public class Utils {
    //扫描包路径下的类
   
public static Set<Class> getClasses(String packagePath) {
        Set<Class> res = new HashSet<>();
        String path = packagePath.replace(".", "/");
        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
        if (url == null) {
            System.out.println(packagePath + " is not exit");
            return res;
        }
        String protocol = url.getProtocol();
        if ("jar".equalsIgnoreCase(protocol)) {
            try {
                res.addAll(getJarClasses(url, packagePath));
            } catch (IOException e) {
                e.printStackTrace();
                return res;
            }
        } else if ("file".equalsIgnoreCase(protocol)) {
            res.addAll(getFileClasses(url, packagePath));
        }
        return res;
    }

    //获取file路径下的class文件
   
private static Set<Class> getFileClasses(URL url, String packagePath) {
        Set<Class> res = new HashSet<>();
        String filePath = url.getFile();
        File dir = new File(filePath);
        String[] list = dir.list();
        if (list == null) return res;
        for (String classPath : list) {
            if (classPath.endsWith(".class")) {
                classPath = classPath.replace(".class", "");
                try {
                    Class<?> aClass = Class.forName(packagePath + "." + classPath);
                    res.add(aClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                res.addAll(getClasses(packagePath + "." + classPath));
            }
        }
        return res;
    }

    //使用JarURLConnection类获取路径下的所有类
   
private static Set<Class> getJarClasses(URL url, String packagePath) throws IOException {
        Set<Class> res = new HashSet<>();
        JarURLConnection conn = (JarURLConnection) url.openConnection();
        if (conn != null) {
            JarFile jarFile = conn.getJarFile();
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                if (name.contains(".class") && name.replaceAll("/", ".").startsWith(packagePath)) {
                    String className = name.substring(0, name.lastIndexOf(".")).replace("/", ".");
                    try {
                        Class clazz = Class.forName(className);
                        res.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return res;
    }
    public static void main(String[] args) {
        Set<Class> classes = Utils.getClasses("com.ioc");
        classes.addAll(Utils.getClasses("net.sf"));
        for (Class clazz : classes) {
            System.out.println(clazz.getName());
        }
    }
}

       在IOCMap中,使用一个HashMap作为对象存储空间,在IOCMap构造函数里面去进行对象创建:先找到需要扫描包路径下的所有类,遍历所有的类查询注解是否需要实例化,对于要实例化的类查询是否有需要注入的属性,如果有需要注入的属性,查询注入名称是否有实例在Map中,如果没有则将类放在下一次遍历Set中。如果类需要注入的对象全部存在,则创建。如果这一次需要创建的类数目和下一次需要创建的类数目一样,判断为循环引用。

       第三步:创建对象

/**
 * ClassA
需要被IOC容器实例化 名称为 class_a
 */
@Bean(name = "class_a")
public class ClassA {
    public String call() {
        return " I am A";
    }
}
/**
 * ClassB
需要被IOC创建 名称为class_b
 *
具有一个属性 ClassA 需要注入名称为class_a的实例
 
*/
@Bean(name = "class_b")
public class ClassB {
    @Require(name = "class_a")
    private ClassA classA;
    public String call() {
        return classA.call();
    }
}

       在上面的ClassA和ClassB都需要被容器实例化,其中ClassB 具有一个ClassA的属性。

第四步:测试

/**
 *
测试,从IOCMap中获取叫 classb_的实例
 
*/
public class App {
    public static void main(String[] args) {
        IOCMap iocMap = new IOCMap();
        ClassB classB = (ClassB) iocMap.getBean("class_b");
        System.out.println(classB.call());
    }
}

 

       从容器中获取名叫 class_b 的实例,调用call方法

执行结果:IOCMap实例化之后的数据

 

输出:I am A

     扩展考虑:如果需要想实现单例,多例的区别,增加注解属性,实现工厂接口。HashMap中就存放 name 和factory,获取的时候就调用factory的create方法就行。如果想要根据Class进行注入,那map的key使用Class就行了,获取的时候判断子父类就OK

posted on 2018-01-10 22:54  Stark_Tan  阅读(143)  评论(0编辑  收藏  举报