1、依赖和依赖注入
传统应用程序设计中所说的依赖一般指的是“类与类之间的关系”,那么首先让我们复习一下类之间的关系:
泛化:表示类与类之间的继承关系,表示接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种相识关系,只在某个特定的地方才有关系。
关联:表示接口与接口或类与类之间的依赖关系,表现为“拥有关系”;具体代码可以用实例变量来表示。
组合:属于关联的特殊情况,体现了部分整体的关系,是一种“强拥有关系”;整体与部分拥有相同的生命周期,是一种强关联;
聚合:属于关联的特殊情况,也体现了部分整体的关系,是一种“弱拥有关系”;整体和部分可以有不一样的生命周期。是一种弱关联;
它们关系的强弱顺序:泛化= 实现> 组合> 聚合> 关联> 依赖
spring IoC容器依赖有两层含义:bean依赖容器和容器注入bean的依赖资源。
- bean依赖容器:这里的依赖是指容器负责创建bean,并管理bean的生命周期,正式由于容器来控制创建bean并注入依赖,也就是控制值被反转了,这也正式IoC名字的由来,此处的依赖指的是bean和容器之间的依赖关系。
- 容器注入bean的依赖资源:依赖资源可以是bean、外部文件、常量数据等,在Java中都反应为对象,并且有容器负责组装bean之间的依赖关系,此处的依赖指的是bean之间的关系可以认为是传统类与类之间的关联、组合、聚合关系。
2、为什么要依赖注入
- 动态替换bean依赖对象,程序更灵活:替换bean对象无需更改源文件,直接修改配置文件即可。
- 更好实践面向接口编程,代码更清晰:在bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容器注入依赖实现。
- 更好实践优先使用组合,而不是继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实现对象的组合。
- 增加bean的复用性:依赖于对象组合,bean可复用且复用更简单
- 降低bean之间的耦合:由于我们完全面向接口编程,在代码中没有直接引用bean依赖实现,全部引用接口,而且不会显示创建依赖对象代码。
- 代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码结构更清晰。
如何设计好类结构才是关键,依赖注入只是装配对象的一种手段。上一篇我们已经了解了bean依赖容器,spring IoC容器注入依赖资源主要有以下三种形式:
构造器注入:就是容器实例化bean时注入那些依赖,通过在bean定义中指定构造器参数注入依赖,包括实例工厂方法参数注入依赖。
setter注入:通过setter方法注入依赖。
方法注入:通过配置方式替换掉bean方法,也就是通过配置改变bean方法功能。
3、依赖注入具体配置
3.1 构造器注入:
3.1.1 使用构造器注入通过配置构造器参数实现,构造器参数就是依赖。除了构造器方式还有实例工厂,静态工厂可以进行构造器注入。
构造器注入可以根据参数的索引注入,参数的类型注入或参数名注入(参数名注入有限制:编译程序时打开调试模式或在构造器上使用@ConstructorProperties注解指定参数名)
1 public class HelloImpl3 implements HelloApi { 2 private String message; 3 private int index; 4 //@java.beans.ConstructorProperties({"message", "index"}) 5 public HelloImpl3(String message, int index) { 6 this.message = message; 7 this.index = index; 8 } 9 @Override 10 public void sayHello() { 11 System.out.println(index + ":" + message); 12 }
1 <!-- 通过构造器参数索引方式依赖注入 --> 2 <bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3"> 3 <constructor-arg index="0" value="Hello World!"/> 4 <constructor-arg index="1" value="1"/> 5 </bean> 6 <!-- 通过构造器参数类型方式依赖注入 --> 7 <bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3"> 8 <constructor-arg type="java.lang.String" value="Hello World!"/> 9 <constructor-arg type="int" value="2"/> 10 </bean> 11 <!-- 通过构造器参数名称方式依赖注入 --> 12 <bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3"> 13 <constructor-arg name="message" value="Hello World!"/> 14 <constructor-arg name="index" value="3"/> 15 </bean>
1 @Test 2 public void testConstructorDependencyInjectTest() { 3 BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/constructorDependencyInject.xml"); 4 //获取根据参数索引依赖注入的Bean 5 HelloApi byIndex = beanFactory.getBean("byIndex", HelloApi.class); 6 byIndex.sayHello(); 7 //获取根据参数类型依赖注入的Bean 8 HelloApi byType = beanFactory.getBean("byType", HelloApi.class); 9 byType.sayHello(); 10 //获取根据参数名字依赖注入的Bean 11 HelloApi byName = beanFactory.getBean("byName", HelloApi.class); 12 byName.sayHello(); 13 }
3.1.2 静态工厂方法注入和实例工厂方法注入
1 <!--静态工厂方法--> 2 <bean id="byIndex" class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance"> 3 <constructor-arg index="0" value="Hello World!"/> 4 <constructor-arg index="1" value="1"/> 5 </bean> 6 <bean id="byType" class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance"> 7 <constructor-arg type="java.lang.String" value="Hello World!"/> 8 <constructor-arg type="int" value="2"/> 9 </bean> 10 <bean id="byName" class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance"> 11 <constructor-arg name="message" value="Hello World!"/> 12 <constructor-arg name="index" value="3"/> 13 </bean> 24 <!--实例工厂方法--> 25 <bean id="instanceFactory" class="cn.javass.spring.chapter3.DependencyInjectByInstanceFactory"/> 26 <bean id="byIndex" factory-bean="instanceFactory" factory-method="newInstance"> 27 <constructor-arg index="0" value="Hello World!"/> 28 <constructor-arg index="1" value="1"/> 29 </bean> 30 <bean id="byType" factory-bean="instanceFactory" factory-method="newInstance"> 31 <constructor-arg type="java.lang.String" value="Hello World!"/> 32 <constructor-arg type="int" value="2"/> 33 </bean> 34 <bean id="byName" factory-bean="instanceFactory" factory-method="newInstance"> 35 <constructor-arg name="message" value="Hello World!"/> 36 <constructor-arg name="index" value="3"/> 37 </bean>
1 //静态工厂类 2 package cn.javass.spring.chapter3; 3 import cn.javass.spring.chapter2.helloworld.HelloApi; 4 public class DependencyInjectByStaticFactory { 5 public static HelloApi newInstance(String message, int index) { 6 return new HelloImpl3(message, index); 7 } 8 } 9 //实例工厂类 10 package cn.javass.spring.chapter3; 11 import cn.javass.spring.chapter2.helloworld.HelloApi; 12 public class DependencyInjectByInstanceFactory { 13 public HelloApi newInstance(String message, int index) { 14 return new HelloImpl3(message, index); 15 } 16 }
因为参数名需做额外配置,因此不建议使用根据参数名进行构造器注入
3.2 setter注入
setter注入,是通过构造器、静态工厂、实例工厂实例化好bean中,再调用bean类的setter方法注入依赖。
1 <!-- 通过setter方式进行依赖注入 --> 2 <bean id="bean" class="cn.javass.spring.chapter3.HelloImpl4"> 3 <property name="message" value="Hello World!"/> 4 <property name="index"> 5 <value>1</value> 6 </property> 7 </bean>
1 package cn.javass.spring.chapter3; 2 import cn.javass.spring.chapter2.helloworld.HelloApi; 3 public class HelloImpl4 implements HelloApi { 4 private String message; 5 private int index; 6 //setter方法 7 public void setMessage(String message) { 8 this.message = message; 9 } 10 public void setIndex(int index) { 11 this.index = index; 12 } 13 @Override 14 public void sayHello() { 15 System.out.println(index + ":" + message); 16 } 17 } 18 19 @Test 20 public void testSetterDependencyInject() { 21 BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/setterDependencyInject.xml"); 22 HelloApi bean = beanFactory.getBean("bean", HelloApi.class); 23 bean.sayHello(); 24 }
JavaBean本质就是一个pojo,具体有如下限制:
该类必须有公共的无惨构造器
属性为private访问级别
属性必要时通过一组setter和getter方法来访问
3.3 常量注入
注入常量是依赖注入中最简单的,配置方式如下:
1 <property name="message" value="Hello World!"/> 2 或 3 <property name="index"><value>1</value></property>
以上两种方式都可以,从配置来看第一种更简洁,此处的value中指定的全是字符串,由spring容器将此字符串转换成属性所需的类型,如果转换出错则抛出相应的异常。spring容器目前能对各种基本类型把配置的字符串参数转换为所需的类型。
1 //测试类 2 public class BooleanTestBean { 3 private boolean success; 4 public void setSuccess(boolean success) { 5 this.success = success; 6 } 7 public boolean isSuccess() { 8 return success; 9 } 10 }
1 <!-- boolean参数值可以用on/off --> 2 <bean id="bean2" class="cn.javass.spring.chapter3.bean.BooleanTestBean"> 3 <property name="success" value="on"/> 4 </bean> 5 <!-- boolean参数值可以用yes/no --> 6 <bean id="bean3" class="cn.javass.spring.chapter3.bean.BooleanTestBean"> 7 <property name="success" value="yes"/> 8 </bean> 9 <!-- boolean参数值可以用1/0 --> 10 <bean id="bean4" class="cn.javass.spring.chapter3.bean.BooleanTestBean"> 11 <property name="success" value="1"/> 12 </bean>
3.4 注入集合、数组和字典
spring IoC容器不仅能注入简单的数据类型,还能注入集合(Collection、Set、List)类型、数组类型、字典数据类型、Properties类型。
3.4.1 注入集合类型:
- List类型:需要使用list标签注入,其具体配置如下:
1 <bean id="listBean" class="cn.javass.spring.chapter3.bean.ListTestBean"> 2 <property name="values"> 3 <list> 4 <value>1</value> 5 <value>2</value> 6 <value>3</value> 7 </list> 8 </property> 9 </bean>
1 package cn.javass.spring.chapter3.bean; 2 import java.util.List; 3 public class ListTestBean { 4 private List<String> values; 5 public List<String> getValues() { 6 return values; 7 } 8 public void setValues(List<String> values) { 9 this.values = values; 10 } 11 } 12 @Test 13 public void testListInject() { 14 BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/listInject.xml"); 15 ListTestBean listBean = beanFactory.getBean("listBean", ListTestBean.class); 16 System.out.println(listBean.getValues().size()); 17 Assert.assertEquals(3, listBean.getValues().size()); 18 }
- set类型:需要使用set标签来配置注入,其配置参数含义与list相同
1 <bean id="setBean" class="cn.javass.spring.chapter3.bean.SetTestBean"> 2 <property name="values"> 3 <set> 4 <value>1</value> 5 <value>2</value> 6 <value>3</value> 7 </set> 8 </property> 9 </bean>
1 package cn.javass.spring.chapter3.bean; 2 import java.util.Set; 3 public class SetTestBean { 4 private Set<String> values; 5 public void setValues(Set<String> values) { 6 this.values = values; 7 } 8 public Set<String> getValues() { 9 return values; 10 } 11 }
- Collection类型:由于Collection类型是List和Set类型的基类型,所以使用list和set标签都可以进行注入,配置方式和以上配置方式相同。
3.4.2 注入数组类型
需要使用array标签来配置注入,其中标签属性“value-type”和“merge”与list标签含义完全相同。具体配置如下:
3.4.3 注入字典类型:
字典类型是包含键值对数据的数据结构,需要使用map、entry标签来配置注入,其属性key-type和value-type分别指定键值数据类型,其含义同list标签相同。使用key、value字标签分别指定键值数据。具体配置如下:
3.4.4 Properties注入:
spring能注入java.util.Properties类型数据,需要使用props标签来配置注入,键值类型必须是String,不能变。字标签<prop key=“键”>值<prop>来指定键值对,具体配置如下: