依赖注入

  spring核心容器就是一个超级大工厂,所以的对象(数据源、hibernate SessionFactory等基础性资源)都会被当做spring核心容器的管理对象——spring把容器中的一切对象统称为Bean。

  Spring对Bean没有任何要求,只要是一个java类,spring就可以管理这个java类,并把它当做Bean处理。对于spring框架而言,一切java对象都是Bean。

  1. package service;
  2.  
  3. public class Axe
  4. {
  5. public String chop()
  6. {
  7. return "使用斧头砍柴";
  8. }
  9. }

  这个Axe类是一个普通的java类。

  1. package service;
  2.  
  3. public class Person
  4. {
  5. private Axe axe;
  6. //设值注入所需的setter方法
  7. public void setAxe(Axe axe)
  8. {
  9. this.axe = axe;
  10. }
  11. public void useAxe()
  12. {
  13. System.out.print("我打算去砍柴!");
  14. //调用axe的chop()方法
  15. //声明Person对象依赖于Axe
  16. System.out.println(axe.chop());
  17. }
  18. }

  这个Person类的useAxe()方法需要调用Axe对象的chop()方法,这种A对象需要调用B对象方法的情形,被称为依赖。

  Spring核心容器是整个应用的超级工厂,所有的java对象都会讲给Spring的容器管理——这些java对象被称为Spring容器中的Bean。

  Spring对于bean的管理需要在xml中进行配置。

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
  3. "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5. <!--配置名为person的Bean,其实现类为service.Person-->
  6. <bean id="person" class="service.Person">
  7. <!--控制调用setAxe()方法,将容器中的ax Bean作为参数传递进去-->
  8. <property name="axe" ref="axe"/>
  9. </bean>
  10. <!--配置名为axe的Bean,其实现类为service.Axe-->
  11. <bean id="axe" class="service.Axe"></bean>
  12.  
  13. </beans>

  配置文件的根元素是<beans .../>,根元素中包含多个<bean .../>元素,每个元素定义一个Bean。在这里配置了两个Bean,分别是service.Person和service.Axe。

  只要将java类配置到xml中,spring就可以对其进行管理。

  1. <bean id="person" class="service.Person">

  配置文件会将<bean .../>元素默认以反射方式类调用这个类的无参构造器,spring解析这一元素之后得到两个字符串,其中idStr的值为"person"(对应的是id属性的值),classStr的值为"service.Person"(对于的是class属性的值)。

  1. String idStr = ...;
  2. String classStr = ...;
  3. Class clazz = Class.forName(classStr);
  4. Object obj = clazz.newInstance();
  5. //container代表spring容器
  6. container.put(idStr, obj);

  spring框架通过反射根据<bean .../>元素的class属性创建了一个java对象,并以<bean .../>元素的id属性的值为key,将该对象放入spring容器中——这个java对象就成为了spring容器中的Bean。

  在spring配置文件中配置Bean时,class属性的值必须是Bean实现类的完整类名。

  上面配置文件中还包括一个<property .../> 子元素,它驱动spring在底层以反射执行一次setter方法。name属性决定了执行哪些setter方法,value或者ref决定执行setter方法的传入参数。

  • 如果传入的是基本类型及其包装类、String等类型,则使用value属性指定传入参数;
  • 如果以容器中其他Bean作为传入参数,则使用ref属性指定传入的参数。

  spring框架只要看到<property .../>子元素,就会在底层反射执行一次setter方法,该Bean一旦创建,spring就会立即根据<property .../>子元素来执行setter方法。也就是说:

  1. <bean .../>元素驱动spring调用构造器创建对象;
  2. <property .../>元素驱动spring执行setter方法。

  这两步是先后执行的,中间几乎没有时间间隔。

  上面的配置中<property .../>元素的name属性为axe,该元素驱动spring以反射方式执行person Bean中的setAxe()方法;ref属性值为axe,该属性值指定以容器名为axe的Bean作为执行setter方法的传入参数。

  也就是说,在spring的底层会执行如下代码:

  1. //解析<property .../>元素的name属性得到该字符串值为"axe"
    String nameStr = ...;
    //解析<property .../>元素的ref属性得到该字符串的值为"axe"
  2. String refStr = ...;
  3. String setterName = "set" + nameStr.substring(0, 1).toUpperCase() + nameStr.substring(1);
  4. //获取spring容器中名为refStr的Bean,该Bean将会作为传入参数
  5. Object paramBean = container.get(refStr);
  6. //此处的clazz是从反射得到的Class对象
  7. Method setter = clazz.getMethod(setterName, paramBean.getClass());
  8. //此处的obj参数是之前一段反射代码创建的对象
  9. setter.invoke(obj, paramBean);

  上述的代码是反射代码的实例,通过<property .../>的name属性决定调用哪个setter方法,并且根据value和ref决定调用setter方法的传入参数。

  id为person的<bean .../>元素还包括一个<property .../>子元素,因此spring会在创建完person Bean之后,立即以容器中id为axe的Bean被赋值给person对象的axe实例变量。

  接下来程序会通过spring容器来访问容器中的Bean,ApplicationContext是Spring容器中最常用的接口,该接口有如下两个实现类。

  1. ClassPathXmlApplicationContext:从类加载路径下搜索配置文件,并根据配置文件来创建spring容器;
  2. FileSystemXmlApplicationContext:从文件系统的相对路径或绝对路径下去搜索配置文件,并根据配置文件来创建spring容器。

