7.3 Spring 的核心机制 : 依赖注入
Spring 框架的核心功能有两个。
Spring容器作为超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。
Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称为“依赖注入”的方式来管理Bean之间的依赖关系。
7.3.1 理解依赖注入
当某个java对象需要调用另一个Java对象的方法时,在传统模式下通常有如下两种做法:
1. 原始做法 : 调用者主动创建被依赖对象,然后在调用被依赖对象的方法。
2. 简单工厂模式 : 调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后在调用被依赖对象的方法。
解1:由于调用者需要通过形如“new Person()”的代码创建对象,因此必然导致调用者与被依赖对象实现类的硬编码耦合,非常不利于项目升级的维护。
解2:对于简单工厂的方式,大致需要把握三点:①调用者面向被依赖对象的接口编程;②将被依赖对象的创建交给工厂完成;③调用者通过工厂来获得被依赖组件。调用者只需与被依赖对象的接口耦合,避免了类层次的硬编码耦合。缺点:调用组件需要主动通过工厂去获取被依赖对象,这就会带来调用组件与被依赖对象工厂的耦合。
使用Spring框架的两个主要改变是:
程序无须使用 new 调用构造器去创建对象。所有的Java对象都可交给 Spring 容器去创建。
当调用者需要调用被依赖对象的方法时,调用者无须主动获取被依赖对象,只要等待 Spring 容器注入即可。
依赖注入通常有两种:
设值注入 : IoC 容器使用成员变量的setter方法来注入被依赖对象。
构造注入 : IoC 容器使用构造器来注入被依赖对象。
7.3.2 设值注入
设值注入是指IoC容器通过成员变量的setter方法来注入被依赖对象。
Spring 推荐面向接口编程。不管是调用者,还是被依赖对象,都应该为之定义接口,程序应该面向它们的接口,而不是面向实现类编程,这样以便程序后期的升级、维护。
Spring 推荐面向接口编程,这样可以更好地让规范和实现分离,从而提供更好的解耦。对于一个Java EE应用,不管是DAO组件,还是Service组件,都应该先定义一个接口,该接口定义了该组件应该实现的功能,但功能的实现则由实现类提供。
Interface : Person
package edu.pri.lime._7_3_2.bean; //定义Person对象应遵守的规范 public interface Person { // 定义一个使用斧头的方法 public void useAxe(); }
Interface :Axe
package edu.pri.lime._7_3_2.bean; //定义Axe对象应遵守的规范 public interface Axe { // Axe 接口中定义一个chop()方法 public String chop(); }
Class :Chinese
package edu.pri.lime._7_3_2.bean.impl; import edu.pri.lime._7_3_2.bean.Axe; import edu.pri.lime._7_3_2.bean.Person; public class Chinese implements Person { private Axe axe; // 设值注入所需的setter()方法 public void setAxe(Axe axe) { this.axe = axe; } //实现Person接口的userAxe()方法 public void useAxe() { // 调用axe的 chop()方法 // 表明Person对象依赖于axe对象 System.out.println(axe.chop()); } }
Class : StoneAxe
package edu.pri.lime._7_3_2.bean.impl; import edu.pri.lime._7_3_2.bean.Axe; public class StoneAxe implements Axe { public String chop() { // TODO Auto-generated method stub return "石斧砍柴好慢"; } }
配置文件
<?xml version="1.0" encoding="UTF-8"?> <!-- Spring 配置文件的根元素,使用Spring-beans-4.0.xsd语义约束 --> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <!-- 配置名为chinese的Bean。其实现类是edu.pri.lime._7_3_2.bean.impl.Chinese类 --> <bean id="person" class="edu.pri.lime._7_3_2.bean.impl.Chinese"> <!-- 驱动调用chinese的setAxe()方法,将容器中的stoneAxe Bean 作为传入参数 --> <property name="axe" ref="stoneAxe"/> </bean> <!-- 配置名为StoneAxe的Bean,其实现类是edu.pri.lime._7_3_2.bean.impl.StoneAxe类 --> <bean id="stoneAxe" class="edu.pri.lime._7_3_2.bean.impl.StoneAxe"/> </beans>
Class : TestBean
package edu.pri.lime._7_3_2.mian; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.pri.lime._7_3_2.bean.Person; import edu.pri.lime._7_3_2.bean.impl.Chinese; public class BeanTest { public static void main(String[] args){ // 创建Spring容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_3_2.xml"); // 获取chinese 实例 Person per = ctx.getBean("chinese", Chinese.class); // 调用 useAxe()方法 per.useAxe(); } }
Class : SteelAxe
package edu.pri.lime._7_3_2.bean.impl; import edu.pri.lime._7_3_2.bean.Axe; public class SteelAxe implements Axe { public String chop() { // TODO Auto-generated method stub return "钢斧砍柴真快"; } }
Class : TestBean
package edu.pri.lime._7_3_2.main; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.pri.lime._7_3_2.bean.Person; import edu.pri.lime._7_3_2.bean.impl.Chinese; public class BeanTest { public static void main(String[] args){ //创建Spring容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_3_2.xml"); //获取chinese 实例 Person per = ctx.getBean("chinese", Chinese.class); //调用 useAxe()方法 per.useAxe(); Person chi = ctx.getBean("china",Chinese.class); chi.useAxe(); } }
Spring IoC 容器的三个基本要点:
应用程序的各组件面向接口编程。面向接口编程可以将组件之间的耦合关系提升到接口层次,从而有利于项目后期的扩展。
应用程序的各组件不再由程序主动创建,而是有Spring容器来负责产生并初始化。
Spring 采用配置文件或注解来管理 Bean 的实现类、依赖关系,Spring容器则根据配置文件或注解,利用反射来创建实例,并为之注入依赖关系。
7.3.3 构造注入
在构造实例时,已经为其完成依赖关系的初始化,这种利用构造器来设置依赖关系的方式,被称为构造注入。
驱动Spring 在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化------这就是构造注入的本质。
<bean.../> 元素默认驱动Spring调用无参数的构造器来创建对象。使用<constructor-arg.../>子元素代表一个构造器参数,如果<bean.../>元素包含N个<contructor-arg.../>子元素,就会驱动Spring调用带N个参数的构造器来创建对象。
Class :Chinese
package edu.pri.lime._7_3_3.bean.impl; import edu.pri.lime._7_3_2.bean.Axe; import edu.pri.lime._7_3_2.bean.Person; public class Chinese implements Person { private Axe axe; public Chinese() { super(); // TODO Auto-generated constructor stub } // 未有setter()方法,使用构造方法注入 public Chinese(Axe axe) { super(); this.axe = axe; } public void useAxe() { System.out.println(axe.chop()); } }
配置文件 :
<?xml version="1.0" encoding="UTF-8"?> <!-- Spring 配置文件的根元素,使用Spring-beans-4.0.xsd语义约束 --> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <!-- 配置名为chinese的Bean。其实现类是edu.pri.lime._7_3_3.bean.impl.Chinese类 --> <bean id="chinese" class="edu.pri.lime._7_3_3.bean.impl.Chinese"> <!-- 驱动Spring调用Chinese的带一个参数的构造器来创建对象 --> <constructor-arg ref="stoneAxe"/> </bean> <!-- 配置名为StoneAxe的Bean,其实现类是edu.pri.lime._7_3_2.bean.impl.StoneAxe类 --> <bean id="stoneAxe" class="edu.pri.lime._7_3_2.bean.impl.StoneAxe" /> <bean id="steelAxe" class="edu.pri.lime._7_3_2.bean.impl.SteelAxe"/> </beans>
Class : BeanTest
package edu.pri.lime._7_3_3.main; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.pri.lime._7_3_2.bean.Person; import edu.pri.lime._7_3_3.bean.impl.Chinese; public class BeanTest { public static void main(String[] args){ ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_3_3.xml"); Person per = ctx.getBean("chinese",Chinese.class); per.useAxe(); } }
Spring底层,反射机制
String idStr = ... // Spring 解析<bean.../> 元素得到id属性值为chinese String refStr = ... // Spring 解析 <constructor-arg.../> 元素得到ref属性值为steelAxe Object paramBean = container.get(refStr); // Spring 会用反射方式执行下面代码(仅仅示例,为用反射) Object obj = new Chinese(paramBean); // container 代表Spring 容器 container.put(idStr,obj);
package edu.pri.lime._7_3_3.main; import java.lang.reflect.Constructor; import edu.pri.lime._7_3_2.bean.Axe; import edu.pri.lime._7_3_2.bean.Person; public class ReflectTest { public static void main(String[] args) throws Exception{ Class<?> clazzChi = Class.forName("edu.pri.lime._7_3_3.bean.impl.Chinese"); Class<?> clazzAxe = Class.forName("edu.pri.lime._7_3_2.bean.impl.SteelAxe"); Class<?> clazz = Class.forName("edu.pri.lime._7_3_2.bean.Axe"); Axe axe = (Axe) clazzAxe.newInstance(); Constructor<?> con = clazzChi.getConstructor(clazz); Person per = (Person) con.newInstance(axe); per.useAxe(); } }
配置<constructor-arg .../> 元素是可指定一个index属性,用于指定该构造参数值将作为第几个构造参数数值:index=0表明该构造参数值将作为第一个构造参数值。
<constructor-arg value="23" type="int"/>:用于更明确的指定数据类型。
7.3.4 两种注入方式的对比