本文是为学生讲课后整理的笔记,同道中人可以任意传播,唯独期望在 CxDN 上全文照抄时,注明原文出处
本节主要讲解在 Spring IoC 中的依赖注入(Dependency Injection)的两种实现方式。
由于英文水平有限,同时为保证原汁原味地表达Spring中关于依赖注入部分本来的含义,标题和部分内容采用 Spring Framework 官方文档中的英文原文。
另外,在测试代码中读取 Spring 配置文件时,尽量使用了完整的路径而未使用通配符。
0、Field and Proprey
设存在以下 Java Bean :
public class Person {
private int id ;
private String name ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在 Person 类中,以成员变量(Member Variable)形式存在的 id 和 name 属于 Person 类中的字段( Field )。字段(Field)本质上是一个变量,用于存储数值(可以是基本数据类型的数值、也可以是引用)。
在 Person 类中刻意声明了两种不同类型的字段(Field):
private int id ;
private String name ;
因为id
是基本数据类型,因此该字段(Field)可以直接存储整数值。
而name
是 String 类型,属于引用类型,因此该字段(Field)用于存储字符串的引用。
注意:即使在为 String 类型的变量赋值时使用了字符串字面量,也不能改变String变量中存储的是引用的事实。
在 Java Bean 中,字段(Field)通常是私有的,因此需要提供setter和getter来访问相应的字段,比如在 Person 类中,就有以下方法用以访问 id 字段:
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
在这里,我们要通过getter或setter来强调属性(property)的概念。
对于 getter 来说,按照以下顺序进行变换即可得到属性(property):
- 将 getId 方法的方法名去除 get ,得到 Id (注意不改变任何字母的大小写)
- 将 第1步 中得到的字符串首字母变小写,得到 id (即得到属性名称)
同样对于 setter 来说,也可以按照以下顺序进行变换即可得到属性(property):
- 将 setId 方法的方法名去除 set ,得到 Id (注意不改变任何字母的大小写)
- 将 第1步 中得到的字符串首字母变小写,得到 id (即得到属性名称)
因为绝大多数的 Java Bean 中字段(field)名称 和 属性(property) 是相同的,因此很多人会误以为属性(property)就是字段(field)、字段(field)就是属性(property)。
为了进一步区分字段(field)和属性(property),我们将 Person 改成以下形式:
public class Person {
private int personId ;
private String personName ;
public int getId() {
return personId;
}
public void setId(int id) {
this.personId = id;
}
public String getName() {
return personName;
}
public void setName(String name) {
this.personName = name;
}
}
此时,成员变量 personId 和 personName 是 Person 类中的字段(field),用于存储数据。而 id 和 name 是 Person 类的属性(property),用于访问相应的字段。
因此在 JSP 中,可以通过以下形式的 EL 表达式中可以用以下形式来获取属性(property)值
id : ${person.id}
name : ${person.name}
本质上,是通过 getId 和 getName 来获取 personId 和 personName 字段的值。
接下来,我们提供一个 Java Bean 供后续使用:
package io.malajava.ioc.injection;
import java.util.Date;
public class Sinaean {
private Integer id ;
private String name ;
private char gender ;
private Date birthdate ;
private boolean married ;
public Sinaean() {
super();
System.out.println( "public Sinaean()" );
}
public Sinaean(Integer id, String name) {
super();
System.out.println( "public Sinaean(Integer,String) : " + id );
this.id = id;
this.name = name;
}
public Sinaean(Integer id, String name, char gender) {
super();
System.out.println( "public Sinaean(Integer,String,char) : " + id );
this.id = id;
this.name = name;
this.gender = gender;
}
@Override
public String toString() {
return "{" +
"id : " + id +
", name : '" + name + '\'' +
", gender : " + gender +
", birthdate : [ " + birthdate +
" ], married : " + married +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println( "Sinaean # setId( Integer )" );
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println( "Sinaean # setName( String )" );
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
System.out.println( "Sinaean # setGender( char )" );
this.gender = gender;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
System.out.println( "Sinaean # setBirthdate( java.util.Date )" );
this.birthdate = birthdate;
}
public boolean isMarried() {
return married;
}
public void setMarried(boolean married) {
System.out.println( "Sinaean # setMarried( boolean )" );
this.married = married;
}
}
1、Setter-based dependency injection
1.1、使用 <property> 标签实现注入
- Java Bean
参见前面步骤中的 Sinaean 类
- Configuration Metadata
<!-- 使用 DateFactoryBean 来创建任意年月日对应的 Date 对象-->
<bean id="birthdate" class="io.malajava.ioc.beancreation.DateFactoryBean">
<property name="year" value="1996" />
<property name="month" value="7" />
<property name="date" value="4" />
</bean>
<bean id="xiaoyuer" class="io.malajava.ioc.injection.Sinaean" >
<property name="id" value="1001" />
<property name="name" value="小鱼儿" />
<property name="gender" value="男" />
<property name="married" value="false" />
<property name="birthdate" ref="birthdate" />
</bean>
<bean id="huawuque" class="io.malajava.ioc.injection.Sinaean" >
<property name="id" value="1002" />
<property name="name" value="花无缺" />
<property name="gender" value="男" />
<property name="married" value="false" />
<property name="birthdate">
<bean class="io.malajava.ioc.beancreation.DateFactoryBean">
<property name="year" value="1997" />
<property name="month" value="7" />
<property name="date" value="5" />
</bean>
</property>
</bean>
</beans>
注: 设以上内容存在于类路径下的 io/malajava/ioc/injection/injection-setter-property.xml 文件中。
注意,在以上配置中使用 <property>
标签可以为指定JavaBean的属性(property)注入属性值,其中:
<property>
标签的 name 属性用于指定 Java Bean 中属性的名称<property>
标签的 value 属性用于指定 需要注入的属性值<property>
标签的 ref 属性用于指定 引用其它的 bean
以下17种类型可以通过<property>
标签的 value 属性直接指定字面值:
- Java语言中的 8 种基本数据类型
- Java语言中 8 种基本数据类型对应的包装类类型
- java.lang.String 类型
所有的引用类型(包括包装类类型和String类型)的属性,都可以使用 <property>
标签的 ref 属性来应用已经存在于IoC容器中的其它的 bean (同种类型),比如:
<!-- 使用 DateFactoryBean 来创建任意年月日对应的 Date 对象-->
<bean id="birthdate" class="io.malajava.ioc.beancreation.DateFactoryBean">
<property name="year" value="1996" />
<property name="month" value="7" />
<property name="date" value="4" />
</bean>
<bean id="xiaoyuer" class="io.malajava.ioc.injection.Sinaean" >
<!-- 此处省略其它属性 -->
<property name="birthdate" ref="birthdate" />
</bean>
所有的引用类型(包括包装类类型和String类型)的属性,也都可以在<property>
标签内部使用 <bean>
标签来创建需要注入的bean,比如:
<property name="birthdate">
<bean class="io.malajava.ioc.beancreation.DateFactoryBean">
<property name="year" value="1997" />
<property name="month" value="7" />
<property name="date" value="5" />
</bean>
</property>
- Testing
public class SetterInjectionTest1 {
public static void main(String[] args) {
DateFormat df = new SimpleDateFormat( "yyyy-MM-dd" );
// 指定 Configuration Metadata
String configLocation = "classpath:io/malajava/ioc/injection/injection-setter-property.xml" ;
// 创建 Spring IoC 容器
ApplicationContext iocContainer = new ClassPathXmlApplicationContext( configLocation ) ;
// 从容器中获取 bean
Sinaean s = iocContainer.getBean( "xiaoyuer" , Sinaean.class );
System.out.println( s ); // s.toString()
System.out.println( df.format( s.getBirthdate() ) );
Sinaean x = iocContainer.getBean( "huawuque" , Sinaean.class );
System.out.println( x ); // x.toString()
System.out.println( df.format( x.getBirthdate() ) );
}
}
1.2、使用 p 命名空间 实现注入
- Java Bean
参见前面步骤中的 Sinaean 类
- Configuration Metadata
从 Spring 3 开始,支持在XML配置文件中使用 p 命名空间。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用 DateFactoryBean 来创建任意年月日对应的 Date 对象-->
<bean id="birthdate"
class="io.malajava.ioc.beancreation.DateFactoryBean"
p:year="1996"
p:month="7"
p:date="4" />
<bean id="huaan"
class="io.malajava.ioc.injection.Sinaean"
p:id="9527"
p:name="华安"
p:gender="男"
p:married="true"
p:birthdate-ref="birthdate" />
</beans>
注: 设以上内容存在于类路径下的 io/malajava/ioc/injection/injection-setter-p-namespace.xml 文件中。
使用 p 命名空间时需要注意:
- Java 语言中 8 种基本数据类型的属性可以直接在等号之后书写 字面值
- 8 种基本数据类型对应的包装类类型的属性可以直接在等号之后书写 字面值
- java.lang.String 类型的属性可以直接在等号之后书写 字面值
- 所有引用类型(包括包装类类型和String类型)的属性都可以使用 -ref 来引用其它 bean
另外,千万要注意,只有在 <beans>
标记中增加了以下内容才可以使用p命名空间:
xmlns:p="http://www.springframework.org/schema/p"
- Tesging
public class SetterInjectionTest2 {
public static void main(String[] args) {
DateFormat df = new SimpleDateFormat( "yyyy-MM-dd" );
// 指定 Configuration Metadata
String configLocation = "classpath:io/malajava/ioc/injection/injection-setter-p-namespace.xml" ;
// 创建 Spring IoC 容器
ApplicationContext iocContainer = new ClassPathXmlApplicationContext( configLocation ) ;
// 从容器中获取 bean
Sinaean s = iocContainer.getBean( "huaan" , Sinaean.class );
System.out.println( s ); // s.toString()
System.out.println( df.format( s.getBirthdate() ) );
}
}
2、Constructor-based dependency injection
2.1、使用 <constructor-arg> 标签实现参数注入
- Java Bean
参见前面步骤中的 Sinaean 类
- Configuration Metadata
设以下内容存在于类路径下的 io/malajava/ioc/injection/injection-constructor-arg.xml 文件中。
<!-- Sinaean(Integer id, String name, char gender) -->
<bean id="first" class="io.malajava.ioc.injection.Sinaean">
<!-- 使用 constructor-arg 的 name 属性可以指定 构造方法中的 参数名称 -->
<constructor-arg name="id" value="1001" />
<constructor-arg name="gender" value="女" />
<constructor-arg name="name" value="赵敏" />
</bean>
<!-- Sinaean(Integer id, String name) -->
<bean id="second" class="io.malajava.ioc.injection.Sinaean">
<!-- 使用 constructor-arg 的 index 属性 指定 构造方法 中参数的索引-->
<constructor-arg index="0" value="1002" />
<constructor-arg index="1" value="韩小昭" />
</bean>
<!-- Sinaean(Integer id, String name ,char gender) -->
<bean id="third" class="io.malajava.ioc.injection.Sinaean">
<!-- 使用 constructor-arg 的 type 属性 指定 构造方法 中参数的索类型-->
<!-- 根据类型来确定 哪个值 应该放在传递给哪个参数 ( 仅限构造方法中没有两个以上相同类型参数 的情况 ) -->
<constructor-arg type="java.lang.Integer" value="1003" />
<constructor-arg type="java.lang.String" value="殷离" />
<constructor-arg type="char" value="女" />
</bean>
<!-- Sinaean(Integer id, String name ,char gender) -->
<bean id="fourth" class="io.malajava.ioc.injection.Sinaean">
<constructor-arg index="0" name="id" type="java.lang.Integer" value="1004" />
<constructor-arg index="1" name="name" type="java.lang.String" value="周芷若" />
<constructor-arg index="2" name="gender" type="char" value="女" />
</bean>
对于以下类型的参数来说,可以直接使用 <constructor-arg
的 value 来指定参数值:
- Java 语言中 8 种基本数据类型
- 8 种基本数据类型对应的包装类类型
- java.lang.String 类型
所有引用类型(包括包装类类型和String类型),都可以通过 <constructor-arg
的 ref 来指定参数值(即引用其它的bean),比如:
<!-- public Integer( int value ) -->
<bean id="fourthId" class="java.lang.Integer">
<constructor-arg type="int" value="1004" />
</bean>
<!-- public String( String source ) -->
<bean id="fourthName" class="java.lang.String">
<constructor-arg type="java.lang.String" value="周芷若" />
</bean>
<bean id="fourth" class="io.malajava.ioc.injection.Sinaean">
<constructor-arg name="id" ref="fourthId" />
<constructor-arg name="name" ref="fourthName" />
<constructor-arg name="gender" value="女" />
</bean>
所有引用类型(包括包装类类型和String类型),也都可以在 <constructor-arg
标签内部通过 <bean>
标签来创建相应对象后再通过构造注入:
<!-- public Integer( int value ) -->
<bean id="fourthId" class="java.lang.Integer">
<constructor-arg type="int" value="1004" />
</bean>
<!-- public String( String source ) -->
<bean id="fourthName" class="java.lang.String">
<constructor-arg type="java.lang.String" value="周芷若" />
</bean>
<bean id="fourth" class="io.malajava.ioc.injection.Sinaean">
<constructor-arg name="id">
<bean class="java.lang.Integer">
<constructor-arg type="int" value="1004" />
</bean>
</constructor-arg>
<constructor-arg name="name">
<bean class="java.lang.String">
<constructor-arg type="java.lang.String" value="周芷若" />
</bean>
</constructor-arg>
<constructor-arg name="gender" value="女" />
</bean>
实际应用时可以根据实际情况,灵活应用。
- Testing
public class ConstructorInjectionTest1 {
public static void main(String[] args) {
// 指定 Configuration Metadata
String configLocation = "classpath:io/malajava/ioc/injection/injection-constructor-arg.xml" ;
// 创建 Spring IoC 容器
ApplicationContext iocContainer = new ClassPathXmlApplicationContext( configLocation ) ;
Sinaean s = null ;
// 从容器中获取 bean
s = iocContainer.getBean( "first" , Sinaean.class );
System.out.println( s ); // s.toString()
s = iocContainer.getBean( "second" , Sinaean.class );
System.out.println( s ); // s.toString()
s = iocContainer.getBean( "third" , Sinaean.class );
System.out.println( s ); // s.toString()
s = iocContainer.getBean( "fourth" , Sinaean.class );
System.out.println( s ); // s.toString()
}
}
2.2、使用 c 命名空间 实现参数注入
- Java Bean
参见前面步骤中的 Sinaean 类
- Configuration Metadata
在 Spring 配置文件中通过添加以下内容来使用 c 命名空间:
xmlns:c="http://www.springframework.org/schema/c"
添加后的 <beans >
标签如下所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
设以下内容存在于类路径下的 io/malajava/ioc/injection/injection-constructor-c.xml 文件中。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用 c 命名空间,可以通过 c:_index 形式指定构造方法的参数值 -->
<!-- 因为这里依次指定了 三 个参数,因此会调用 Sinaean(Integer,String,char) -->
<bean id="first"
class="io.malajava.ioc.injection.Sinaean"
c:_0="2001"
c:_1="令狐冲"
c:_2="男" />
<!-- 因为依次指定了 两 个参数,因此会调用 Sinaean(Integer,String) -->
<bean id="second"
class="io.malajava.ioc.injection.Sinaean"
c:_0="2999"
c:_1="东方不败" />
<!-- 使用 c 命名空间,也可以通过 c:argumentName 来指定构造方法的参数值 -->
<!-- 根据参数名称,这里调用的构造方法是 Sinaean( Integer id ,String name ,char gender )-->
<bean id="third"
class="io.malajava.ioc.injection.Sinaean"
c:id="2002"
c:name="仪琳"
c:gender="女" />
<!-- public Integer( int value ) -->
<bean id="fourthId" class="java.lang.Integer">
<constructor-arg type="int" value="1004" />
</bean>
<!-- public String( String source ) -->
<bean id="fourthName" class="java.lang.String">
<constructor-arg type="java.lang.String" value="周芷若" />
</bean>
<!-- 对于任意引用类型(包括包装类和String)的参数来说,都可以通过 -ref 形式来引用其它 bean -->
<bean id="fourth"
class="io.malajava.ioc.injection.Sinaean"
c:id-ref="fourthId"
c:name-ref="fourthName"
c:gender="女" />
</beans>
- Testing
public class ConstructorInjectionTest2 {
public static void main(String[] args) {
// 指定 Configuration Metadata
String configLocation = "classpath:io/malajava/ioc/injection/injection-constructor-c.xml" ;
// 创建 Spring IoC 容器
ApplicationContext iocContainer = new ClassPathXmlApplicationContext( configLocation ) ;
Sinaean s = null ;
// 从容器中获取 bean
s = iocContainer.getBean( "first" , Sinaean.class );
System.out.println( s ); // s.toString()
s = iocContainer.getBean( "second" , Sinaean.class );
System.out.println( s ); // s.toString()
s = iocContainer.getBean( "third" , Sinaean.class );
System.out.println( s ); // s.toString()
s = iocContainer.getBean( "fourth" , Sinaean.class );
System.out.println( s ); // s.toString()
}
}