在spring中,类加载路径是稳定的,因此通常使用ClassPathXmlApplicationContext来创建容器。

  1. package service;
  2.  
  3. import org.springframework.beans.factory.BeanFactory;
  4. import org.springframework.beans.factory.xml.XmlBeanFactory;
  5. import org.springframework.context.ApplicationContext;
  6. import org.springframework.context.support.ClassPathXmlApplicationContext;
  7. import org.springframework.core.io.FileSystemResource;
  8. import org.springframework.core.io.Resource;
  9.  
  10. public class Main {
  11. public static void main(String[] args)
  12. {
  13. //引入配置文件,创建spring容器
  14. Resource r = new FileSystemResource("src/beans.xml");
  15. //加载配置文件
  16. BeanFactory f = new XmlBeanFactory(r);
  17. //获取id为person的Bean
  18. Person p = (Person)f.getBean("person", Person.class);
  19. //调用useAxe()方法
  20. p.useAxe();
  21. }
  22.  
  23. }

  spring获取Bean对象的方式有两种:

1.Object getBean(String id):根据容器中Bean的id来获取Bean,获取Bean之后需要进行强制类型转换;

2.T getBean(String name, Class<T> requiredType):根据容器中Bean的id来获取指定的Bean,但是该方法带一个泛型参数,因此获得Bean之后无需进行强制类型转换。

  获取Bean对象之后,可以调用方法、访问实例变量,即可以像使用java对象一样使用这个Bean对象。

1、依赖注入

  1.调用者面向被依赖对象的接口编程;

  2.将被依赖对象的创建交给工厂完成;

  3.调用者通过工厂来获取被依赖组件。

  通过这三点,可以保证调用者主需与被依赖对象的接口耦合,这就避免了类层次的硬编码耦合,使用spring框架之后,调用者无需主动获取被依赖对象,只需被动接受spring容器为调用者的成员变量赋值即可(只要配置一个<property .../>子元素,spring就会执行对应的setter方法为调用者的成员变量赋值)。于是,使用了spring之后,调用者获取被依赖对象的方式由原来的主动获取变成了变动接受,这被称为控制反转(Inversion of Control,IoC)。

  从spring框架的角度来说,spring容器负责将被依赖对象赋值给调用者的成员变量——相当于为调用者注入它依赖的实例,因此这种方式被称为依赖注入(Dependency Injection)。

  使用了spring框架之后,主要有两个变化:

1.程序员无需使用new创建对象,所有的java对象的创建都交给spring容器完成;

2.当调用者需要调用被依赖对象的方法的时候,调用者无需主动获取被依赖对象,只需要等待spring容器注入即可。

2、注入方式

  • 设值注入:IoC容器使用成员变量的setter方法来注入被依赖对象;
  • 构造注入:IoC容器使用构造器来注入被依赖对象。

2.1 设值注入

  设值注入指的是IoC容器通过成员变量的setter方法来注入被依赖对象。

  spring推荐面向接口编程,不管是调用者还是被依赖的对象,都应该为之定义接口,程序应该面向它们的接口,而不是面向实现类编程,这样利于后期的维护和升级。

①Axe接口

  1. package test;
  2.  
  3. public interface Axe
  4. {
  5. public String chop();
  6. }

②Person接口

  1. package test;
  2.  
  3. public interface Person
  4. {
  5. public void useAxe();
  6.  
  7. }

