所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中。当spring容器启动后,spring容器初始化,创建并管理bean对象,以及销毁它。所以我们只需从容器直接获取Bean对象就行,而不用编写一句代码来创建bean对象。这种现象就称作控制反转,即应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转。虽然平时只需要按要求将bean配置到配置文件中,但是了解其实现过程对理解spring的实现原理是有好处的,下面就是模拟spring实现依赖注入的过程。
    首先最简单的就是模拟创建bean实例,实现这个过程需要几个辅助类和辅助方法。有一个最重要的辅助方法就是读取XML的配置,可以利用DOM4J来实现对配置文件的读取,这里假设已经读取到一个List对象beanDefines中去了。读取配置后就需要将读取到的代表一个bean的信息放到一个对象中。这个对象的类就是BeanDefinition(包括id、className和一个装PropertyDefinition对象的List),还一个辅助类就是属性值对象的类PropertyDefinition(包括name属性和ref属性,都是字符串,属性名字和配置文件中的属性名字一致)。这两个辅助类就是普通JavaBean对象,都有getter和setter方法。
      for(BeanDefinition beanDefinition : beanDefines){
          if(beanDefinition.getClassName()!=null && !"".equals(beanDefinition.getClassName().trim()))
          sigletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
      }
    如上所示,通过一个for循环对beanDefines进行循环遍历,如果其中的某个BeanDefinition具有正确的类名,怎通过Class.forName创建该类的字节码对象,然后通过newInstance方法创建一个调用默认构造函数
创建出来的对象,然后放入名字为sigletons的Map对象中,它的key是bean定义的id属性的值。以上代码省略了try-catch块和其它相关的定义。从这里可以看出,spring初始化bean时,首先是从配置文件中获取bean的定义信息,然后在通过某种方式创建实例对象。
    创建一个bean的实例出来还只是依赖注入的一小部分前提工作,而最重要的属性还没有被注入到相应的对象中。下面就是一为bean对象注入相应属性值的关键代码,其中分了两种情况注入,一种是通过ref属性注入的,还有一种是经过value属性注入的简单属性(为了注入简单属性,在读取xml配置的时候,也保存属性为value的值,因此propertyDefinition中增加了value属性,用来存储对应的值):
      for(BeanDefinition beanDefinition : beanDefines){
        Object bean = sigletons.get(beanDefinition.getId());
          if(bean!=null){
            try {
              PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
              for(PropertyDefinition propertyDefinition : beanDefinition.getPropertys()){
                for(PropertyDescriptor properdesc : ps){
                  if(propertyDefinition.getName().equals(properdesc.getName())){
                    Method setter = properdesc.getWriteMethod();
                    if(setter!=null){
                      Object value = null;
                      if(propertyDefinition.getRef()!=null && !"".equals(propertyDefinition.getRef().trim())){
                        value = sigletons.get(propertyDefinition.getRef());
                      }else{
                        value = ConvertUtils.convert(propertyDefinition.getValue(), properdesc.getPropertyType());
                      }
                      setter.setAccessible(true);
                      setter.invoke(bean, value);
                    }
                   break;
                 }
               }
             }
           } catch (Exception e) {
           }
         }
       }
    上面代码中最外层的for循环是依次将属性的值注入进从xml中读取并实例化的bean对象中。下面来分析循环中的一个:首先从集合beanDefines拿出一个BeanDefinition对象,接着从存储实例化的bean对象的Map对象sigletons中取得bean的引用。因为存储bean的key是相应beanDefinition中的id属性,所以可以很简单的通过这个id的值找到对应的实例化的bean对象(此时还为注入任何属性)。程序严谨一点就先判断一下bean是否为空,因为师为bean注入属性,当然先保证它自身的存在。有了bean的实例对象后,就可以跟据bean的字节码码对象生成该bean的属性描述数组ps,这是利用工具类Introspector办到的。然后开始遍历从配置文件读取的bean的定义对象beanDefinition,从中获取某一个属性的描述对象。接下来的关键就是再次通过一个for循环遍历ps的属性描述数组,找到与前面从beanDefinition中获取的属性描述对象名字相同的属性的名字,找到后,通过该ps对象中的属性描述对象利用反射获取setter方法(Method setter = properdesc.getWriteMethod())。简单点说,就是以properdesc的name为依据,去beanDefinition里的属性描述对象里找着相同名字的属性定义对象。如果找到就可以开始执行注入功能了,在这里就可以判断该属性定义对象的ref属性有值还是value对象的值存在,如果是ref,就可以从spring中bean的Map容器找ref属性值的bean对象,如果是value属性的定义,就调用ConvertUtils工具类中的convert方法,将value值转换成bean的属性的类型。最后就是设置setter方法的可访问性(准确的说就是为了能够访问私有的方法),然后利用反射在相应的bean上调用该方法。
    通过上述分析,发现依赖注入的实现并不是很困难。当然spring的实现是不可能这么简单那和粗糙的,但是至少说明了依赖注入的一种实现方式,让依赖注入不在那么抽象和遥不可及。这就是传智播客特有特点,对知识点的讲解非常深入,让学员知其然也知其所以然,这样在以后编写代码过程中很少有迷惑的地方,也能编写出更健壮和优雅的代码。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