首页 > 代码库 > 编码剖析Spring依赖注入的原理

编码剖析Spring依赖注入的原理

Spring的依赖注入

前面我们就已经讲过所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中。 
Spring的依赖注入有两种方式:

  • 通过构造器参数,让容器把创建好的依赖对象注入。
  • 使用setter方法进行注入。

现在我们使用第二种方式进行依赖注入。以Spring管理的Bean的生命周期的案例为基础展开本文的说明。 
首先在src目录下新建一个cn.itcast.dao包,并在该包下新建一个接口——PersonDao.java,其代码为:

public interface PersonDao {    void add();}
  • 1

提示:在Spring开发中建议大家使用面向接口编程,若我们要实现软件各层之间的解耦,须通过接口。 
紧接着在src目录下新建一个cn.itcast.dao.impl包,并在该包下新建一个PersonDao接口的实现类——PersonDaoBean.java,其代码为:

public class PersonDaoBean implements PersonDao {    @Override    public void add() {        System.out.println("执行PersonDaoBean中的add()方法");    }}
  • 1

然后为了让PersonDaoBean依赖对象注入到PersonServiceBean对象中,我们使用setter方法进行注入,所以要将PersonServiceBean类的代码修改为:

public class PersonServiceBean implements PersonService {    private PersonDao personDao;    public PersonDao getPersonDao() {        return personDao;    }    public void setPersonDao(PersonDao personDao) {        this.personDao = personDao;    }    @Override    public void save() {        personDao.add();    }}
  • 1

注意:千万不能忘却setter方法。从上可看出业务逻辑层与数据访问层之间彻底解耦了,这正是我们所想要的。 
再接下来就要修改Spring的配置文件——beans.xml的内容为:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd">    <!-- 依赖注入的第一种方式 -->    <bean id="personDao" class="cn.itcast.dao.impl.PersonDaoBean"></bean>    <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean">        <property name="personDao" ref="personDao"></property>    </bean></beans>
  • 1

最后,我们要修改测试类——SpringTest.java的代码为:

public class SpringTest {    @Test    public void test() {        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");         PersonService personService = (PersonService) ctx.getBean("personService");        personService.save();        ctx.close(); // 正常关闭Spring容器    }}
  • 1

测试test()方法,Eclipse控制台会打印: 
技术分享

编码剖析Spring依赖注入的原理

在上面我们使用Spring的依赖注入将PersonDaoBean依赖对象注入到了PersonServiceBean对象中,我们不仅要问Spring依赖注入的内部原理是什么。现在我们就来编码剖析Spring依赖注入的原理。 
首先在junit.test包下新建一个JavaBean——PropertyDefinition.java,该JavaBean专门用于存放<property ...>的信息,其代码如下:

/** * 该JavaBean专门用户存放<property ...>的信息 * @author li ayun */public class PropertyDefinition {    private String name;    private String ref;    public PropertyDefinition(String name, String ref) {        this.name = name;        this.ref = ref;    }    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;    }}
  • 1

然后我们就要修改BeanDefinition类的代码为:

/** * 将读取到的bean的信息存到一个JavaBean对象中 * @author li ayun * */public class BeanDefinition {    private String id;    private String className;    private List<PropertyDefinition> propertys = new ArrayList<PropertyDefinition>();    public BeanDefinition(String id, String className) {        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;    }    public List<PropertyDefinition> getPropertys() {        return propertys;    }    public void setPropertys(List<PropertyDefinition> propertys) {        this.propertys = propertys;    }}
  • 1

紧接着我们就要对传智播客版的Spring容器进行修改了,我们先修改ItcastClassPathXMLApplicationContext类的代码为:

/** * 传智播客版Spring容器 * @author li ayun * */public class ItcastClassPathXMLApplicationContext {    private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();    private Map<String, Object> sigletons = new HashMap<String, Object>();    public ItcastClassPathXMLApplicationContext(String filename) {        this.readXML(filename);        this.instanceBeans();    }    /**     * 完成bean的实例化     */    private void instanceBeans() {        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();            }        }    }    /**     * 读取XML配置文件     * @param filename     */    private void readXML(String filename) {        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");                    System.out.println(propertyName + "=" + propertyRef); // 测试用                    PropertyDefinition propertyDefinition = new PropertyDefinition(propertyName, propertyRef);                    beanDefine.getPropertys().add(propertyDefinition);                }                beanDefines.add(beanDefine);            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 获取bean实例     * @param beanName     * @return     */    public Object getBean(String beanName) {        return this.sigletons.get(beanName);    }}
  • 1

写了这么多代码心里不禁打颤,到底是否正确呢?我们可以先测试一下,将SpringTest测试类的代码置为:

public class SpringTest {    @Test    public void test() {        ItcastClassPathXMLApplicationContext ctx = new ItcastClassPathXMLApplicationContext("beans.xml");     }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试test()方法,发现Eclipse控制台输出:

personDao=personDao

这就说明到目前为止,我们所写的代码是正确无误的。 
接下来,我们就要为bean对象的属性(依赖)注入值了。我们最后一次修改ItcastClassPathXMLApplicationContext类的代码为:

/** * 传智播客版Spring容器 * @author li ayun * */public class ItcastClassPathXMLApplicationContext {    private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();    private Map<String, Object> sigletons = new HashMap<String, Object>();    public ItcastClassPathXMLApplicationContext(String filename) {        this.readXML(filename);        this.instanceBeans();        this.injectObject();    }    /**     * 为bean对象的属性(依赖)注入值     */    private void injectObject() {        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 propertyDesc : ps) {                            if (propertyDefinition.getName().equals(propertyDesc.getName())) {                                Method setter = propertyDesc.getWriteMethod(); // 获取属性的setter方法,若setter方法是private的                                if (setter != null) { // 最好判断有无setter方法,因为属性可以没有setter方法                                    Object value = http://www.mamicode.com/sigletons.get(propertyDefinition.getRef());"hljs-keyword">true); // 允许访问私有的setter方法                                    setter.invoke(bean, value); // 把引用对象注入到属性中                                }                                break;                            }                        }                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }    }    /**     * 完成bean的实例化     */    private void instanceBeans() {        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();            }        }    }    /**     * 读取XML配置文件     * @param filename     */    private void readXML(String filename) {        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");                    // System.out.println(propertyName + "=" + propertyRef); // 测试用                    PropertyDefinition propertyDefinition = new PropertyDefinition(propertyName, propertyRef);                    beanDefine.getPropertys().add(propertyDefinition);                }                beanDefines.add(beanDefine);            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 获取bean实例     * @param beanName     * @return     */    public Object getBean(String beanName) {        return this.sigletons.get(beanName);    }}
  • 1

这样,一个模拟Spring容器的传智播客版Spring容器就应运而生了。最后,我们就要来测试该传智播客版Spring容器是否可行?只须修改SpringTest测试类的代码为:

public class SpringTest {    @Test    public void test() {        ItcastClassPathXMLApplicationContext ctx = new ItcastClassPathXMLApplicationContext("beans.xml");         PersonService personService = (PersonService) ctx.getBean("personService");        personService.save();    }}
  • 1技术分享

再次测试test()方法,Eclipse控制台输出:

执行PersonDaoBean中的add()方法

通过编写代码剖析Spring依赖注入的原理,我们就能比别人更加深刻地理解Spring内部到底是如何实现依赖注入的。源码可点击编码剖析Spring依赖注入的原理进行下载。

编码剖析Spring依赖注入的原理