③Chinese类

  1. package test;
  2.  
  3. public class Chinese implements Person
  4. {
  5. private Axe axe;
  6. public void setAxe(Axe axe)
  7. {
  8. this.axe = axe;
  9. }
  10. //实现Person接口定义的useAxe()方法
  11. public void useAxe()
  12. {
  13. System.out.println(axe.chop());
  14. }
  15. }

④StoneAxe类

  1. package test;
  2.  
  3. public class StoneAxe implements Axe
  4. {
  5. public String chop()
  6. {
  7. return "石斧砍柴好慢";
  8. }
  9.  
  10. }

⑤beans.xml配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
  3. "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5. <bean id="chinese" class="test.Chinese">
  6. <property name="axe" ref="stoneAxe"/></bean>
  7. <bean id="stoneAxe" class="test.StoneAxe"></bean>
  8. </beans>

⑥Main类

  1. package test;
  2.  
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. import org.springframework.context.support.FileSystemXmlApplicationContext;
  6.  
  7. //import org.springframework.context.ApplicationContext;
  8. public class Main {
  9. public static void main(String[] args)
  10. {
  11. ApplicationContext ctx = new FileSystemXmlApplicationContext("D:/java/workspace_j2ee/SpringDemo3/src/beans.xml");
  12. Person p = (Person)ctx.getBean("chinese", Person.class);
  13. p.useAxe();
  14. }
  15.  
  16. }

注意使用FileSystemXmlApplicationContext的时候,可以直接使用"src/beans.xml"作为beans.xml的路径,spring可以找的到配置文件的位置。

  假设Axe有另外的实现类:SteelAxe。

  1. package test;
  2.  
  3. public class SteelAxe implements Axe{
  4. public String chop()
  5. {
  6. return "钢斧砍柴更快";
  7. }
  8.  
  9. }

  此时,需要将SteelAxe部署在spring容器中去,只需要在beans.xml中添加配置信息。

  1. <bean id="steelAxe" class="test.SteelAxe"></bean>

  这一行定义了一个Axe实例,id是steelAxe,实现的类是SteelAxe,然后需要修改chinese的配置信息。

  1. <bean id="chinese" class="test.Chinese">
  2. <!-- <property name="axe" ref="stoneAxe"/></bean> -->
  3. <property name="axe" ref="stoneAxe"/></bean>

  因为chinese实例与具体的Axe实现类之间没有任何关系,chinese仅仅与接口Axe耦合,这样就能保证chinese实例与Axe的松耦合——这是spring强调面向接口编程的原因。

  Bean与Bean之间的依赖关系由spring管理,spring采用setter方法为目标Bean注入所依赖的Bean,让Bean之间的耦合从代码层次上分离出来,依赖注入是一种优秀的解耦方式。

  所以,可以看出spring的IoC容器的三个基本要点:

  ①应用程序的各组件面向接口编程,面向接口编程可以将组件之间的耦合关系提升到接口层次,从而利于项目后期拓展;

  ②应用程序各组件不再由程序主动创建,而是由spring容器来负责产生并初始化;

  ③spring采用配置文件或注解来管理Bean的实现类、依赖关系,spring容器根据配置文件或注解,利用反射机制来创建实例,并将其注入依赖关系。

2、 构造注入

  这种方式在构造实例的时候,已经为其完成了依赖关系的初始化,这种利用构造器来设置依赖关系的方式,被称为构造注入

  驱动spring在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化——这是构造注入的本质。

  <bean .../>元素默认总是驱动spring调用无参数的构造器来创建对象,使用<consrtructor-arg .../>子元素,每个<constructor-arg .../>子元素代表一个构造器参数,如果<bean .../>元素包含N个<constructor-arg .../>子元素,就会驱动spring调用带N个参数的构造器来创建对象。

①Roles类

  1. package test;
  2.  
  3. public class Roles {
  4. private int id;
  5. private String roleName;
  6. public Roles(){
  7. }
  8. public Roles(int id, String roleName)
  9. {
  10. this.id = id;
  11. this.roleName = roleName;
  12. }
  13. public String toString()
  14. {
  15. return "Users [id = " + id + ", name= " + roleName + "]";
  16. }
  17. }

②beans.xml配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
  3. "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5. <bean id="roles" class="test.Roles">
  6. <constructor-arg value="1"/>
  7. <constructor-arg value="小明"/>
  8. </bean>
  9. </beans>

