每个基本的Java应用有一些对象在一起工作,最终展现给终端用户的是一个工作的应用。当写一个复杂的Java应用时,应该使类之间尽量解耦以增加类的可用性并独立于其他的类来做单元测试。依赖注入帮我我们装配这些类并保持他们各自的独立性。
考虑你有一个应用有一个文本编辑组建并且你想提供拼写检查。你标准的代码会看起来像下面这个样子:
public class TextEditor {
private SpellChecker spellChecker;
public TextEditor() {
spellChecker = new SpellChecker();
}
}
这里我们创建了一个TextEditor对SpellChecker的依赖。在控制反转的情景我们会像这样写:
public class TextEditor {
private SpellChecker spellChecker;
public TextEditor(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
}
}
这里TextEditor不用担心SpellChecker的实现。这个SpellChecker将会独立的执行,并在TextEditor初始化时实例化并且整个过程有Spring框架控制。
这里,你已经完全从TextEditor中移除控制到别的地方(即XML配置文件)这个依赖(即SpellChecker类)已经被类构造函数注入。因此控制流已经被反转通过依赖注入因为你已经有效的借助了其他系统实现了分离。
第二种依赖注入是通过TextEditor类的Setter方法我们会创建一个SpellChecker实例并且这个实例会通过setter方法初始化TextEditor的属性。
DI主要有两个变体在接下来的两个子章节中我们会涵盖他们用示例。
序号 | 依赖注入类型&描述 |
1 | 基于构造函数的依赖注入被完成,当容器调用一个多个参数的构造函数,每一个参数表示对其他类的依赖。 |
2 | 基于setter方法的依赖注入被完成,通过容器调用bean的setter方法,在调用无参构造函数或无参静态工厂方法实例化bean之后。 |
你可以混合两种,基于构造的和基于setter方法的依赖注入但是有一个好的经验使用构造参数用于强制的依赖,使用setter依赖用于可选的依赖。
通过DI的原则使代码更清晰,解耦更加有效,当类需要依赖注入时。对象不关心他的依赖信息,不知道他们依赖对象的地方或者类,这些都是Spring框架的工作。
基于构造函数的依赖注入
完成基于构造函数的DI是通过容器调用一个类的构造函数通过一系列的参数,每个参数表示一个对其他对象的依赖。
示例
如下的实例展示了一个TextEditor类只使用构造函数依赖注入。
让我们使用Eclipse IDE按如下的步骤创建这个应用:
步骤 | 描述 |
1 | 创建一个SpringExample的项目并在src目录下创建com.tutorialspoint包。 |
2 | 在Add External JARs选项卡中添加Spring库如在Spring Hello World章节中所讲。 |
3 | 在com.tutorialspoint包下创建TextEditor、SpellChecker和MainApp类。 |
4 | 在src目录下创建bean的配置文件Beans.xml |
5 | 最后一步在Java类中和Bean配置文件中添加内容,并运行应用。 |
如下是TextEditor的源代码:
package com.tutorialspoint;
public class TextEditor {
private SpellChecker spellChecker;
public TextEditor (SpellChecker spellChecker) {
System.out.println ("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling ();
}
}
如下另一个依赖类SpellChecker的源代码:
package com.tutorialspoint;
public class SpellChecker {
public SpellChecker() {
System.out.println("Inside SpellChecker constructor.");
}
public void checkSpelling() {
System.out.println("Inside checkSpelling.");
}
}
如下是MainApp的源代码:
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}
}
如下是基于构造函数的依赖注入的配置文件Beans.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-3.0.xsd">
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker"/>
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<constructor-arg ref="spellChecker" />
</bean>
</beans>
一旦你完成了上述源代码和配置文件,运行应用。如果一切顺利,会输出如下内容:
Inside SpellChecker constructor.
Inside TextEditor constructor.
Inside checkSpelling.
构造函数参数解析
有一个模糊的地方当这个构造函数有多个参数时。为了解决这个模糊的地方,构造函数参数的顺序在bean定义的地方要和构造函数的参数顺序一致。考虑如下类:
package x.y;
public class Foo {
public Foo (Bar bar, Baz baz) {
// ...
}
如下工作正常:
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
让我们测试另一个案例当我们传递不同类型的参数给构造函数时,考虑如下类:
package x.y;
public class Foo {
public Foo (int year, String name) {
// ...
}
}
这个容器可以通过利用简单类型匹配当你明确的指明构造函数参数使用的类型属性,如下:
<beans>
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="2001" />
<constructor-arg type="java.lang.String" value="Zara" />
</bean>
</beans>
最后传递给构造函数参数最好的方式是,使用index属性来明确指明构造函数参数的顺序。这里索引起始值是0。如例:
<beans>
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="2001" />
<constructor-arg index="1" value="Zara" />
</bean>
</beans>
最后一个注意点,当你传递一个对象的引用,你需要使用<constructor-arg>标签的“ref”属性,如果你直接传递一个值类型你需要使用“value”属性,如上所示。
基于setter方法的依赖注入
基于setter的DI通过容器在你的bean调用无参构造函数或者无参静态工厂方法时实例化你的bean之后调用setter方法完成。
示例
如下的示例展示了一个类TextEditor值使用基于setter方法的依赖注入。
让我们按如下的步骤在Eclipse IDE中创建Spring应用。
步骤 | 描述 |
1 | 创建一个SpringExample的项目并在src目录下创建com.tutorialspoint包。 |
2 | 在Add External JARs选项卡中添加Spring库如在Spring Hello World章节中所讲。 |
3 | 在com.tutorialspoint包下创建TextEditor、SpellChecker和MainApp类。 |
4 | 在src目录下创建bean的配置文件Beans.xml |
5 | 最后一步在Java类中和Bean配置文件中添加内容,并运行应用。 |
如下是TextEditor的源代码:
package com.tutorialspoint;
public class TextEditor {
private SpellChecker spellChecker;
// a setter method to inject the dependency.
public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker.");
this.spellChecker = spellChecker;
}
// a getter method to return spellChecker
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
这里你需要命名一个约定名字的setter方法。为了设置一个变量spellChecker我们需要命名setSpellChecker()方法,这个Java的POJO类基本相同。让我们创建另一个依赖的类SpellChecker:
package com.tutorialspoint;
public class SpellChecker {
public SpellChecker() {
System.out.println("Inside SpellChecker constructor.");
}
public void checkSpelling() {
System.out.println("Inside checkSpelling.");
}
}
如下是MainApp源代码:
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}
}
如下是基于setter方法的配置文件Beans.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-3.0.xsd">
<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker" ref="spellChecker" />
</bean>
<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>
</beans>
你需要注意一下基于constructor(构造函数)和基于setter方法依赖注入配置文件Beans.xml的不同。唯一的不同是在<bean>元素下面我们使用<constructor-arg>标签实现基于构造函数的注入,在基于setter方法的注入时我们使用<property>标签实现注入。
第二个注意点是当你传递一个引用对象时,你需要在<property>标签下使用“ref“属性,如果你传递值类型你需要使用”value”属性。
一旦你完成了上面源代码和配置文件,运行应用,如果一切正常,会打印如下消息:
Inside SpellChecker constructor.
Inside setSpellChecker.
Inside checkSpelling.
XML配置文件使用p-namespace
如果你有多个setter方法,很容易使用p-namespace在XML配置文件中。让我们检查一下不同:
让我们举一个带有<prpperty>标签的标准的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-3.0.xsd">
<bean id="john-classic" class="com.example.Person">
<property name="name" value="John Doe" />
<property name="spouse" ref="jane" />
</bean>
<bean name="jane" class="com.example.Person">
<property name="name" value="John Doe" />
</bean>
</beans>
上面的XML配置文件可以用使用p-namespace如下重写:
<?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-3.0.xsd">
<bean id="john-classic" class="com.example.Person" p:name="John Doe"
p:spouse-ref="jane" />
<bean name="jane" class="com.example.Person" p:name="John Doe" />
</beans>
这里你不不需要使用p-namespace指明基本类型和对象类型引用的不同 ,-ref部分指明这不是一个直接的值类型而是一个引用其他对象的类型。