6月11日,晴。“夏条绿已密,朱萼缀明鲜。炎炎日正午,灼灼火俱燃。”
IT人习惯把具体的事物加工成的形状一致的类,正是这样的一致,加上合适的规范,才能彰显对象筋道的牙感和bean清香的味道。Spring比谁都清楚OO的奥妙,让组件之间的依赖关系由容器在运行时期决定,称作依赖注入(Dependency Injection)。
下面用一通俗的例子,一探依赖注入奥妙。
设计模式中的一个原则:针对接口编程,不要针对实现编程。
一、设计两个接口:
(1)奶制品接口—MilkProductInterface.java
package edu.eurasia.IOC; public interface MilkProductInterface { public void drinkMilk(); public void eatCheese(); }(2)奶制品接口的实现类—Milk.java
package edu.eurasia.IOC; public class Milk implements MilkProductInterface { public void drinkMilk() { System.out.println("新鲜的牛奶真好喝!"); } public void eatCheese() { System.out.println("只有鲜奶,无奶酪o(╯□╰)o"); } }(3)奶制品接口的实现类—Cheese.java
package edu.eurasia.IOC; public class Cheese implements MilkProductInterface { public void drinkMilk() { System.out.println("只有奶酪,无鲜奶o(╯□╰)o"); } public void eatCheese() { System.out.println("发酵后的美味奶酪真好吃!"); } }
(4)人的接口—PersonInterface.java
package edu.eurasia.IOC; public interface PersonInterface { public void drink(); public void eat(); }(5)人接口的实现类—Person.java
正常情况下,人要喝牛奶,吃奶酪,这样写就ok了。
package edu.eurasia.IOC; public class Person implements PersonInterface { public void drink() { MilkProductInterface milk = new Milk(); milk.drinkMilk(); } public void eat() { MilkProductInterface cheese = new Cheese(); cheese.eatCheese(); } }(6)测试类—TestIOC.java
package edu.eurasia.IOC; import org.junit.Test; public class TestIOC { @Test public void eatdrink(){ PersonInterface p = new Person(); p.drink(); p.eat(); } }结果输出:新鲜的牛奶真好喝!
发酵后的美味奶酪真好吃!
上述代码,很多初学者看不出来有什么问题。但是,类是一样的类,一“new”见高下。使用“new”的时候,就已经在实例化一个具体类了,这就是一种实现,只要有具体类的出现,就会导致代码更缺乏弹性。重温设计模式中的一个原则:针对接口编程,不要针对实现编程。
二、依赖注入(Set):
(1)修改人接口的实现类—Person.java
package edu.eurasia.IOC; public class Person implements PersonInterface { private MilkProductInterface milkX; public void setMilkX(MilkProductInterface milkX) { this.milkX = milkX; } @Override public void drink() { milkX.drinkMilk(); } @Override public void eat() { milkX.eatCheese(); } }
声明一个MilkProductInterface接口变量milkX,这个milkX代表任意一种具体的奶制品,可能是牛奶milk,也可能是奶酪cheese,还有可能是奶茶milky tea...,总之,我们可以动态的在运行时改变它的具体属性。怎么改?别着急,你发现了milkX的set方法,哈哈,这就是大名鼎鼎的Set注入(setter injection)。
看到了注入,那么依赖(Dependency)在哪里?
我们对着雾霾喊: 依赖,我们的好DI, 你在哪里呵,你在哪里? 你可知道,我们想念你...
雾霾翻滚: 它就在配置文件里...
(2)编写配置文件—applicationContext.xml,放在src下面。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="milk" class="edu.eurasia.IOC.Milk"></bean> <bean id="cheese" class="edu.eurasia.IOC.Cheese"></bean> <bean id="person" class="edu.eurasia.IOC.Person"> <property name="milkX" ref="milk"></property> </bean> </beans>
配置文件是Spring依赖注入的关键:
首先,定义两个bean,第一个bean的id是milk, class指定该bean实例的实现类;第二个bean的id是cheese,class同样指定该bean实例的实现类。这两个bean是为下面的Bean提供引用注入的。
其次,再定义一个bean,bean的id是person, class指定该bean实例的实现类。property元素用来指定需要容器注入的属性,属性名milkX要与Person.java中的属性一致,ref引用要注入的值即上面定义的任一个id。不同的id,就是注入的不同值,因此Person类必须拥有setMilkX方法。说了半天,灵活性就在这里体现了。
从配置文件中,可以看到Spring管理bean的灵巧性。bean与bean之间的依赖关系放在配置文件里组织,而不是写在代码里。通过配置文件的
指定,Spring能精确地为每个bean注入属性。因此,配置文件里的bean的class元素,不能仅仅是接口,而必须是真正的实现类。
Spring会自动接管每个bean定义里的property元素定义。Spring会在执行无参数的构造器后、创建默认的bean实例后,调用对应的setter方法为注入属性值。property定义的属性值将不再由该bean来主动创建、管理,而改为被动接收Spring的注入。
每个bean的id属性是该bean的惟一标识,程序通过id属性访问bean,bean与bean的依赖关系也通过id属性完成。
注:
1、spring配置文件中Bean中的id和name的区别
id:鼓励使用ID属性来标识一个Bean, 不能重名,可以被DTD验证,不能以数字,符号打头,不能有空格;
name: 可以重名, 后面的覆盖前面。
2、JavaBean关于属性命名的特殊规范Spring配置文件中<property>元素所指定的属性名和Bean实现类的Setter方法满足Sun JavaBean的属性命名规范:xxx的属性对应setXxx()方法。
一般情况下,Java的属性变量名都以小写字母开头,如maxSpeed、brand等。但也存在特殊的情况,考虑到一些特殊意义的大写英文缩略词(如USA、XML等),JavaBean也允许大写字母开头的属性变量名,但必须满足“变量的前两个字母要么全部大写,要么全部小写”的要求,如brand、ICCard是合法的,而iC、iCCard是非法的。
(3)改写测试类—TestIOC.javapackage edu.eurasia.IOC; import org.junit.Test; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestIOC { @Test public void eatdrink(){ AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Person p = (Person) context.getBean("person"); p.drink(); p.eat(); } }
程序实例化Spring的上下文,通过Person bean的id来获取bean实例,从而实现面向接口编程。
结果输出:新鲜的牛奶真好喝!
只有鲜奶,无奶酪o(╯□╰)o
更改applicationContext.xml里面的ref="cheese"。
此时再次执行程序,将得到如下结果:
只有奶酪,无鲜奶o(╯□╰)o
发酵后的美味奶酪真好吃!
采用setter方法为目标bean注入属性的方式,称为设值注入。此外,还有一种叫构造注入,所谓构造注入,指通过构造函数来完成依赖关系的设定,而不是通过setter方法。
三、依赖注入(构造):
(1)修改人接口的实现类—Person.java
package edu.eurasia.IOC; public class Person implements PersonInterface { private MilkProductInterface milkX; public Person(MilkProductInterface milkX) { super(); this.milkX = milkX; } @Override public void drink() { milkX.drinkMilk(); } @Override public void eat() { milkX.eatCheese(); } }(2)修改配置文件—applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="milk" class="edu.eurasia.IOC.Milk"></bean> <bean id="cheese" class="edu.eurasia.IOC.Cheese"></bean> <bean id="person" class="edu.eurasia.IOC.Person"> <!-- <property name="milkX" ref="milk"></property> --> <constructor-arg ref="milk"></constructor-arg> </bean> </beans>执行效果与使用设值注入时的执行效果完全一样。区别在于:创建Person实例中属性的时机不同—设值注入是现创建一个默认的bean实例,然后调用对应的构造方法注入依赖关系。而构造注入则在创建bean实例时,已经完成了依赖关系。