Spring依赖注入原理

所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中。当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方法。

BeanDefinition.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package junit.test;

import java.util.ArrayList;
import java.util.List;

public class BeanDefinition {
    private String id;
    private String className;
   
    private List<PropertyDefinition> propertys = new ArrayList<PropertyDefinition>();
   
    public List<PropertyDefinition> getPropertys() {
        return propertys;
    }
    public void setPropertys(List<PropertyDefinition> propertys) {
        this.propertys = propertys;
    }
    public BeanDefinition(String id, String className) {
        super();
        this.id = id;
        this.className = className;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
}

PropertyDefinition.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package junit.test;

public class PropertyDefinition {
    private String name;
    private String ref;
    private String value;
   
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public PropertyDefinition(String name, String ref, String value) {
        this.name = name;
        this.ref = ref;
        this.value = value;
    }
   
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getRef() {
        return ref;
    }
    public void setRef(String ref) {
        this.ref = ref;
    }
}

readXML方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void readXML(String filename) {
        // TODO Auto-generated method stub
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            URL xmlpath = this.getClass().getClassLoader().getResource(filename);
            document = saxReader.read(xmlpath);
            Map<String,String> nsMap = new HashMap<String,String>();
            nsMap.put("ns","http://www.springframework.org/schema/beans");//加入命名空间
            XPath xsub = document.createXPath("//ns:beans/ns:bean");//传经beans/bean查询路径
            xsub.setNamespaceURIs(nsMap);//设置命名空间
            List<Element> beans = xsub.selectNodes(document);//获取文档下所有bean节点
            for(Element element : beans){
                String id = element.attributeValue("id");//获取id属性值
                String clazz = element.attributeValue("class");//获取class属性值
                BeanDefinition beanDefine = new BeanDefinition(id,clazz);
                XPath propertysub = element.createXPath("ns:property");
                propertysub.setNamespaceURIs(nsMap);
                List<Element> propertys = propertysub.selectNodes(element);
                for(Element Property : propertys){
                    String propertyName = Property.attributeValue("name");
                    String propertyRef = Property.attributeValue("ref");
                    String propertyValue = Property.attributeValue("value");
                    System.out.println(propertyName+" -- "+propertyRef);
                   
                    PropertyDefinition propertyDefinition = new PropertyDefinition(propertyName, propertyRef, propertyValue);
                    beanDefine.getPropertys().add(propertyDefinition);
                }
                beanDefines.add(beanDefine);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

实例化bean对象:

1
2
3
4
5
6
7
8
9
10
11
private void instanseBeans() {
        // TODO Auto-generated method stub
        for(BeanDefinition beanDefinition: beanDefines){
            try {
                if(beanDefinition.getClassName()!=null && !"".equals(beanDefinition.getClassName().trim()))
                sigletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

如上所示,通过一个for循环对beanDefines进行循环遍历,如果其中的某个BeanDefinition具有正确的类名,怎通过Class.forName创建该类的字节码对象,然后通过newInstance方法创建一个调用默认构造函数
创建出来的对象,然后放入名字为sigletons的Map对象中,它的key是bean定义的id属性的值。以上代码省略了try-catch块和其它相关的定义。从这里可以看出,spring初始化bean时,首先是从配置文件中获取bean的定义信息,然后在通过某种方式创建实例对象。
创建一个bean的实例出来还只是依赖注入的一小部分前提工作,而最重要的属性还没有被注入到相应的对象中。下面就是一为bean对象注入相应属性值的关键代码,其中分了两种情况注入,一种是通过ref属性注入的,还有一种是经过value属性注入的简单属性(为了注入简单属性,在读取xml配置的时候,也保存属性为value的值,因此propertyDefinition中增加了value属性,用来存储对应的值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void injectObject() {
        // TODO Auto-generated method stub
        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();//获取属性的setter
                                    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) {
                        // TODO: handle exception
                    }
            }
        }
    }

上面代码中最外层的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的实现是不可能这么简单那和粗糙的,但是至少说明了依赖注入的一种实现方式,让依赖注入不在那么抽象和遥不可及。这就是传智播客特有特点,对知识点的讲解非常深入,让学员知其然也知其所以然,这样在以后编写代码过程中很少有迷惑的地方,也能编写出更健壮和优雅的代码。

 

除非注明,Coder文章均为原创,转载请以链接形式标明本文地址

本文地址:http://www.blogfshare.com/spring-di.html

本文链接:http://www.blogfshare.com/spring-di.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>