③测试

  1. package test;
  2.  
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.FileSystemXmlApplicationContext;
  5.  
  6. public class SpringTest {
  7. public static void main(String[] args)
  8. {
  9. ApplicationContext ctx = new FileSystemXmlApplicationContext("src/bean.xml");
  10. Roles r = (Roles)ctx.getBean("roles");
  11. System.out.println(r.toString());
  12. }
  13. }

  设值注入是先通过无参数的构造器创建一个Bean实例,然后调用setter方法注入依赖关系,而构造注入则直接调用有参数的构造器,当Bean实例创建完成之后,已经完成了依赖关系的注入。

  <constructor-arg .../>可以指定参数的值,  

  1. <bean id="roles" class="test.Roles">
  2. <constructor-arg value="1"/>
  3. <constructor-arg value="小明"/>

  这一段的内容相当于:

  1. Roles role = new Role(1, "小明");

  有时候,如果包含的构造器中的参数不同,但是构造器的名称相同,假设Test(String, String) 和Test(String, int),假如通过<constructor-arg value="1">由于spring只能解析出"1"字符串,但是到底转换为哪一个明确的构造器的数据类型,就无从判断了,所以spring允许为<constructor-arg .../>元素指定一个type类型,比如说<constructor-arg value="1", type="int"/>,此时就完成了int类型参数的配置。

  假如存在依赖,看下面的例子:

①Users类

  1. package test;
  2.  
  3. public class Users {
  4. private int id;
  5. private String name;
  6. public Users(){
  7. }
  8. public Users(int id, String name)
  9. {
  10. this.id = id;
  11. this.name = name;
  12. }
  13. public String toString()
  14. {
  15. return "User [id = " + id + ", name = " + name + "]";
  16. }
  17.  
  18. }

②Roles类

  1. package test;
  2.  
  3. public class Roles {
  4. private int id;
  5. private String roleName;
  6. //用户
  7. private Users users;
  8. public Roles(){
  9. }
  10. public Roles(int id, String roleName, Users users)
  11. {
  12. this.id = id;
  13. this.roleName = roleName;
  14. this.users = users;
  15. }
  16. public String toString()
  17. {
               //因为这里是在字符串加法中调用的Users对象,所以会隐式调用user.toString()方法,也可以显示调用users.toString方法
  18. return "Roles [id=" + id + ", roleName=" + roleName + ", users="
  19. + users + "]";
  20. }
  21. }

③配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
  3. "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5. <bean id="roles" class="test.Roles">
  6. <constructor-arg value="1"/>
  7. <constructor-arg value="小明"/>
  8. <constructor-arg ref="users"/>
  9. </bean>
  10. <bean id = "users" class = "test.Users">
  11. <constructor-arg value = "2"/>
  12. <constructor-arg value = "小华"/>
  13. </bean>
  14. </beans>

④测试

  1. package test;
  2.  
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.FileSystemXmlApplicationContext;
  5.  
  6. public class SpringTest {
  7. public static void main(String[] args)
  8. {
  9. ApplicationContext ctx = new FileSystemXmlApplicationContext("src/bean.xml");
  10. Roles r = (Roles)ctx.getBean("roles");
  11. System.out.println(r.toString());
  12. }
  13. }

⑤输出结果

  1. Roles [id=1, roleName=小明, users=User [id = 2, name = 小华]]

3.两种注入方法的对比

  设置注入的适用场景:

  1. 与传统的JavaBean的写法相似,更容易理解,通过setter方法设定依赖关系显得更加直观,自然;
  2. 对于复杂的依赖关系,如果采用构造注入, 会导致构造器过于臃肿,难以阅读,spring在创建Bean实例的时候,需要同时实例化其依赖的全部实例,因而导致性能下降,而如果使用设值注入,会比较轻松;
  3. 尤其是在某些成员变量可选的情况下,多参数的构造器很笨重。

  

  构造注入的适用场景:

  1. 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入,例如,组件中其它依赖关系的注入,常常需要依赖于Datasource的注入,采用构造注入可以设置注入的顺序;
  2. 对于依赖关系无需变化的Bean,构造注入更加实用。因为没有setter方法,所有的依赖关系都在构造器中设定,因此,无需担心后续代码对依赖关系产生破坏;
  3. 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全是透明的,更符合高内聚的原则。

总结:

  建议以设置注入为主,构造注入为辅。对于依赖关系无需变化的注入,尽量采用构造注入,而其他依赖关系的注入,考虑使用设值注入的方式。

  

