# 设计一个IOC 我们要自己设计一个IOC,那么目标是什么呢? 我们的IOC容器要可以存储对象,还要有注解注入的功能即可。 Java语言允许通过程序化的方式间接对Class进行操作,Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数、属性和方法等。Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这就为使用程序化方式操作Class对象开辟了途径。 我们将从一个简单例子开始探访Java反射机制的征程,下面的Hero类拥有一个构造函数、五个方法以及两个属性,如代码清单所示: ```java /** * LOL英雄 */ public class Hero { // 英雄名称 private String name; // 装备名称 private String outfit; public Hero() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getOutfit() { return outfit; } public void setOutfit(String outfit) { this.outfit = outfit; } public void say(){ System.out.println(name + "购买了" + outfit); } } ``` 测试代码: ```java public class Test { public static void main(String[] args) throws Exception { //1. 通过类装载器获取Hero类对象 ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class clazz = loader.loadClass("com.biezhi.ioc.Hero"); //2. 获取类的默认构造器对象并通过它实例化Hero Constructor cons = clazz.getDeclaredConstructor((Class[])null); Hero hero = (Hero)cons.newInstance(); //3. 通过反射方法设置属性 Method setBrand = clazz.getMethod("setName", String.class); setBrand.invoke(hero, "小鱼人"); Method setColor = clazz.getMethod("setOutfit", String.class); setColor.invoke(hero, "爆裂魔杖"); // 4. 运行方法 hero.say(); } } ``` 输出了: `小鱼人购买了爆裂魔杖` 这说明我们完全可以通过编程方式调用Class的各项功能,这和直接通过构造函数和方法调用类功能的效果是一致的,只不过前者是间接调用,后者是直接调用罢了。 在Test中,使用了几个重要的反射类,分别是ClassLoader、Class、Constructor和Method,通过这些反射类就可以间接调用目标Class的各项功能了。在①处,我们获取当前线程的ClassLoader,然后通过指定的全限定类`"com.biezhi.ioc.Hero"`装载Hero类对应的反射实例。在②处,我们通过Hero的反射类对象获取Hero的构造函数对象cons,通过构造函数对象的newInstrance()方法实例化Hero对象,其效果等同于new Hero()。在③处,我们又通过Hero的反射类对象的getMethod(String methodName,Class paramClass)获取属性的Setter方法对象,第一个参数是目标Class的方法名;第二个参数是方法入参的对象类型。获取方法反射对象后,即可通过invoke(Object obj,Object param)方法调用目标类的方法,该方法的第一个参数是操作的目标类对象实例;第二个参数是目标方法的入参。 第三步是通过反射方法操控目标类的元信息,如果我们将这些信息以一个配置文件的方式提供,就可以使用Java语言的反射功能编写一段通用的代码对类似于Hero的类进行实例化及功能调用操作了。 简单的例子说完了,我们开始设计一个自己的IOC容器,做出这个东东后再来看那些复杂的原理。 首先设计接口,一个IOC容器中最核心的当属容器接口,来一个Container。 那么容器里应该有什么呢,我想它至少要有存储和移除一个对象的能力,其次可以含括更多的获取和注册对象的方法。 ```java /** * IOC容器 * @author biezhi * */ public interface Container { /** * 根据Class获取Bean * @param clazz * @return */ public T getBean(Class clazz); /** * 根据名称获取Bean * @param name * @return */ public T getBeanByName(String name); /** * 注册一个Bean到容器中 * @param object */ public Object registerBean(Object bean); /** * 注册一个Class到容器中 * @param clazz */ public Object registerBean(Class clazz); /** * 注册一个带名称的Bean到容器中 * @param name * @param bean */ public Object registerBean(String name, Object bean); /** * 删除一个bean * @param clazz */ public void remove(Class clazz); /** * 根据名称删除一个bean * @param name */ public void removeByName(String name); /** * @return 返回所有bean对象名称 */ public Set getBeanNames(); /** * 初始化装配 */ public void initWired(); } ``` 那么我写一个简单的实现代码: ```java /** * 容器简单实现 * @author biezhi */ @SuppressWarnings("unchecked") public class SampleContainer implements Container { /** * 保存所有bean对象,格式为 com.xxx.Person : @52x2xa */ private Map beans; /** * 存储bean和name的关系 */ private Map beanKeys; public SampleContainer() { this.beans = new ConcurrentHashMap(); this.beanKeys = new ConcurrentHashMap(); } @Override public T getBean(Class clazz) { String name = clazz.getName(); Object object = beans.get(name); if(null != object){ return (T) object; } return null; } @Override public T getBeanByName(String name) { String className = beankeys.get(name); Object obj = beans.get(className); if(null != object){ return (T) object; } return null; } @Override public Object registerBean(Object bean) { String name = bean.getClass().getName(); beanKeys.put(name, name); beans.put(name, bean); return bean; } @Override public Object registerBean(Class clazz) { String name = clazz.getName(); beanKeys.put(name, name); Object bean = ReflectUtil.newInstance(clazz); beans.put(name, bean); return bean; } @Override public Object registerBean(String name, Object bean) { String className = bean.getClass().getName(); beanKeys.put(name, className); beans.put(className, bean); return bean; } @Override public Set getBeanNames() { return beanKeys.keySet(); } @Override public void remove(Class clazz) { String className = clazz.getName(); if(null != className && !className.equals("")){ beanKeys.remove(className); beans.remove(className); } } @Override public void removeByName(String name) { String className = beanKeys.get(name); if(null != className && !className.equals("")){ beanKeys.remove(name); beans.remove(className); } } @Override public void initWired() { Iterator> it = beans.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); Object object = entry.getValue(); injection(object); } } /** * 注入对象 * @param object */ public void injection(Object object) { // 所有字段 try { Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { // 需要注入的字段 AutoWired autoWired = field.getAnnotation(autoWired.class); if (null != autoWired) { // 要注入的字段 Object autoWiredField = null; String name = autoWired.name(); if(!name.equals("")){ String className = beanKeys.get(name); if(null != className && !className.equals("")){ autoWiredField = beans.get(className); } if (null == autoWiredField) { throw new RuntimeException("Unable to load " + name); } } else { if(autoWired.value() == Class.class){ autoWiredField = recursiveAssembly(field.getType()); } else { // 指定装配的类 autoWiredField = this.getBean(autoWired.value()); if (null == autoWiredField) { autoWiredField = recursiveAssembly(autoWired.value()); } } } if (null == autoWiredField) { throw new RuntimeException("Unable to load " + field.getType().getCanonicalName()); } boolean accessible = field.isAccessible(); field.setAccessible(true); field.set(object, autoWiredField); field.setAccessible(accessible); } } } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } private Object recursiveAssembly(Class clazz){ if(null != clazz){ return this.registerBean(clazz); } return null; } } ``` 这里将所有Bean的名称存储在 `beanKeys` 这个map中,将所有的对象存储在 `beans` 中,用 `beanKeys` 维护名称和对象的关系。 在装配的时候步骤如下: 1. 判断是否使用了自定义命名的对象(是:根据name查找bean) 2. 判断是否使用了Class类型Bean(是:根据Class查找Bean,如果查找不到则创建一个无参构造函数的Bean) 下面是一个测试: ```java public class IocTest { private static Container container = new SampleContainer(); public static void baseTest(){ container.registerBean(Lol.class); // 初始化注入 container.initWired(); Lol lol = container.getBean(Lol.class); lol.work(); } public static void iocClassTest(){ container.registerBean(Lol2.class); // 初始化注入 container.initWired(); Lol2 lol = container.getBean(Lol2.class); lol.work(); } public static void iocNameTest(){ container.registerBean("face", new FaceService2()); container.registerBean(Lol3.class); // 初始化注入 container.initWired(); Lol3 lol = container.getBean(Lol3.class); lol.work(); } public static void main(String[] args) { baseTest(); //iocClassTest(); //iocNameTest(); } } ``` 输出结果: ```sh 剑圣买了5毛钱特效,装逼成功! ``` [代码出处](https://github.com/junicorn/easy-ioc/tree/master/src/test/java/com/junicorn/ioc/test) ## links * [目录]() * 上一节: [Spring中怎么用](<2.spring.md>) * 下一节:[原理分析](<4.principle.md>)