开始建立一个基于Spring的应用程序的关键在于为你的程序创建BeanFactory配置。一个没有仸何bean定义的的基本配置文件看起来是这个样子的:
仸何bean的定义都是通过在根节点的 Listing 4-10.使用XML配置的Hello World示例 我们可以修改第2章的代码来通过XmlBeanFactory读取这些配置。 Listing 4-11.从XML文件读取Hello World配置信息 package com.apress.prospring.ch4; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.FileSystemResource; import com.apress.prospring.ch2.MessageProvider; import com.apress.prospring.ch2.MessageRenderer; public class HelloWorldXml { public static void main(String[] args) throws Exception { // get the bean factory BeanFactory factory = getBeanFactory(); MessageRenderer mr = (MessageRenderer) factory.getBean(\"renderer\"); MessageProvider mp = (MessageProvider) factory.getBean(\"provider\"); mr.setMessageProvider(mp); mr.render(); } private static BeanFactory getBeanFactory() throws Exception { // get the bean factory XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource( \"ch4/src/conf/beans.xml\")); return factory; } } 注意Listing 4-11的main()方法与第2章Listing 2-10的main()方法之间没有区别,但是这里的getBeanFactory()方法的代码量显著减少。此处非常有意思的地方就在于main()方法的代码没有仸何改变。这是因为它通过BeanFactory接口来协同,而不是其它子接口或者类。虽然你也许需要在配置的时候指定它与特定的 BeanFactory合作,但是你不需要在你的应用程序中的其它地方通过getBean()方法来查找beans。这是一种应该遵循的很好的模式,你应该避免使你的应用程序与一个特定的BeanFactory实现间过渡耦合。 注意在Listing 4-11的代码中有一个问题,这个问题我们在第2张遇到并已经解决了——应用程序依然必须将provider bean传递给参考bean来满足它们的依赖。在第2章,我们修改了它的配置;Spring通过setter依赖注入来解决这个问题。我们当然也可以通过 XML配置支持来完成。 使用Setter依赖注入 通过XML支持来配置setter依赖注入,你需要在 从这段代码里,我们将provider bean指派给messageProvider属性。我们使用标记将一个bean的引用分配给一个属性(稍后详细讨论)。现在我们可以移除Hellow World示例中的无用的属性分配,就像Listing 4-12中的样子: Listing 4-12. 使用XML配置依赖注入 package com.apress.prospring.ch4; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.FileSystemResource; import com.apress.prospring.ch2.MessageRenderer; public class HelloWorldXmlWithDI { public static void main(String[] args) throws Exception { // get the bean factory BeanFactory factory = getBeanFactory(); MessageRenderer mr = (MessageRenderer) factory.getBean(\"renderer\"); mr.render(); } private static BeanFactory getBeanFactory() throws Exception { // get the bean factory XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource( \"ch4/src/conf/beans.xml\")); return factory; } } 这个例子充分利用了Spring的依赖注入能力,完全使用XML格式进行配置。 使用构造器依赖注入 前面的例子中,MessageProvider的实现即HelloWorldMessageProvider为每个getMessage()方法返回相同的手工编码的信息。在Spring配置文件中,你可以轻松的创建一个允许在外部定义消息的可配置的messageProvider,就像 Listing 4-13种的样子。 Listing 4-13. 可配置的MessageProvider类 package com.apress.prospring.ch4; import com.apress.prospring.ch2.MessageProvider; public class ConfigurableMessageProvider implements MessageProvider { private String message; public ConfigurableMessageProvider(String message) { this.message = message; } public String getMessage() { return message; } } 如你所见,不可能在不提供某个消息的值(除非支持null)的情况下创建一个ConfigurableMessageProvider的实例。这正是我们所需要的,这个类非常适合使用构造器依赖注入。Listing 4-14展示了如何能够为创建一个ConfigurableMessageProvider的实例重新定义provider bean。 Listing 4-14. 使用构造器依赖注入 这段代码中,代替 当你拥有超过一个的构造器参数或者你的类拥有多个构造器,你需要给每个 避免构造器混淆 早某些情况,Spring会难以确定你需要使用哪个构造器进行依赖注入。这一般发生在你拥有两个具有相同数量的参数且参数的类型也完全相同的构造器的时候。考虑一下Listing 4-15中的代码。 Listing 4-15. 构造器混淆 package com.apress.prospring.ch4; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.FileSystemResource; public class ConstructorConfusion { private String someValue; public ConstructorConfusion(String someValue) { System.out.println(\"ConstructorConfusion(String) called\"); this.someValue = someValue; } public ConstructorConfusion(int someValue) { System.out.println(\"ConstructorConfusion(int) called\"); this.someValue = \"Number: \" + Integer.toString(someValue); } public static void main(String[] args) { BeanFactory factory = new XmlBeanFactory(new FileSystemResource( \"./ch4/src/conf/beans.xml\")); ConstructorConfusion cc = (ConstructorConfusion) factory.getBean(\"constructorConfusion\"); System.out.println(cc); } public String toString() { return someValue; } } 这里,你可以清楚地看到这些代码的功能——它简单的从BeanFactory获取一个ConstructorConfusion类型的bean,然后将值写入到标准输出。现在,看看Listing 4-16中的配置代码。 Listing 4-16. 混淆的沟造器 这里会调哪一个构造器?运行一下次出的代码得到如下的输出: ConstructorConfusion(String) called 90 这表明具有String参数的构造器被调用了。这不是所想要的效果,因为我们希望仸何具有整数前缀的值都被传递到通过数字注入的沟造器中,即例子中的int构造器。改变这个问题,我们需要对配置文件进行一个小的修改,就像Listing 4-17那样。 Listing 4-17. 克服沟造器混淆 注意,现在 ConstructorConfusion(int) called Number: 90 注入参数 在前面的两个例子里,你看到了如何使用setter依赖注入和沟造器依赖注入将其他组件和值注射到bean里面。Spring支持非常多的依赖注入参数选项,不仅允许你注入组件和简单的值,还支持包括Java集合类、外部定义的属性文件,甚至其它工厂中的beans。你可以在setter依赖注入和构造器依赖注入中使用所有这些参数类型,通过在 简单值注入 将简单的值注入到beans里面是很简单的。如果需要,只需要简单的在配置标记中指定值,将其封装在一个 Listing 4-18. 注入简单值 package com.apress.prospring.ch4; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.FileSystemResource; public class InjectSimple { private String name; private int age; private float height; private boolean isProgrammer; private Long ageInSeconds; public static void main(String[] args) { XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource( \"./ch4/src/conf/beans.xml\")); InjectSimple simple = (InjectSimple)factory.getBean(\"injectSimple\"); System.out.println(simple); } public void setAgeInSeconds(Long ageInSeconds) { this.ageInSeconds = ageInSeconds; } public void setIsProgrammer(boolean isProgrammer) { this.isProgrammer = isProgrammer; } public void setAge(int age) { this.age = age; } public void setHeight(float height) { this.height = height; } public void setName(String name) { this.name = name; } public String toString() { return \"Name :\" + name + \"¥n\" + \"Age:\" + age + \"¥n\" + \"Age in Seconds: \" + ageInSeconds + \"¥n\" + \"Height: \" + height + \"¥n\" + \"Is Programmer?: \" + isProgrammer; } } 除了这些属性外,InjectSimple类还可以定义main()方法来创建一个XmlBeanFactory然后从Spring获取一个InjectSimple的bean。这个bean的属性值被写入到标准输出。这个bean的配置在Listing 4-19中。 Listing 4-19. 配置简单值注入 从Listing 4-18和4-19中你可以看到,可以在bean里面定义接受String值的属性,可以是原生数据类型值或者原生封装类值,使用 Name: John Smith Age: 35 Age in Seconds: 1103760000 Height: 1.78 Is Programmer?: true 在第5章,你会看到如何扩展使用 在同一个工厂注入Beans 就像你所看到的,允许使用标记将一个bean注入到另一个bean中。Listing 4-20展示了一个暴露了setter的类,它允许注入一个bean。 Listing 4-20. 注入Beans package com.apress.prospring.ch4; public class InjectRef { private Oracle oracle; public void setOracle(Oracle oracle) { this.oracle = oracle; } } 要配置Spring将一个bean注入到另一个中,你首先需要配置两个beans:一个是要注入的,另一个是注入的目标。这样做了以后,你可以使用 标记在目标bean里面配置注入。记住,必须在一个 Listing 4-21. 配置Bean注入 要说明的非常重要的一点是,要注入的类型不一定就是目标中定义的类型;这些类型只需要相容就可以了。相容意味着如果目标类中定义的类型是个接口,那么注入的类型必须实现了这个接口。如果定义了的类型诗歌类,那么注入的类型必须是相同的类或者一个子类。在例子中,InjectRef类型定义了一个 setOracle()方法接受一个Oracle的实例,那是一个接口,注入的类型是一个实现了Oracle的类BookwormOracle。这点可能会造成一些开发者的混淆,但是这真的非常简单。注入受控于仸何Java代码所需要遵循的类型控制关系,所以当你理解了Java的类型是如何工作的,那么理解注入中的类型控制就非常简单了。 在前面的例子里,要注入的bean的id使用了本地的标记属性来指定。你后面将会看到,在“理解Bean命名”那一节中,你可以给一个bean多个命名,这样你可以通过很多的别名来引用它。当你使用本地属性,那么意味着标记仅仅通过bean的id属性查找,而不通过仸何的别名查找。通过仸何命名来注入bean,使 用bean的标记的属性代替本地属性。Listing 4-22展示了前面例子的另一种配置方式,通过别名来注入bean。 Listing 4-22. 使用Bean别名注入 class=\"com.apress.prospring.ch4.BookwormOracle\"/> 在这个例子中,oracle bean通过name属性的一个别名给出,然后它被通过别名和bean属性的标记之间的连接注入到injectRef bean。这里不用过渡担心命名的语义学——我们会在本章中详细讨论这个问题。 注入和BeanFactory嵌套 到目前为止我们注入的bean与需要被注入的bean都放在同一个bean工厂里。可是,Spring支持Bean工厂的分层结构,如此一个工厂可以被当作另一个工厂的父节点。通过允许Bean工厂的嵌套,Spring允许你将你的配置文件分割为多个不同的文件——对于拥有大量beans的工程是个福音。 当嵌套Bean工厂时,Spring允许其中的beans将子工厂作为父工厂的参考beans。唯一的缺点是这只能在配置中使用。调用子Bean工厂的getBean()来访问父工厂中的一个bean是不可能的。 使用XmlBeanFactory的Bean工厂嵌套非常容易被约束。将一个XmlBeanFactory嵌套到另一个里,只需要简单的将父 XmlBeanFactory作为子XmlBeanFactory的构造器参数传递过去就可以了。在Listing 4-23展示 了这种方法。 Listing 4-23. 嵌套XmlBeanFactories BeanFactory parent = new XmlBeanFactory(new FileSystemResource( \"./ch4/src/conf/parent.xml\")); BeanFactory child = new XmlBeanFactory(new FileSystemResource( \"./ch4/src/conf/beans.xml\"), parent); 在子Bean工厂的配置文件中,在父Bean工厂中引用一个bean与子Bean工厂中引用一个bean的工作方式是相同的,除非你在子Bean工厂中有一个与之同名的bean。如果那样,你只要用父工厂替代标记的bean属性,就可以了。Listing 4-24展示了父Bean工厂的配置文件的样例。 Listing 4-24. 父Bean工厂的配置 如你所见,配置简单定义了两个beans:injectBean和injectBeanParent。两个都是父工厂中数值Bean的String对象。Listing 4-25展示了子Bean工厂的配置样例。 Listing 4-25. 子Bean工厂配置 注意我们在此处定义了四个beans。这个listing中的injectBean与父工厂的injectBean类似,除了那个String的值不同,这表明他是在从子BeanFactory获取的。 Target1这个bean使用了标记的bean属性来引用另外一个命名为injectBeanParent的bean。因为这个bean只在父Bean工厂中存在,target1获得那个bean的引用。这里有两点很有意义。第一点,你既可以在子Bean工厂也可以在父 Bean工厂使用bean属性来引用bean。这使透明的引用bean易于实现,当你的程序膨胀时允许你在配置文件中移动beans。第二点,你不能使用本地属性来引用父工厂中的beans。XML解析器会在同一个文件中检查本地的属性是否存在一个可用的元素(element)对应,阻止引用父工厂中的 beans。 Target2这个bean使用了标记的bean属性来引用injectBean。因为这个bean在两个Bean工厂中都有定义,target2 bean所得到的injectBean的引用来自自己那个BeanFactory。 Target3这个bean使用了标记的parent属性来直接引用父Bean工厂中的injectBean。因为target3使用了标记的parent属性,在子Bean工厂中所声名的injectBean被完全忽略了。 Listing 4-26. HierarchicalBeanFactoryUsage(使用分层Bean工厂)类 package com.apress.prospring.ch4; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.FileSystemResource; public class HierarchicalBeanFactoryUsage { public static void main(String[] args) { BeanFactory parent = new XmlBeanFactory(new FileSystemResource( \"./ch4/src/conf/parent.xml\")); BeanFactory child = new XmlBeanFactory(new FileSystemResource( \"./ch4/src/conf/beans.xml\"), parent); SimpleTarget target1 = (SimpleTarget) child.getBean(\"target1\"); SimpleTarget target2 = (SimpleTarget) child.getBean(\"target2\"); SimpleTarget target3 = (SimpleTarget) child.getBean(\"target3\"); System.out.println(target1.getVal()); System.out.println(target2.getVal()); System.out.println(target3.getVal()); } } 下面是这个例子运行后的输出: Bean In Parent Bean In Child Bean In Parent 和我们料想的一样,target1和target3的bean都得到了父Bean工厂中的bean的引用,而target2 bean从子BeanFacory中得到了bean的引用。 使用集合进行注入 一般,你的bean需要访问对象的集合,而不是访问一个单一的bean或者值。因此,理所当然的,Spring允许你在你的一个bean中注入一个对象的集合。使用集合很简单:你可以选择、