spring框架(1)— 依赖注入的更多相关文章

  1. 采用dom4j和反射模拟Spring框架的依赖注入功能

    Spring的依赖注入是指将对象的创建权交给Spring框架,将对象所依赖的属性注入进来的行为.在学习了dom4j后,其实也可以利用dom4j和反射做一个小Demo模拟Spring框架的这种功能.下面 ...

  2. spring框架之依赖注入(DI)

    1. IOC和DI的概念 * IOC -- Inverse of Control,控制反转,将对象的创建权反转给Spring!! * DI -- Dependency Injection,依赖注入,在 ...

  3. Spring框架(依赖注入)

    特点 1轻量级和侵入性低 2依赖注入和面向接口实现松耦合 3面向切面编程 减少样式代码 专有名词: 1依赖注入:对象无需自行管理依赖关系.通过系统负责协调在创建对象的第三方组件的设定,实现依赖关系自动 ...

  4. Spring框架——IOC依赖注入

    本来想把IOC和AOP一起介绍的,但是AOP内容太多了,所以就分开了,最后的结果就是这一篇只剩下一点点了.这不是第一次写关于IOC的文章了,之前写过Java反射,Java注解,也写过通过XML解析实现 ...

  5. Spring学习(一)——Spring中的依赖注入简介【转】

      [前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring. ...

  6. JavaEE开发之Spring中的依赖注入与AOP

    上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...

  7. JavaEE开发之Spring中的依赖注入与AOP编程

    上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...

  8. Spring学习(一)——Spring中的依赖注入简介

    [前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring.不知 ...

  9. 3、Spring的DI依赖注入

    一.DI介绍 1.DI介绍 依赖注入,应用程序运行依赖的资源由Spring为其提供,资源进入应用程序的方式称为注入. Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称为&qu ...

  10. Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->Spring Framework的依赖注入和控制反转

    Dependency Injection and Inversion of Control 1.概述: 1.1相关概念 bean:由IoC容器所管理的对象,也即各个类实例化所得对象都叫做bean 控制 ...

随机推荐

  1. vue调用豆瓣API加载图片403问题

    "豆瓣API是有请求次数限制的”,这会引发图片在加载的时候出现403问题,视图表现为“图片加载不出来”,控制台表现为报错403. 其实是豆瓣限制了图片的加载,我自己用了一个办法把图片缓存下来 ...

  2. Google+百度,自动识别知名人物的性别

    最近有一个任务,需要采集一批知名学者的性别信息.该任务的难点在于提供学者信息的网站并不会主动标注学者的性别性别,因此只能靠别的方法了. 对一个普通人来说,在网上判断一个人的性别的最快的方式就是看他的照 ...

  3. Flask之蓝图的使用

    蓝图,听起来就是一个很宏伟的东西 在Flask中的蓝图 blueprint 也是非常宏伟的 它的作用就是将 功能 与 主服务 分开怎么理解呢? 比如说,你有一个客户管理系统,最开始的时候,只有一个查看 ...

  4. PHP的发展历程

    PHP的发展历程 了解一门语言,我们必须知道这门语言的发展史,现在我通过版本的变化以时间轴的形式来说明PHP的发展历程. 1.1995年初PHP1.0诞生 Rasmus Lerdof发明了PHP,这是 ...

  5. 初学者:__init__.py文件的作用

    __init__.py 文件的作用及意义 __init__.py文件是一个包必须的文件,即使它是空的,但也是必须的,如果没有这个文件,python将不会把该文件夹当做一个package,而仅仅是一个d ...

  6. Java核心技术36讲----------Exception和Error有什么区别

    1.异常知识点学习实例 代码如下: package fromnet; /** * 参考链接:https://blog.csdn.net/qq_18505715/article/details/7319 ...

  7. Libcurl交叉编译

    目录 配置configure 执行make 取得su权限 开始安装 踩坑总结 配置configure ./configure --build=arm --host=mipsel-openwrt-lin ...

  8. C语言小程序-基于链表的学生信息管理

    程序支持增加.查询.删除.存盘和读取操作 一 程序定义和函数声明 头文件studentsys.h定义如下 /* student management system by list */ #ifndef ...

  9. python3 小实践(一)——selenium获取的cookie传递

    from selenium import webdriver from time import sleep import requests import pickle #获取登录后的cookies c ...

  10. Linux命令应用大词典-第35章 终端

    35.1 tty:显示当前连接到当前标准输入的终端设备文件名 35.2 consoletype:显示连接到标准输入的控制台类型 35.3 fgconsole:显示活动的虚拟终端数量 35.4 ming ...