接下来学习Spring相关知识IOC和DI,即控制反转和依赖注入。
什么是IOC和DI
IOC(Inversion of Control 控制反转),即对象之间的依赖关系由Spring容器来建立。
DI(Dependency Injection 依赖注入),Spring容器可以通过调用set方法或者构造器来建立对象之间的依赖关系。
简单来说,对象之间关系由最初人来建立,变成交给Spring容器来建立,这样造成控制角色的转变,人只需要等着就可以了,容器给什么对象我就使用什么对象。为了实现IOC,DI是必不可少的,其可以实现对象动态注入,根据接口实现类的不同可以调用不同的对象,这样可以大大提高代码可维护性。
DI依赖注入对象
依赖注入有两种方式,即set方法注入和构造器注入,一般使用set方法注入。
set方法注入
(1)需要注入对象的类中,添加对应对象set方法,属性一般使用接口类型,这样可以实现动态注入
接口

1 package com.boe; 2 /** 3 * 员工接口 4 */ 5 public interface Employee { 6 7 //接口方法,就是工作 8 public void work(); 9 10 }
实现类

1 package com.boe; 2 3 public class Engineer implements Employee{ 4 5 public Engineer() { 6 System.out.println("创建了一个新的Engineer"); 7 } 8 9 public void work() { 10 System.out.println("call厂商,写报告,被怼"); 11 } 12 13 }

1 package com.boe; 2 3 public class Leader implements Employee{ 4 5 public Leader() { 6 System.out.println("新创建了一个Leader"); 7 } 8 9 public void work() { 10 System.out.println("开会,坐办公室,专职怼下属"); 11 } 12 13 }
测试类中添加要注入的属性emp,类型为接口Employee。
1 package com.boe; 2 3 public class Company { 4 //属性为员工 5 private Employee emp; 6 7 //添加set,get方法 8 public Employee getEmp() { 9 return emp; 10 } 11 12 public void setEmp(Employee emp) { 13 this.emp = emp; 14 } 15 16 //构造方法 17 public Company() { 18 System.out.println("创建了一个新的公司"); 19 } 20 21 //调用员工工作的方法 22 public void runCompany() { 23 //根据注入的对象不同,执行得到不同的结果 24 emp.work(); 25 } 26 }
(2)在配置文件中,使用<property name="" ref="">来配置属性,name即类中注入对象对应的属性名,ref为注入对象的bean id

1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" 4 xmlns:jee="http://www.springframework.org /schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" 5 xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 8 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd 9 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd 10 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd 11 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd 12 http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd 13 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> 14 15 <!--Set方式注入 --> 16 <bean id="engineer" class="com.boe.Engineer"></bean> 17 <bean id="leader" class="com.boe.Leader"></bean> 18 <!-- 开始注入 --> 19 <bean id="company" class="com.boe.Company"> 20 <property name="emp" ref="leader"></property> 21 </bean> 22 23 </beans>
使用junit测试

1 import org.junit.Test; 2 import org.springframework.context.ApplicationContext; 3 import org.springframework.context.support.ClassPathXmlApplicationContext; 4 5 import com.boe.Company; 6 7 public class TestDI { 8 9 //测试set方法注入 10 @Test 11 public void testSet() { 12 String path="myIOC.xml"; 13 ApplicationContext ac=new ClassPathXmlApplicationContext(path); 14 Company company=ac.getBean("company",Company.class); 15 company.runCompany(); 16 } 17 18 }
测试结果如下,当property中ref指向的bean id发生变化时,对应就有不同的执行效果,实现了对象的依赖注入,配置什么ref就有什么对象注入进来。
构造器注入
(1)需要注入对象的类中添加带有参数的构造器,参考下面代码。
注入类,宅男类

1 package com.boe; 2 /** 3 * 宅男类 4 * @author yangchaolin 5 */ 6 public class Otaku { 7 //构造方法 8 public Otaku() { 9 System.out.println("创建一个新的宅男"); 10 } 11 }
被注入类,女神类

1 package com.boe; 2 /** 3 * 女神类 4 * @author yangchaolin 5 */ 6 public class Gakki { 7 //属性 8 private Otaku otaku; 9 //带参数的构造方法 10 public Gakki(Otaku otaku) { 11 System.out.println("创建一个女神,有宅男"); 12 this.otaku = otaku; 13 } 14 //默认构造方法 15 public Gakki() { 16 System.out.println("创建一个女神,无宅男"); 17 } 18 //toString方法 19 @Override 20 public String toString() { 21 return "Gakki [otaku=" + otaku + "]"; 22 } 23 }
(2)在配置文件中,使用<constructor-arg index="" ref="" />元素来配置,其中index代表构造方法中参数的位置,位置从0开始,ref为bean id。

1 <!-- 构造器方式注入 --> 2 <bean id="otaku" class="com.boe.Otaku"></bean> 3 <bean id="gakki" class="com.boe.Gakki"> 4 <constructor-arg index="0" ref="otaku"></constructor-arg> 5 </bean>
junit测试类

1 //测试构造器方法注入 2 @Test 3 public void testConstructor() { 4 String path="myIOC.xml"; 5 ApplicationContext ac=new ClassPathXmlApplicationContext(path); 6 Gakki gakki=ac.getBean("gakki",Gakki.class); 7 System.out.println(gakki); 8 }
测试结果
自动装配
默认情况下容器不会自动装配对象,只有依据某些规则自动帮我们建立对象之间的依赖关系,需设置autowire属性,有三种类型。
(1)byName:表示依据属性名查找对应的bean,即通过属性名,查找xml配置文件中是否有bean id等于这个属性名的,有就将bean注入,没有就不注入,参考将人注入到国家。
人对应的类

1 package com.boe; 2 3 public class People { 4 //默认构造方法 5 public People() { 6 System.out.println("创建一个人"); 7 } 8 }
国家对应的类

1 package com.boe; 2 3 public class Country { 4 //属性 5 private People people; 6 //get set方法 7 public People getPeople() { 8 return people; 9 } 10 11 public void setPeople(People people) { 12 this.people = people; 13 } 14 //构造方法 15 public Country() { 16 System.out.println("创建一个新的国家"); 17 } 18 //重写toString方法 19 @Override 20 public String toString() { 21 return "Country [people=" + people + "]"; 22 } 23 }
xml中配置信息
1 <!-- byName --> 2 <bean id="people" class="com.boe.People"></bean><!-- 属性名为people,bean id也为people --> 3 <bean id="country" class="com.boe.Country" autowire="byName"></bean>
junit测试

1 //测试使用自动装配-byName&byType 2 @Test 3 public void testAutowireByName() { 4 String path="myIOC.xml"; 5 ApplicationContext ac=new ClassPathXmlApplicationContext(path); 6 Country country=ac.getBean("country",Country.class); 7 System.out.println(country); 8 }
当bean id等于属性名people时,autowire="byName"是可以将People类实例对象注入的,否则不会注入。
修改bean id为其他名字,不会注入。
(2)byType:依据属性的类型来查找对应的bean,即查看bean的class属性是否匹配,如果一致就注入。
在上述修改基础上,将autowire属性值设置为byType
1 <!-- byType --> 2 <bean id="people1" class="com.boe.People"></bean> 3 <bean id="country" class="com.boe.Country" autowire="byType"></bean>
即使bean id不匹配,class属性是匹配的,因此也可以注入。
但是byType属性只能有一个bean与之对应,如果有多个class类型一样的bean,这种自动装配会有致命错误发生。
1 <!-- byType --> 2 <bean id="people1" class="com.boe.People"></bean> 3 <bean id="people2" class="com.boe.People"></bean> 4 <bean id="country" class="com.boe.Country" autowire="byType"></bean>
(3)constructor:与byType类似,只不过使用构造器来注入,将Country换成Province测试。
省对应的类

1 package com.boe; 2 3 public class Province { 4 //属性 5 private People people; 6 //带参数构造方法 7 public Province(People people) { 8 System.out.println("新建一个省,有人民"); 9 this.people = people; 10 } 11 //默认构造方法 12 public Province() { 13 System.out.println("新建一个省,没有人民"); 14 } 15 //重写toString 16 @Override 17 public String toString() { 18 return "Province [people=" + people + "]"; 19 } 20 }
xml中配置信息
1 <!-- constructor --> 2 <bean id="p" class="com.boe.People"></bean> 3 <!-- <bean id="p1" class="com.boe.People"></bean> --> 4 <bean id="province" class="com.boe.Province" autowire="constructor"></bean>
junit代码

1 //测试使用自动装配-constructor 2 @Test 3 public void testAutowireConstructor() { 4 String path="myIOC.xml"; 5 ApplicationContext ac=new ClassPathXmlApplicationContext(path); 6 Province province=ac.getBean("province",Province.class); 7 System.out.println(province); 8 }
测试结果,可以看出跟byType类似,如果有多个同样类型的bean匹配,则不会注入且不报错,这点跟byType有点不同。
DI依赖注入值
Spring容器可以帮我们注入基本数据类型的值,可以注入的类型有:char、int、double等,此外还可以注入String。
测试类valueBean

1 package value; 2 3 import java.util.List; 4 import java.util.Map; 5 import java.util.Properties; 6 import java.util.Set; 7 8 public class valueBean { 9 //注入基本数据类型示例 10 private String name; 11 private int age; 12 //注入集合类型示例 13 private List<String> interests;//List集合中的值可以重复 14 private Set<String> city;//Set集合中的值不可以重复 15 private Map<String,Double> score;//Map集合的注入 16 private Properties db; 17 18 19 public Properties getDb() { 20 return db; 21 } 22 public void setDb(Properties db) { 23 this.db = db; 24 } 25 public Map<String, Double> getScore() { 26 return score; 27 } 28 public void setScore(Map<String, Double> score) { 29 this.score = score; 30 } 31 public Set<String> getCity() { 32 return city; 33 } 34 public void setCity(Set<String> city) { 35 this.city = city; 36 } 37 public List<String> getInterests() { 38 return interests; 39 } 40 public void setInterests(List<String> interests) { 41 this.interests = interests; 42 } 43 public String getName() { 44 return name; 45 } 46 public void setName(String name) { 47 this.name = name; 48 } 49 public int getAge() { 50 return age; 51 } 52 public void setAge(int age) { 53 this.age = age; 54 } 55 //无参数构造器 56 public valueBean() { 57 System.out.println("valueBean()"); 58 } 59 @Override 60 public String toString() { 61 return "valueBean [name=" + name + ", age=" + age +" "+"interests=" + interests + " "+"city=" + city + " "+"score=" 62 + score + " "+"db=" + db + "]"; 63 } 64 }
注入基本数值xml配置
1 <!-- 注入基本类型数据--> 2 <bean id="vb1" class="value.valueBean"> 3 <!-- 注入基本数据类型--> 4 <!-- 调用setName方法和setAge方法,将关关和22传入到对象中 --> 5 <property name="name" value="关关"></property> 6 <property name="age" value="22"></property> 7 </bean>
junit测试

1 @Test 2 /** 3 * 测试注入-直接注入 4 */ 5 public void test6() { 6 //写配置路径 7 String config="value.xml"; 8 //启动Spring容器 9 ApplicationContext ac=new ClassPathXmlApplicationContext(config); 10 valueBean valueBean=ac.getBean("vb1", valueBean.class); 11 System.out.println(valueBean); 12 }
测试结果
容器可以注入集合类型的值(List,Set,Map,Properties),有如下两种方式
(1)直接注入,在xml中写好
1 <bean id="vb1" class="value.valueBean"> 2 <!-- 注入集合类型数据 --> 3 <!-- 调用setInterests方法,将集合传入到对象中,list代表集合,其中的值就是集合中的元素 --> 4 <property name="interests"> 5 <list> 6 <value>玩电脑</value> 7 <value>玩诛仙</value> 8 <value>玩街头篮球</value> 9 </list> 10 </property> 11 <property name="city"> 12 <set> 13 <value>长沙</value> 14 <value>佛山</value> 15 <value>北京</value> 16 <value>北京</value><!-- 写两个北京,set只会显示一个,不允许重复的值 --> 17 </set> 18 </property> 19 20 <!-- Map集合的注入 --> 21 <property name="score"> 22 <map> 23 <entry key="物理" value="59"></entry> 24 <entry key="工程力学" value="49"></entry> 25 <entry key="材料科学与工程" value="58"></entry> 26 </map> 27 </property> 28 29 <!-- Properties集合的注入 --> 30 <property name="db"> 31 <props> 32 <prop key="username">yangchaolin</prop> 33 <prop key="password">1234</prop> 34 </props> 35 </property> 36 </bean>
注入结果
(2)采用引用的方式注入
step1 将集合类型的值先配置成一个bean
step2 再将这个bean注入到对应的bean里面
测试类ExampleBean

1 package value; 2 3 import java.util.List; 4 import java.util.Map; 5 import java.util.Properties; 6 import java.util.Set; 7 /** 8 * 采用另外一种注入方式注入集合,即引用的方式,将集合先配置成bean,然后注入到ExampleBean里面来 9 * @author yangchaolin 10 */ 11 public class ExampleBean { 12 13 private List<String> interests; 14 private Set<String> city; 15 private Map<String,Double> score; 16 private Properties db; 17 //无参数构造器 18 public ExampleBean() { 19 System.out.println("ExampleBean()"); 20 } 21 public List<String> getInterests() { 22 return interests; 23 } 24 public void setInterests(List<String> interests) { 25 this.interests = interests; 26 } 27 public Set<String> getCity() { 28 return city; 29 } 30 public void setCity(Set<String> city) { 31 this.city = city; 32 } 33 public Map<String, Double> getScore() { 34 return score; 35 } 36 public void setScore(Map<String, Double> score) { 37 this.score = score; 38 } 39 public Properties getDb() { 40 return db; 41 } 42 public void setDb(Properties db) { 43 this.db = db; 44 } 45 @Override 46 public String toString() { 47 return "ExampleBean [interests=" + interests + " "+"city=" + city + " "+"score=" + score + " "+"db=" + db + "]"; 48 } 49 }
xml配置
1 <!-- 将集合类型的值配置成一个bean,util:list其实就是一个bean,因此可以作为ref来注入--> 2 <!-- util:list中的util是命名空间(namespace),是为了区分同名的元素而设定的前缀,如list是上面spring框架下的list,不是java.util下的list --> 3 <util:list id="interestBean"> 4 <value>喝酒</value> 5 <value>玩游戏</value> 6 <value>逃课</value> 7 <value>挂科</value> 8 </util:list> 9 <util:set id="cityBean"> 10 <value>长沙</value> 11 <value>佛山</value> 12 <value>中山</value> 13 <value>北京</value> 14 </util:set> 15 <util:map id="scoreBean"> 16 <entry key="大学数学" value="79"></entry> 17 <entry key="C语言" value="76"></entry> 18 <entry key="大学计算机基础" value="75"></entry> 19 </util:map> 20 <util:properties id="dbBean"> 21 <prop key="物理化学">59</prop> 22 <prop key="弹性力学">55</prop> 23 <prop key="大学英语">80</prop> 24 </util:properties> 25 <!-- 采用引用的方式,注入集合类型的值 --> 26 <bean id="eb1" class="value.ExampleBean"> 27 <property name="interests" ref="interestBean"></property> 28 <property name="city" ref="cityBean"></property> 29 <property name="score" ref="scoreBean"></property> 30 <property name="db" ref="dbBean"></property> 31 </bean>
junit测试

1 @Test 2 /** 3 * 测试注入-使用引用注入 4 */ 5 public void test7() { 6 //写配置路径 7 String config="value.xml"; 8 //启动Spring容器 9 ApplicationContext ac=new ClassPathXmlApplicationContext(config); 10 ExampleBean exampleBean=ac.getBean("eb1", ExampleBean.class); 11 System.out.println(exampleBean); 12 }
测试结果
结论
(1)IOC是目标,DI是手段
(2)DI注入有set方法注入和构造器方法注入,一般使用前者
(3)自动装配有三种类型,byName、byType和constructor
(4)DI注入除了可以注入对象外,还可以注入数据,如基本数据类型和集合,其中集合注入可以直接注入,也可以将集合配置成一个bean后注入