序:依赖注入技术(Dependency Injection)被世人所认知和使用有一段时间了。近来,越来越多的人开始使用它的原理来设计、开发、和单元测试Java程序。一些很优秀很有意思的开源框架也由此开发出来,比如SpringFramework和Google Guice等等。译者翻译的这篇近两年前的文章,对于人们了解和学习Dependency Injection的基本原理应该是有裨益的。 就Dependency Injection目前在业界的直译(依赖注入)而言,译者认为不明了。所以以下就还一直使用英文。另请注意,这项技术并不是将依赖性进行注入,而是一些功能和资源的注入,请读者不要望文生义。 原文: 范畴 这篇文章从较高的层次来一览Dependency Injection(DI)技术。它的目的是在如何使用几种DI容器的背景下,.....
airline-agency-class = com.dnene.ditutorial.common.impl.SimpleAirlineAgency
cab-agency-class = com.dnene.ditutorial.common.impl.ConstructorBasedCabAgency
用来实例化AirlineAgency,CabAgency,和TripPlanner的源代码如下:
AirlineAgency airlineAgency = null;
CabAgency cabAgency = null;
TripPlanner tripPlanner = null;
// Get class names from property file
InputStream propertyFile = getClass().getClassLoader().
getResourceAsStream("nocontainer-agency.properties");
Properties properties = new Properties();
properties.load(propertyFile);
Class airlineAgencyClass = Class.forName
(properties.getProperty("airline-agency-class"));
Class cabAgencyClass = Class.forName
(properties.getProperty("cab-agency-class"));
Class tripPlannerClass = Class.forName
(properties.getProperty("trip-planner-class"));
if (AirlineAgency.class.isAssignableFrom(airlineAgencyClass))
{
// Instantiate airline agency
airlineAgency = (AirlineAgency) airlineAgencyClass.newInstance();
}
if (CabAgency.class.isAssignableFrom(cabAgencyClass))
{
// Instantiate CabAgency, and satisfy its dependency on an airlineagency.
Constructor constructor = cabAgencyClass.getConstructor
(new Class[]{AirlineAgency.class});
cabAgency = (CabAgency) constructor.newInstance
(new Object[]{airlineAgency});
}
if (TripPlanner.class.isAssignableFrom(tripPlannerClass))
{
Constructor constructor = tripPlannerClass.getConstructor
(new Class[]{AirlineAgency.class,CabAgency.class});
tripPlanner = (TripPlanner) constructor.newInstance
(new Object[]{airlineAgency,cabAgency});
}
return tripPlanner;
请注意对AirlineAgency的引用被传递给了CabAgency的constructor(这表示两服务组件间的依赖关系)。
实际旅行规划操作的代码是在AbstractHarness和AbstractTripPlanner中。它在所有的harness类重复使用。
AbstractHarness.java的部分代码如下:
public void runHarness() throws ExceptionAbstractTripPlanner.java的部分代码如下:
{
this.configure();
this.getPlanner().planTrip(
"1, Acme Street",
"11111",
"22222",
new Date(System.currentTimeMillis() + 7200000));
}
public void planTrip(String departingAddress, String departingZipCode,
String arrivalZipCode, Date preferredArrivalTime)
{
FlightSchedule schedule = airlineAgency.bookTicket
(departingZipCode,arrivalZipCode,preferredArrivalTime);
cabAgency.requestCab(departingAddress ,schedule.getFlightNumber());
}
Pico容器的实施
容器的实例化组件的注册依赖性的解决方案
在Pico的容器里注册实施类。
pico.registerComponentImplementation(SimpleAirlineAgency.class);
pico.registerComponentImplementation(ConstructorBasedCabAgency.class);
pico.registerComponentImplementation(ConstructorBasedTripPlanner.class);
依赖性的声明
依赖性是将包含着某一特定服务组件所依赖的接口程序列表的一个constructor里声明的。在下面的情况里,
ConstructorBasedCabAgency的服务件是依赖于AirlineAgency的接口程序的。
public ConstructorBasedCabAgency(AirlineAgency airlineAgency)
{
this.airlineAgency = airlineAgency;
}
类似地,ConstructorBasedTripPlanner也在它的constructor里声明对AirlineAgency和CabAgency的依赖。
依赖性的解决方案
Pico通过查看constructors来解决依赖性的问题。服务组件借助于对具体实施类的注册而完成在Pico容器中的注册。用户组件
通过那些接口类的名字来调用服务组件。智能化的Pico容器将遍历接口实施的层次和等级来返回适合用户需求的实施。(请注意在前面讨论过的
无容器实施方案也是通过类名来实例化实施类的)。
因为TripPlanner接口是依赖于Airline和CabAgency两个接口的,CabAgency接口又是依赖于AirlineAgency接口的,Pico容器
会自动将AirlineAgency首先实例化,然后在建CabAgency时,将对AirlineAgency的引用传递过去;在建TripPlanner时,
将对AirlineAgency和CabAgency的引用传递过去。在下面的需求提出时,上面整个的次序得以执行:
pico.getComponentInstanceOfType(TripPlanner.class);
HiveMind的实施
XML的声明
在这个例子里,接口和实施类都在xml文件里来声明。请注意各服务端组件间的依赖性不在xml文件里声明,而是在随后的代码里。
xml文件里的一个服务点将记录下一个服务组件所需的必要数据。
<?xml version="1.0"?>
<module id="com.dnene.ditutorial.hivemind" version="1.0.0">
<service-point id="AirlineAgency" interface="com.dnene.ditutorial.common.AirlineAgency">
<create-instance class="com.dnene.ditutorial.common.impl.SimpleAirlineAgency"/>
</service-point>
<service-point id="CabAgency" interface="com.dnene.ditutorial.common.CabAgency">
<invoke-factory>
<construct class="com.dnene.ditutorial.common.impl.SetterBasedCabAgency"/>
</invoke-factory>
</service-point>
<service-point id="TripPlanner" interface="com.dnene.ditutorial.common.TripPlanner">
<invoke-factory>
<construct class="com.dnene.ditutorial.common.impl.SetterBasedTripPlanner"/>
</invoke-factory>
</service-point>
</module>
容器的初始化
这一步会自动按照上面xml文件里注明的值将注册目录调配妥当。
Registry registry = RegistryBuilder.constructDefaultRegistry();
依赖性声明
CabAgency对AirlineAgency的依赖性通过在相关setter方法中的定义暗中传达给了HiveMind容器。
public void setAirlineAgency(AirlineAgency airlineAgency) {
this.airlineAgency = airlineAgency;
}
依赖性的解决方案
当调用TripPlanner的引用时,依赖性的解决方案同样是自动进行的(见以下部分代码)。HiveMind能够
遍历所有依赖性的结构,从而来依次对AirlineAgency,CabAgency,和TripPlanner接口来进行实例化,并
在CabAgency中激活setter(setAirlineAgency),在TripPlanner中激活两个setter(setAirlineAgency,
setCapAgency),从而来解决它们之间的依赖性。
registry.getService(TripPlanner.class);
Spring框架的实施
XML声明
需要实例化的实施都将在XML里声明。各服务组件间的依赖性作为属性元素来声明。Spring框架将使用这些信息来激活相应的
setter方法。
<beans>
<bean id="AirlineAgency" class="com.dnene.ditutorial.common.impl.SimpleAirlineAgency" singleton="true"/>
<bean id="CabAgency" class="com.dnene.ditutorial.common.impl.SetterBasedCabAgency" singleton="true">
<property name="airlineAgency">
<ref bean="AirlineAgency"/>
</property>
</bean>
<bean id="TripPlanner" class="com.dnene.ditutorial.common.impl.SetterBasedTripPlanner" singleton="true">
<property name="airlineAgency">
<ref bean="AirlineAgency"/>
</property>
<property name="cabAgency">
<ref bean="CabAgency"/>
</property>
</bean>
</beans>
容器的初始化
容器的初始化通常需要对xml文件的引用和对bean工厂的实例化。
ClassPathResource res = new ClassPathResource("spring-beans.xml");
BeanFactory factory = new XmlBeanFactory(res);
XWork实施
XWork其实是有一定DI功能的命令行模式的框架。它可能是在所有DI框架中最不成熟的了。但是,我发现如果你已经决定使用
webwork,特别是你要把依赖性注入到操作类中去的时候,XWork是一个很有用的框架。
XML声明
<components>请注意XWork依赖于一个叫做‘推动者‘的接口(enabler interface)。它基本上是一个为服务组件定义setter方法的接口。
<component>
<scope>application</scope>
<class>com.dnene.ditutorial.common.impl.SimpleAirlineAgency</class>
<enabler>com.dnene.ditutorial.xwork.AirlineAgencyAware</enabler>
</component>
<component>
<scope>application</scope>
<class>com.dnene.ditutorial.common.impl.SetterBasedCabAgency</class>
<enabler>com.dnene.ditutorial.xwork.CabAgencyAware</enabler>
</component>
<component>
<scope>application</scope>
<class>com.dnene.ditutorial.common.impl.SetterBasedTripPlanner</class>
<enabler>com.dnene.ditutorial.xwork.TripPlannerAware</enabler>
</component>
</components>
public interface CabAgencyAware和其他的容器不同的是,XWork要求我们定义AirlineAgencyAware,CabAgencyAware和TripPlannerAware 接口,并
{
public void setCabAgency(CabAgency cabAgency);
}
ComponentConfiguration config = new ComponentConfiguration();依赖性的声明
InputStream in =
XworkHarness.class.getClassLoader().getResourceAsStream("components.xml");
config.loadFromXml(in);
cm = new DefaultComponentManager();
config.configure(cm,"application");
public class SetterBasedCabAgency extends AbstractCabAgency implements AirlineAgencyAware { ... }
依赖性的解决方案XWork和WebWork
XWork通常和WebWork被绑在一起使用。在那样的情况下,你需要做的就是定义并实施推动者接口类,写出XML文件,
并使用组件拦截器。组件管理器间的互动得以通过WebWork来实施,所以只要将操作类的依赖性加以解决,组件管理器的
代码是不需要的。此外,对不同的组件管理器设立不同的范畴(比如说,应用,session,或request)是很容易的。
结论
正如你所看到的,每一个DI框架在实施它的功能上都或有不同。平心而论,它们在使用上也有不同(比如说,Pico容器
是基于Setter的DI,而Spring框架则是基于constructor的DI)。我刚刚决定要记录下其中的一种已被支持的机制。
从全面的角度来考量,要说一个框架比另一个框架好有点难。我想你需要根据你自身的实际情况来使用最合适的。 从以上情境来看,最清晰不过的是,使用DI框架将显著地降低实例化不同组件以及解决它们之间的依赖性的复杂程度。
额外功能
请注意我主要把重心放在下面两方面:
在一些领域里,有着相当程度的争论(通常来自于不同DI框架的作者或支持者)。其中的一些如:
基于constructor或基于setter的DI?
这里是指用constructor和用setter来声明依赖性,哪一个更好些?我不确信是否能干净利落地证明一个比另一个好。但是我
注意到,当整体的依赖性结构变得更复杂时,基于setter的DI看上去更易于管理。
XML或Java?
一些人偏好将依赖性的关系用Java代码来编织起来,更多的人则偏向于XML。我认为用Java来做更简单些。但是至少在
一个境况下,XML是唯一可行的选择。那就是当你推出二进制的产品时,为了系统管理员、集成人员、最终用户能够通过插入
为服务组件而做的不同的实施(为布署系统做的一些特别改动)而修改和添加默认的功能。你的最终用户更可能来改动你的XML
文件(假设你的文档做得好的话),而不是你的源代码(假设你已经公布了)。
在上述情况下,更重要的是需要指出,争议是关于方法,而不是结果。不管你用何种方法来实施,你都能利用DI框架。注意在
许多情况下,你会有两个选择(你可能需要好好研究一下API的文档)。如果框架的文档推荐使用XML,你很可能可以使用Java
来串起来,只要你能搞清楚那些能这么做的API就行。如果文档建议你用的框架只支持基于constructor或setter的DI,那么
一定要深度探索一下,很有可能你会发现对其他方法的支持。
使用DI能得到什么?
对DI从整体来看,无论是作为一个设计模式,还是不同的框架,你可能都会问,我能从DI上得到些什么?以下就是一些好处
【本文翻译仅为外语学习及阅读目的,原文作者个人观点与译者及译言网无关】