Spring实战(第4版)第2章 装配Bean
文章目录
- Spring实战(第4版)第2章 装配Bean
- 2.1 Spring配置的可选方案
- 2.2 自动化装配bean
- 2.2.1 创建可被发现的bean
- 2.2.2 为组件扫描的bean命名
- 2.2.3 设置组件扫描的基础包
- 2.2.4 通过为bean添加注解实现自动装配
- 2.2.5 验证自动装配
- 2.3 通过Java代码装配bean
- 2.3.1 创建配置类
- 2.3.2 声明简单的bean
- 2.3.3 借助JavaConfig实现注入
- 2.4 通过XML装配bean
- 2.4.1 创建XML配置规范
- 2.4.2 声明一个简单的
- 2.4.3 借助构造器注入初始化bean
- 构造器注入bean引用
- 将字面量注入到构造器中
- 装配集合
- 2.4.4 设置属性
- 将字面量注入到属性中
- 2.5 导入和混合配置
- 2.5.1 在JavaConfig中引用XML配置
- 2.5.2 在XML配置中引用JavaConfig
- 2.6 小结
- 2.6 小结
Spring实战(第4版)第2章 装配Bean
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
2.1 Spring配置的可选方案
Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
在很多场景下,选择哪种方案很大程度上就是个人喜好的问题,你尽可以选择自己最喜欢的方式。
即便如此,我的建议是尽可能地使用自动配置的机制。**显式配置越少越好。**当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
2.2 自动化装配bean
Spring从两个角度来实现自动化装配:
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
自动装配(autowiring):Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。
2.2.1 创建可被发现的bean
程序清单2.1 CompactDisc接口在Java中定义了CD的概念
package soundsystem;public interface CompactDisc {void play();
}
CompactDisc
的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
CompactDisc
的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
我们还需要一个CompactDisc
的实现,实际上,我们可以有CompactDisc
接口的多个实现。在本例中,我们首先会创建其中的一个实现,也就是程序清单2.2所示的SgtPeppers
类。
程序清单2.2 带有@Component注解的CompactDisc实现类SgtPeppers
package soundsystem;
import org.springframework.stereotype.Component;@Component
public class SgtPeppers implements CompactDisc {private String title = "Sgt. Pepper's Lonely Hearts Club Band";private String artist = "The Beatles";public void play() {System.out.println("Playing " + title + " by " + artist);}}
和CompactDisc
接口一样,SgtPeppers
的具体内容并不重要。你需要注意的就是SgtPeppers
类上使用了@Component
注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppers
bean,因为这个类使用了@Component
注解,所以Spring会为你把事情处理妥当。
不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component
注解的类,并为其创建bean。程序清单2.3的配置类展现了完成这项任务的最简洁配置。
程序清单2.3 @ComponentScan注解启用了组件扫描
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan
public class CDPlayerConfig {}
类CDPlayerConfig
通过Java代码定义了Spring的装配规则。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。
如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的context:component-scan元素。程序清单2.4展示了启用组件扫描的最简洁XML配置。
程序清单2.4 通过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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="soundsystem" /></beans>
尽管我们可以通过XML的方案来启用组件扫描,但是在后面的讨论中,我更多的还是会使用基于Java的配置。如果你更喜欢XML的话,context:component-scan元素会有与@ComponentScan注解相对应的属性和子元素。
程序清单2.5 测试组件扫描能够发现CompactDisc
package soundsystem;import static org.junit.Assert.*;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {@Autowiredprivate CompactDisc cd;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}}
CDPlayerTest
使用了Spring的SpringJUnit4ClassRunner
,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration
会告诉它需要在CDPlayerConfig
中加载配置。因为CDPlayerConfig
类中包含了@ComponentScan
,因此最终的应用上下文中应该包含CompactDisc
bean。
为了证明这一点,在测试代码中有一个CompactDisc
类型的属性,并且这个属性带有@Autowired
注解,以便于将CompactDisc
bean注入到测试代码之中(稍后,我会讨论@Autowired
)。最后,会有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着Spring能够发现CompactDisc
类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
这个代码应该能够通过测试,并以测试成功的颜色显示(在你的测试运行器中,或许会希望出现绿色)。你第一个简单的组件扫描练习就成功了!尽管我们只用它创建了一个bean,但同样是这么少的配置能够用来发现和创建任意数量的bean。在soundsystem
包及其子包中,所有带有@Component
注解的类都会创建为bean。只添加一行@ComponentScan
注解就能自动创建无数个bean,这种权衡还是很划算的。
现在,我们会更加深入地探讨@ComponentScan
和@Component
,看一下使用组件扫描还能做些什么。
2.2.2 为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。在前面的例子中,尽管我们没有明确地为SgtPeppers
bean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers
,也就是将类名的第一个字母变为小写。
如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component
注解。比如说,如果想将这个bean标识为lonelyHeartsClub
,那么你需要将SgtPeppers
类的@Component
注解配置为如下所示:
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {...
}
还有另外一种为bean命名的方式,这种方式不使用@Component
注解,而是使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named
注解来为bean设置ID:
package soundsystem;
import javax.inject.Named;@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {...
}
Spring支持将@Named
作为@Component
注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换的。
话虽如此,我更加强烈地喜欢@Component
注解,而对于@Named……
怎么说呢,我感觉它的名字起得很不好。它并没有像@Component
那样清楚地表明它是做什么的。
2.2.3 设置组件扫描的基础包
到现在为止,我们没有为@ComponentScan设置任何属性。这意味着,按照默认规则,它会以配置类所在的包作为基础包(base package)来扫描组件。但是,如果你想扫描不同的包,那该怎么办呢?或者,如果你想扫描多个基础包,那又该怎么办呢?
有一个原因会促使我们明确地设置基础包,那就是我们想要将配置类放在单独的包中,使其与其他的应用代码区分开来。如果是这样的话,那默认的基础包就不能满足要求了。
要满足这样的需求其实也完全没有问题!为了指定不同的基础包,你所需要做的就是在@ComponentScan
的value属性中指明包的名称:
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {}
如果你想更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:
@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig {}
可能你已经注意到了basePackages
属性使用的是复数形式。如果你揣测这是不是意味着可以设置多个基础包,那么恭喜你猜对了。如果想要这么做的话,只需要将basePackages
属性设置为要扫描包的一个数组即可:
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {}
在上面的例子中,所设置的基础包是以String
类型表示的。我认为这是可以的,但这种方法是类型不安全(not type-safe)的。如果你重构代码的话,那么所指定的基础包可能就会出现错误了。
除了将包设置为简单的String类型之外,@ComponentScan
还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}
可以看到,basePackages
属性被替换成了basePackageClasses
。同时,我们不是再使用String
类型的名称来指定包,为basePackageClasses
属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包。
尽管在样例中,我为basePackageClasses
设置的是组件类,但是你可以考虑在包中创建一个用来进行扫描的空标记接口(marker interface)。通过标记接口的方式,你依然能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码(在稍后重构中,这些应用代码有可能会从想要扫描的包中移除掉)。
在你的应用程序中,如果所有的对象都是独立的,彼此之间没有任何依赖,就像SgtPeppers
bean这样,那么你所需要的可能就是组件扫描而已。但是,很多对象会依赖其他的对象才能完成任务。这样的话,我们就需要有一种方法能够将组件扫描得到的bean和它们的依赖装配在一起。要完成这项任务,我们需要了解一下Spring自动化配置的另外一方面内容,那就是自动装配。
2.2.4 通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired
注解。
比方说,考虑程序清单2.6中的CDPlayer
类。它的构造器上添加了@Autowired
注解,这表明当Spring创建CDPlayer
bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc
类型的bean。
程序清单2.6 通过自动装配,将一个CompactDisc注入到CDPlayer之中
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class CDPlayer implements MediaPlayer {private CompactDisc cd;@Autowiredpublic CDPlayer(CompactDisc cd) {this.cd = cd;}public void play() {cd.play();}}
@Autowired
注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如说,如果CDPlayer
有一个setCompactDisc
()方法,那么可以采用如下的注解形式进行自动装配:
@Autowired
public void setCompactDisc(CompactDisc cd) {this.cd = cd;
}
在Spring初始化bean之后,它会尽可能得去满足bean的依赖,在本例中,依赖是通过带有@Autowired
注解的方法进行声明的,也就是setCompactDisc()
。
实际上,Setter方法并没有什么特殊之处。@Autowired
注解可以用在类的任何方法上。假设CDPlayer
类有一个insertDisc()
方法,那么@Autowired
能够像在setCompactDisc()
上那样,发挥完全相同的作用:
@Autowired
public void insertDisc(CompactDisc cd) {this.cd = cd;
}
不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired
的required
属性设置为false
:
@Autowired(required=false)
public CDPlayer(CompactDisc cd) {this.cd = cd;
}
将required
属性设置为false
时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是,把required
属性设置为false
时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException
。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。在第3章中,我们会进一步讨论自动装配中的歧义性。
@Autowired
是Spring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换为@Inject
:
package soundsystem;
import javax.inject.Inject;
import javax.inject.Named;@Named
public class CDPlayer {...@Injectpublic CDPlayer(CompactDisc cd) {this.cd = cd;}...
}
@Inject
注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named
注解。在自动装配中,Spring同时支持@Inject
和@Autowired
。尽管@Inject
和@Autowired
之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。
在@Inject
和@Autowired
中,我没有特别强烈的偏向性。实际上,在有的项目中,我会发现我同时使用了这两个注解。不过在本书的样例中,我会一直使用@Autowired
,而你可以根据自己的情况,选择其中的任意一个。
2.2.5 验证自动装配
现在,我们已经在CDPlayer
的构造器中添加了@Autowired
注解,Spring将把一个可分配给CompactDisc
类型的bean自动注入进来。为了验证这一点,让我们修改一下CDPlayerTest
,使其能够借助CDPlayer bean
播放CD:
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {@Rulepublic final StandardOutputStreamLog log =new StandardOutputStreamLog();@Autowiredprivate MediaPlayer player;@Autowiredprivate CompactDisc cd;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}@Testpublic void play() {player.play();assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band" +" by The Beatles\n",log.getLog());}}
现在,除了注入CompactDisc
,我们还将CDPlayer
bean注入到测试代码的player
成员变量之中(它是更为通用的MediaPlayer
类型)。在play()
测试方法中,我们可以调用CDPlayer
的play()方法,并断言它的行为与你的预期一致。
在测试代码中使用System.out.println()
是稍微有点棘手的事情。因此,该样例中使用了StandardOutputStreamLog
,这是来源于System Rules库(http://stefanbirkner.github.io/system-rules/index.html)的一个JUnit规则,该规则能够基于控制台的输出编写断言。在这里,我们断言SgtPeppers.play()
方法的输出被发送到了控制台上。
现在,你已经了解了组件扫描和自动装配的基础知识,在第3章中,当我们介绍如何处理自动装配的歧义性时,还会继续研究组件扫描。
但是现在,我们先将组件扫描和自动装配放在一边,看一下在Spring中如何显式地装配bean,首先从通过Java代码编写配置开始。
2.3 通过Java代码装配bean
**尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。**比如说,**你想要将第三方库中的组件装配到你的应用中,**在这种情况下,是没有办法在它的类上添加@Component
和@Autowired
注解的,因此就不能使用自动化装配的方案了。
在这种情况下,你必须要采用显式装配的方式。在进行显式配置的时候,有两种可选方案:**Java和XML。**在这节中,我们将会学习如何使用Java配置,接下来的一节中将会继续学习Spring的XML配置。
就像我之前所说的,在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。
同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,**但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。**尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。
接下来,让我们看一下如何通过JavaConfig显式配置Spring。
2.3.1 创建配置类
在本章前面的程序清单2.3中,我们第一次见识到JavaConfig。让我们重温一下那个样例中的CDPlayerConfig
:
package soundsystem;
import org.springframework.context.annotation.Configuration;@Configuration
public class CDPlayerConfig {}
创建JavaConfig类的关键在于为其添加@Configuration
注解,@Configuration
注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
到此为止,我们都是依赖组件扫描来发现Spring应该创建的bean。尽管我们可以同时使用组件扫描和显式配置,但是在本节中,我们更加关注于显式配置,因此我将CDPlayerConfig
的@ComponentScan
注解移除掉了。
移除了@ComponentScan
注解,此时的CDPlayerConfig
类就没有任何作用了。如果你现在运行CDPlayerTest
的话,测试会失败,并且会出现BeanCreation
- Exception
异常。测试期望被注入CDPlayer
和CompactDisc
,但是这些bean根本就没有创建,因为组件扫描不会发现它们。
为了再次让测试通过,你可以将@ComponentScan
注解添加回去,但是我们这一节关注显式配置,因此让我们看一下如何使用JavaConfig装配CDPlayer
和CompactDisc
。
2.3.2 声明简单的bean
要在JavaConfig中声明bean
,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean
注解。比方说,下面的代码声明了CompactDisc bean
:
@Bean
public CompactDisc sgtPeppers() {return new SgtPeppers();
@Bean
注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean
注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers
。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name
属性指定一个不同的名字:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {return new SgtPeppers();
}
不管你采用什么方法来为bean命名,bean声明都是非常简单的。方法体返回了一个新的SgtPeppers
实例。这里是使用Java来进行描述的,因此我们可以发挥Java提供的所有功能,只要最终生成一个CompactDisc
实例即可。
请稍微发挥一下你的想象力,我们可能希望做一点稍微疯狂的事情,比如说,在一组CD中随机选择一个CompactDisc
来播放:
@Bean
public CompactDisc randomBeatlesCD() {int choice = (int) Math.floor(Math.random() * 4);if (choice == 0) {return new SgtPeppers();} else if (choice == 1) {return new WhiteAlbum();} else if (choice == 2) {return new HardDaysNight();} else {return new Revolver();}
}
现在,你可以自己想象一下,借助@Bean
注解方法的形式,我们该如何发挥出Java的全部威力来产生bean。当你想完之后,我们要回过头来看一下在JavaConfig中,如何将CompactDisc
注入到CDPlayer
之中。
2.3.3 借助JavaConfig实现注入
我们前面所声明的CompactDisc
bean是非常简单的,它自身没有其他的依赖。但现在,我们需要声明CDPlayer
bean,它依赖于CompactDisc
。在JavaConfig中,要如何将它们装配在一起呢?
在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如,下面就是一种声明CDPlayer
的可行方案:
@Bean
public CDPlayer cdPlayer() {return new CDPlayer(sgtPeppers());
}
cdPlayer()
方法像sgtPeppers()
方法一样,同样使用了@Bean
注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所创建的bean ID为cdPlayer
,与方法的名字相同。
cdPlayer()
的方法体与sgtPeppers()
稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc
对象的构造器来创建CDPlayer
实例。
看起来,CompactDisc
是通过调用sgtPeppers()
得到的,但情况并非完全如此。因为sgtPeppers()
方法上添加了@Bean
注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。
比如说,假设你引入了一个其他的CDPlayer
bean,它和之前的那个bean完全一样:
@Bean
public CDPlayer cdPlayer() {return new CDPlayer(sgtPeppers());
}@Bean
public CDPlayer anotherCDPlayer() {return new CDPlayer(sgtPeppers());
}
假如对sgtPeppers()
的调用就像其他的Java方法调用一样的话,那么每个CDPlayer
实例都会有一个自己特有的SgtPeppers
实例。如果我们讨论的是实际的CD播放器和CD光盘的话,这么做是有意义的。如果你有两台CD播放器,在物理上并没有办法将同一张CD光盘放到两个CD播放器中。
但是,在软件领域中,我们完全可以将同一个SgtPeppers
实例注入到任意数量的其他bean之中。默认情况下,Spring中的bean都是单例的,我们并没有必要为第二个CDPlayer
bean创建完全相同的SgtPeppers
实例。所以,Spring会拦截对sgtPeppers()
的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()
时所创建的CompactDisc
bean。因此,两个CDPlayer
bean会得到相同的SgtPeppers
实例。
可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {return new CDPlayer(compactDisc);
}
在这里,cdPlayer()
方法请求一个CompactDisc
作为参数。当Spring调用cdPlayer()
创建CDPlayer
bean的时候,它会自动装配一个CompactDisc
到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()
方法也能够将CompactDisc
注入到CDPlayer
的构造器中,而且不用明确引用CompactDisc
的@Bean
方法。
**通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc
声明到同一个配置类之中。**在这里甚至没有要求CompactDisc
必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc
是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer
bean。
另外,需要提醒的是,我们在这里使用CDPlayer
的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。比如说,如果你想通过Setter方法注入CompactDisc
的话,那么代码看起来应该是这样的:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {CDPlayer cdPlayer = new CDPlayer(compactDisc);cdPlayer.setCompactDisc(compactDisc);return cdPlayer;
}
再次强调一遍,带有@Bean注
解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方
法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。
2.4 通过XML装配bean
在Spring刚刚出现的时候,XML是描述配置的主要方式。在Spring的名义下,我们创建了无数行XML代码。在一定程度上,Spring成为了XML配置的同义词。
尽管Spring长期以来确实与XML有着关联,但现在需要明确的是,XML不再是配置Spring的唯一可选方案。Spring现在有了强大的自动化配置和基于Java的配置,XML不应该再是你的第一选择了。
不过,鉴于已经存在那么多基于XML的Spring配置,所以理解如何在Spring中使用XML还是很重要的。但是,我希望本节的内容只是用来帮助你维护已有的XML配置,在完成新的Spring工作时,希望你会使用自动化配置和JavaConfig。
2.4.1 创建XML配置规范
在使用XML为Spring装配bean之前,你需要创建一个新的配置规范。在使用JavaConfig的时候,这意味着要创建一个带有@Configuration
注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以<beans>
元素为根。
最为简单的Spring 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context"><!-- configuration details go here --></beans>
很容易就能看出来,这个基本的XML配置已经比同等功能的JavaConfig类复杂得多了。作为起步,在JavaConfig中所需要的只是@Configuration
,但在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。
用来装配bean的最基本的XML元素包含在spring-beans
模式之中,在上面这个XML文件中,它被定义为根命名空间。<beans>
是该模式中的一个元素,它是所有Spring配置文件的根元素。
就这样,我们已经有了一个合法的Spring XML配置。不过,它也是一个没有任何用处的配置,因为它(还)没有声明任何bean。为了给予它生命力,让我们重新创建一下CD样例,只不过我们这次使用XML配置,而不是使用JavaConfig和自动化装配。
2.4.2 声明一个简单的
要在基于XML的Spring配置中声明一个bean,我们要使用spring-beans
模式中的另外一个元素:<bean>
。<bean>
元素类似于JavaConfig中的@Bean
注解。我们可以按照如下的方式声明CompactDisc
bean:
<bean class="soundsystem.SgtPeppers" />
这里声明了一个很简单的bean,创建这个bean的类通过class属性来指定的,并且要使用全限定的类名。
因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。在本例中,bean的ID将会是“soundsystem.SgtPeppers#0
”。其中,“#0
”是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个SgtPeppers
,并且没有明确进行标识,那么它自动得到的ID将会是“soundsystem.SgtPeppers#1
”。
尽管自动化的bean命名方式非常方便,但如果你要稍后引用它的话,那自动产生的名字就没有多大的用处了。因此,通常来讲更好的办法是借助id属性,为每个bean设置一个你自己选择的名字:
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
稍后将这个bean装配到CDPlayer
bean之中的时候,你会用到这个具体的名字。
减少繁琐为了减少XML中繁琐的配置,只对那些需要按名字引用的bean(比如,你需要将对它的引用注入到另外一个bean中)进行明确地命名。
在进一步学习之前,让我们花点时间看一下这个简单bean声明的一些特征。
第一件需要注意的事情就是你不再需要直接负责创建SgtPeppers
的实例,在基于JavaConfig的配置中,我们是需要这样做的。当Spring发现这个<bean>
元素时,它将会调用SgtPeppers
的默认构造器来创建bean。在XML配置中,bean的创建显得更加被动,不过,它并没有JavaConfig那样强大,在JavaConfig配置方式中,你可以通过任何可以想象到的方法来创建bean实例。
另外一个需要注意到的事情就是,在这个简单的<bean>
声明中,我们将bean的类型以字符串的形式设置在了class
属性中。谁能保证设置给class
属性的值是真正的类呢?Spring的XML配置并不能从编译期的类型检查中受益。即便它所引用的是实际的类型,如果你重命名了类,会发生什么呢?
借助IDE检查XML的合法性使用能够感知Spring功能的IDE,如Spring Tool Suite,能够在很大程度上帮助你确保Spring XML配置的合法性。
以上介绍的只是JavaConfig要优于XML配置的部分原因。我建议在为你的应用选择配置风格时,要记住XML配置的这些缺点。接下来,我们继续Spring XML配置的学习进程,了解如何将SgtPeppers
bean注入到CDPlayer
之中。
2.4.3 借助构造器注入初始化bean
在Spring XML配置中,只有一种声明bean的方式:使用<bean>
元素并指定class
属性。Spring会从这里获取必要的信息来创建bean。
但是,在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:
<constructor-arg>
元素- 使用Spring 3.0所引入的c-命名空间
两者的区别在很大程度就是是否冗长烦琐。可以看到,<constructor-arg>
元素比使用c-命名空间会更加冗长,从而导致XML更加难以读懂。另外,有些事情<constructor-arg>
可以做到,但是使用c-命名空间却无法实现。
在介绍Spring XML的构造器注入时,我们将会分别介绍这两种可选方案。首先,看一下它们各自如何注入bean引用。
构造器注入bean引用
按照现在的定义,CDPlayer
bean有一个接受CompactDisc
类型的构造器。这样,我们就有了一个很好的场景来学习如何注入bean的引用。
现在已经声明了SgtPeppers
bean,并且SgtPeppers
类实现了CompactDisc
接口,所以实际上我们已经有了一个可以注入到CDPlayer
bean中的bean。我们所需要做的就是在XML中声明CDPlayer
并通过ID引用SgtPeppers
:
<bean id="cdPlayer" class="soundsystem.CDPlayer"><constructor-arg ref="compactDisc" />
</bean>
当Spring遇到这个<bean>
元素时,它会创建一个CDPlayer
实例。<constructor-arg>
元素会告知Spring要将一个ID为compactDisc
的bean引用传递到CDPlayer
的构造器中。
作为替代的方案,你也可以使用Spring的c-命名空间。c-命名空间是在Spring 3.0中引入的,它是在XML中更为简洁地描述构造器参数的方式。要使用它的话,必须要在XML的顶部声明其模式,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:c="http://www.springframework.org/schema/c"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">...</beans>
在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了,如下所示:
<bean id="cdPlayer" class="soundsystem.CDPlayer"c:cd-ref="compactDisc" />
在这里,我们使用了c-命名空间来声明构造器参数,它作为<bean>
元素的一个属性,不过这个属性的名字有点诡异。图2.1描述了这个属性名是如何组合而成的。
图2.1 通过Spring的c-命名空间将bean引用注入到构造器参数中
属性名以“c:
”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref
”,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc
,而不是字面量“compactDisc
”。
很显然,使用c-命名空间属性要比使用<constructor-arg>
元素简练得多。这是我很喜欢它的原因之一。除了更易读之外,当我在编写样例代码时,c-命名空间属性能够更加有助于使代码的长度保持在书的边框之内。
在编写前面的样例时,关于c-命名空间,有一件让我感到困扰的事情就是它直接引用了构造器参数的名称。引用参数的名称看起来有些怪异,因为这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中。如果你优化构建过程,将调试标志移除掉,那么这种方式可能就无法正常执行了。
替代的方案是我们使用参数在整个参数列表中的位置信息:
<bean id="cdPlayer" class="soundsystem.CDPlayer"c:_0-ref="compactDisc" />
这个c-命名空间属性看起来似乎比上一种方法更加怪异。我将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下画线作为前缀。
使用索引来识别构造器参数感觉比使用名字更好一些。即便在构建的时候移除掉了调试标志,参数却会依然保持相同的顺序。如果有多个构造器参数的话,这当然是很有用处的。在这里因为只有一个构造器参数,所以我们还有另外一个方案——根本不用去标示参数:
<bean id="cdPlayer" class="soundsystem.CDPlayer"c:_-ref="compactDisc" />
到目前为止,这是最为奇特的一个c-命名空间属性,这里没有参数索引或参数名。只有一个下画线,然后就是用“-ref
”来表明正在装配的是一个引用。
我们已经将引用装配到了其他的bean之中,接下来看一下如何将字面量值(literal value)装配到构造器之中。
将字面量注入到构造器中
迄今为止,我们所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——而有时候,我们需要做的只是用一个字面量值来配置对象。为了阐述这一点,假设你要创建CompactDisc
的一个新实现,如下所示:
package soundsystem;public class BlankDisc implements CompactDisc {private String title;private String artist;public BlankDisc(String title, String artist) {this.title = title;this.artist = artist;}public void play() {System.out.println("Playing " + title + " by " + artist);}
}
在SgtPeppers
中,唱片名称和艺术家的名字都是硬编码的,但是这个CompactDisc
实现与之不同,它更加灵活。像现实中的空磁盘一样,它可以设置成任意你想要的艺术家和唱片名。现在,我们可以将已有的SgtPeppers
替换为这个类:
<bean id="compactDisc"class="soundsystem.BlankDisc"><constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /><constructor-arg value="The Beatles" />
</bean>
我们再次使用<constructor-arg>
元素进行构造器参数的注入。但是这一次我们没有使用“ref
”属性来引用其他的bean,而是使用了value
属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。
如果要使用c-命名空间的话,这个例子又该是什么样子呢?第一种方案是引用构造器参数的名字:
<bean id="compactDisc"class="soundsystem.BlankDisc"c:_title="Sgt. Pepper's Lonely Hearts Club Band"c:_artist="The Beatles" />
可以看到,装配字面量与装配引用的区别在于属性名中去掉了“-ref
”后缀。与之类似,我们也可以通过参数索引装配相同的字面量值,如下所示:
<bean id="compactDisc"class="soundsystem.BlankDisc"c:_0="Sgt. Pepper's Lonely Hearts Club Band"c:_1="The Beatles" />
XML不允许某个元素的多个属性具有相同的名字。因此,如果有两个或更多的构造器参数的话,我们不能简单地使用下画线进行标示。但是如果只有一个构造器参数的话,我们就可以这样做了。为了完整地展现该功能,假设BlankDisc
只有一个构造器参数,这个参数接受唱片的名称。在这种情况下,我们可以在Spring中这样声明它:
<bean id="compactDisc" class="soundsystem.BlankDisc"c:_="Sgt. Pepper's Lonely Hearts Club Band" />
在装配bean引用和字面量值方面,<constructor-arg>
和c-命名空间的功能是相同的。但是有一种情况是<constructor-arg>
能够实现,c-命名空间却无法做到的。接下来,让
我们看一下如何将集合装配到构造器参数中。
装配集合
到现在为止,我们假设CompactDisc
在定义时只包含了唱片名称和艺术家的名字。如果现实世界中的CD也是这样的话,那么在技术上就不会任何的进展。CD之所以值得购买是因为它上面所承载的音乐。大多数的CD都会包含十多个磁道,每个磁道上包含一首歌。
如果使用CompactDisc
为真正的CD建模,那么它也应该有磁道列表的概念。请考虑下面这个新的BlankDisc
:
package soundsystem.collections;
import java.util.List;
import soundsystem.CompactDisc;public class BlankDisc implements CompactDisc {private String title;private String artist;private List<String> tracks;public BlankDisc(String title, String artist, List<String> tracks) {this.title = title;this.artist = artist;this.tracks = tracks;}public void play() {System.out.println("Playing " + title + " by " + artist);for (String track : tracks) {System.out.println("-Track: " + track);}}}
这个变更会对Spring如何配置bean产生影响,在声明bean的时候,我们必须要提供一个磁道列表。
最简单的办法是将列表设置为null
。因为它是一个构造器参数,所以必须要声明它,不过你可以采用如下的方式传递null
给它:
<bean id="compactDisc" class="soundsystem.BlankDisc"><constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /><constructor-arg value="The Beatles" /><constructor-arg><null/></constructor-arg>
</bean>
<null/>
元素所做的事情与你的期望是一样的:将null
传递给构造器。这并不是解决问题的好办法,但在注入期它能正常执行。当调用play()
方法时,你会遇到NullPointerException
异常,因此这并不是理想的方案。
更好的解决方法是提供一个磁道名称的列表。要达到这一点,我们可以有多个可选方案。首先,可以使用元素将其声明为一个列表:
<bean id="compactDisc" class="soundsystem.BlankDisc"><constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /><constructor-arg value="The Beatles" /><constructor-arg><list><value>Sgt. Pepper's Lonely Hearts Club Band</value><value>With a Little Help from My Friends</value><value>Lucy in the Sky with Diamonds</value><value>Getting Better</value><value>Fixing a Hole</value><!-- ...other tracks omitted for brevity... --></list></constructor-arg>
</bean>
其中,<list>
元素是<constructor-arg>
的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>
元素用来指定列表中的每个元素。
与之类似,我们也可以使用<ref>
元素替代<value>
,实现bean引用列表的装配
与之类似,我们也可以使用<ref>
元素替代<value>
,实现bean引用列表的装配。例如,假设你有一个Discography
类,它的构造器如下所示:
public Discography(String artist, List<CompactDisc> cds) { ... }
那么,你可以采取如下的方式配置Discography
bean:
<bean id="beatlesDiscography"class="soundsystem.Discography"><constructor-arg value="The Beatles" /><constructor-arg><list><ref bean="sgtPeppers" /><ref bean="whiteAlbum" /><ref bean="hardDaysNight" /><ref bean="revolver" />...</list></constructor-arg>
</bean>
当构造器参数的类型是java.util.List
时,使用<list>
元素是合情合理的。尽管如此,我们也可以按照同样的方式使用<set>
元素:
<bean id="compactDisc" class="soundsystem.BlankDisc"><constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /><constructor-arg value="The Beatles" /><constructor-arg><set><value>Sgt. Pepper's Lonely Hearts Club Band</value><value>With a Little Help from My Friends</value><value>Lucy in the Sky with Diamonds</value><value>Getting Better</value><value>Fixing a Hole</value><!-- ...other tracks omitted for brevity... --></set></constructor-arg>
</bean>
<set>
和<list>
元素的区别不大,其中最重要的不同在于当Spring创建要装配的集合时,所创建的是java.util.Set
还是java.util.List
。如果是Set
的话,所有重复的值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况下,<set>
或<list>
都可以用来装配List、Set
甚至数组。
在装配集合方面,<constructor-arg>
比c-命名空间的属性更有优势。目前,使用c-命名空间的属性无法实现装配集合的功能。
使用<constructor-arg>
和c-命名空间实现构造器注入时,它们之间还有一些细微的差别。但是到目前为止,我们所涵盖的内容已经足够了,尤其是像我之前所建议的那样,要首选基于Java的配置而不是XML。因此,与其不厌其烦地花费时间讲述如何使用XML进行构造器注入,还不如看一下如何使用XML来装配属性。
2.4.4 设置属性
到目前为止,CDPlayer
和BlankDisc
类完全是通过构造器注入的,没有使用属性的Setter方法。接下来,我们就看一下如何使用Spring XML实现属性注入。假设属性注入的CDPlayer
如下所示:
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import soundsystem.CompactDisc;
import soundsystem.MediaPlayer;public class CDPlayer implements MediaPlayer {private CompactDisc compactDisc;@Autowiredpublic void setCompactDisc(CompactDisc compactDisc) {this.compactDisc = compactDisc;}public void play() {compactDisc.play();}
}
该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。按照这个规则,我们可以说对于BlankDisc
来讲,唱片名称、艺术家以及磁道列表是强依赖,因此构造器注入是正确的方案。不过,对于CDPlayer
来讲,它对CompactDisc
是强依赖还是可选性依赖可能会有些争议。虽然我不太认同,但你可能会觉得即便没有将CompactDisc
装入进去,CDPlayer
依然还能具备一些有限的功能。
现在,CDPlayer
没有任何的构造器(除了隐含的默认构造器),它也没有任何的强依赖。因此,你可以采用如下的方式将其声明为Spring bean:
<bean id="cdPlayer"class="soundsystem.CDPlayer" />
Spring在创建bean的时候不会有任何的问题,但是CDPlayerTest
会因为出现NullPointerException
而导致测试失败,因为我们并没有注入CDPlayer
的compactDisc
属性。不过,按照如下的方式修改XML,就能解决该问题:
<bean id="cdPlayer"class="soundsystem.CDPlayer"><property name="compactDisc" ref="compactDisc" />
</bean>
<property>
元素为属性的Setter方法所提供的功能与<constructor-arg>
元素为构造器所提供的功能是一样的。在本例中,它引用了ID为compactDisc
的bean(通过ref
属性),并将其注入到compactDisc
属性中(通过setCompactDisc()
方法)。如果你现在运行测试的话,它应该就能通过了。
我们已经知道,Spring为<constructor
我们已经知道,Spring为元素提供了c-命名空间作为替代方案,与之类似,Spring提供了更加简洁的p-命名空间,作为元素的替代方案。为了启用p-命名空间,必须要在XML文件中与其他的命名空间一起对其进行声明:
<?xml version="1.0" encoding="UTF-8"?>
…
我们可以使用p-命名空间,按照以下的方式装配compactDisc属性:
p-命名空间中属性所遵循的命名约定与c-命名空间中的属性类似。图2.2阐述了p-命名空间属性是如何组成的。
图2.2 借助Spring的p-命名空间,将bean引用注入到属性中
首先,属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref”结尾,这会提示Spring要进行装配的是引用,而不是字面量。
将字面量注入到属性中
属性也可以注入字面量,这与构造器参数非常类似。作为示例,我们重新看一下BlankDisc
bean。不过,BlankDisc
这次完全通过属性注入进行配置,而不是构造器注入。新的BlankDisc
类如下所示:
package soundsystem;
import java.util.List;
import soundsystem.CompactDisc;public class BlankDisc implements CompactDisc {private String title;private String artist;private List<String> tracks;public void setTitle(String title) {this.title = title;}public void setArtist(String artist) {this.artist = artist;}public void setTracks(List<String> tracks) {this.tracks = tracks;}public void play() {System.out.println("Playing " + title + " by " + artist);for (String track : tracks) {System.out.println("-Track: " + track);}}}
现在,它不再强制要求我们装配任何的属性。你可以按照如下的方式创建一个BlankDisc
bean,它的所有属性全都是空的:
<bean id="reallyBlankDisc"class="soundsystem.BlankDisc" />
当然,如果在装配bean的时候不设置这些属性,那么在运行期CD播放器将不能正常播放内容。play()
方法可能会遇到的输出内容是“Playing null by null”,随之会抛出NullPointerException
异常,这是因为我们没有指定任何的磁道。所以,我们需要装配这些属性,可以借助<property>
元素的value
属性实现该功能:
<bean id="compactDisc"class="soundsystem.BlankDisc"><property name="title"value="Sgt. Pepper's Lonely Hearts Club Band" /><property name="artist" value="The Beatles" /><property name="tracks"><list><value>Sgt. Pepper's Lonely Hearts Club Band</value><value>With a Little Help from My Friends</value><value>Lucy in the Sky with Diamonds</value><value>Getting Better</value><value>Fixing a Hole</value><!-- ...other tracks omitted for brevity... --></list></property>
</bean>
在这里,除了使用<property>
元素的value
属性来设置title
和artist
,我们还使用了内嵌的<list>
元素来设置tracks
属性,这与之前通过<constructor-arg>
装配tracks
是完全一样的。
另外一种可选方案就是使用p-命名空间的属性来完成该功能:
<bean id="compactDisc"class="soundsystem.BlankDisc"p:title="Sgt. Pepper's Lonely Hearts Club Band"p:artist="The Beatles"><property name="tracks"><list><value>Sgt. Pepper's Lonely Hearts Club Band</value><value>With a Little Help from My Friends</value><value>Lucy in the Sky with Diamonds</value><value>Getting Better</value><value>Fixing a Hole</value><!-- ...other tracks omitted for brevity... --></list></property>
</bean>
与c-命名空间一样,装配bean引用与装配字面量的唯一区别在于是否带有“-ref”
后缀。如果没有“-ref”
后缀的话,所装配的就是字面量。
但需要注意的是,我们不能使用p-命名空间来装配集合,没有便利的方式使用p-命名空间来指定一个值(或bean引用)的列表。但是,我们可以使用Spring util-
命名空间中的一些功能来简化BlankDisc
bean。
首先,需要在XML中声明util-
命名空间及其模式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsd">...
</beans>
util-
命名空间所提供的功能之一就是<util:list>
元素,它会创建一个列表的bean。借助<util:list>
,我们可以将磁道列表转移到BlankDisc
bean之外,并将其声明到单独的bean之中,如下所示:
<util:list id="trackList"><value>Sgt. Pepper's Lonely Hearts Club Band</value><value>With a Little Help from My Friends</value><value>Lucy in the Sky with Diamonds</value><value>Getting Better</value><value>Fixing a Hole</value><!-- ...other tracks omitted for brevity... -->
</util:list>
现在,我们能够像使用其他的bean那样,将磁道列表bean注入到BlankDisc
bean的tracks
属性中:
<bean id="compactDisc"class="soundsystem.BlankDisc"p:title="Sgt. Pepper's Lonely Hearts Club Band"p:artist="The Beatles"p:tracks-ref="trackList" />
<util:list>
只是util-
命名空间中的多个元素之一。表2.1列出了util-
命名空间提供的所有元素。
在需要的时候,你可能会用到util-
命名空间中的部分成员。但现在,在结束本章前,我们看一下如何将自动化配置、JavaConfig以及XML配置混合并匹配在一起。
表2.1 Spring util-命名空间中的元素
元 素 | 描 述 |
---|---|
<util:constant>
|
引用某个类型的public static 域,并将其暴露为bean
|
util:list
|
创建一个java.util.List 类型的bean ,其中包含值或引用
|
util:map
|
创建一个java.util.Map 类型的bean ,其中包含值或引用
|
util:properties
|
创建一个java.util.Properties 类型的bean
|
util:property-path
|
引用一个bean 的属性(或内嵌属性),并将其暴露为bean
|
util:set
|
创建一个java.util.Set 类型的bean ,其中包含值或引用
|
2.5 导入和混合配置
在典型的Spring应用中,我们可能会同时使用自动化和显式配置。即便你更喜欢通过JavaConfig实现显式配置,但有的时候XML却是最佳的方案。
幸好在Spring中,这些配置方案都不是互斥的。**你尽可以将JavaConfig的组件扫描和自动装配和/或XML配置混合在一起。**实际上,就像在2.2.1小节中所看到的,我们至少需要有一点显式配置来启用组件扫描和自动装配。
关于混合配置,第一件需要了解的事情就是**在自动装配时,它并不在意要装配的bean来自哪里。**自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
你可能会想在显式配置时,比如在XML配置和Java配置中该如何引用bean呢。让我们先看一下如何在JavaConfig中引用XML配置的bean。
2.5.1 在JavaConfig中引用XML配置
现在,我们临时假设CDPlayerConfig
已经变得有些笨重,我们想要将其进行拆分。当然,它目前只定义了两个bean,远远称不上复杂的Spring配置。不过,我们假设两个bean就已经太多了。
我们所能实现的一种方案就是将BlankDisc
从CDPlayerConfig
拆分出来,定义到它自己的CDConfig
类中,如下所示:
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class CDConfig {@Beanpublic CompactDisc compactDisc() {return new SgtPeppers();}
}
compactDisc()
方法已经从CDPlayerConfig
中移除掉了,我们需要有一种方式将这两个类组合在一起。一种方法就是在CDPlayerConfig
中使用@Import
注解导入CDConfig
:
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {@Beanpublic CDPlayer cdPlayer(CompactDisc compactDisc) {return new CDPlayer(compactDisc);}}
或者采用一个更好的办法,也就是不在CDPlayerConfig
中使用@Import
,而是创建一个更高级别的SoundSystemConfig
,在这个类中使用@Import
将两个配置类组合在一起:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {}
不管采用哪种方式,我们都将CDPlayer
的配置与BlankDisc
的配置分开了。现在,我们假设(基于某些原因)希望通过XML来配置BlankDisc
,如下所示:
<bean id="compactDisc"class="soundsystem.BlankDisc"c:_0="Sgt. Pepper's Lonely Hearts Club Band"c:_1="The Beatles"><constructor-arg><list><value>Sgt. Pepper's Lonely Hearts Club Band</value><value>With a Little Help from My Friends</value><value>Lucy in the Sky with Diamonds</value><value>Getting Better</value><value>Fixing a Hole</value><!-- ...other tracks omitted for brevity... --></list></constructor-arg>
</bean>
现在BlankDisc
配置在了XML之中,我们该如何让Spring同时加载它和其他基于Java的配置呢?
答案是@ImportResource
注解,假设BlankDisc
定义在名为cd-config.xml
的文件中,该文件位于根类路径下,那么可以修改SoundSystemConfig
,让它使用@ImportResource
注解,如下所示:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {}
两个bean——配置在JavaConfig中的CDPlayer
以及配置在XML中BlankDisc
——都会被加载到Spring容器之中。因为CDPlayer
中带有@Bean
注解的方法接受一个CompactDisc
作为参数,因此BlankDisc
将会装配进来,此时与它是通过XML配置的没有任何关系。
让我们继续这个练习,但是这一次,我们需要在XML中引用JavaConfig声明的bean。
2.5.2 在XML配置中引用JavaConfig
假设你正在使用Spring基于XML的配置并且你已经意识到XML逐渐变得无法控制。像前面一样,我们正在处理的是两个bean,但事情实际上会变得更加糟糕。在被无数的尖括号淹没之前,我们决定将XML配置文件进行拆分。
在JavaConfig配置中,我们已经展现了如何使用@Import
和@ImportResource
来拆分JavaConfig类。在XML中,我们可以使用import
元素来拆分XML配置。
比如,假设希望将BlankDisc
bean拆分到自己的配置文件中,该文件名为cd-config.xml,这与我们之前使用@ImportResource
是一样的。我们可以在XML配置文件中使用<import>
元素来引用该文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><import resource="cd-config.xml" /><bean id="cdPlayer"class="soundsystem.CDPlayer"c:cd-ref="compactDisc" />
</beans>
现在,我们假设不再将BlankDisc
配置在XML之中,而是将其配置在JavaConfig
中,CDPlayer
则继续配置在XML中。基于XML的配置该如何引用一个JavaConfig类呢?
事实上,答案并不那么直观。<import>
元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。
但是,有一个你已经熟知的元素能够用来将Java配置导入到XML配置中:<bean>
元素。为了将JavaConfig类导入到XML配置中,我们可以这样声明bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="soundsystem.CDConfig" /><bean id="cdPlayer"class="soundsystem.CDPlayer"c:cd-ref="compactDisc" /></beans>
采用这样的方式,两种配置——其中一个使用XML描述,另一个使用Java描述——被组合在了一起。类似地,你可能还希望创建一个更高层次的配置文件,这个文件不声明任何的bean,只是负责将两个或更多的配置组合起来。例如,你可以将CDConfig
bean从之前的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"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="soundsystem.CDConfig" /><import resource="cdplayer-config.xml" /></beans>
不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan>
或@ComponentScan
)。你会在本书的很多例子中看到这种技术。
2.6 小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
在本章中,我们看到了在Spring中装配bean的三种主要方式:**自动化配置、基于Java的显式配置以及基于XML的显式配置。**不管你采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。
我同时建议**尽可能使用自动化配置,以避免显式配置所带来的维护成本。但是,如果你确实需要显式配置Spring的话,应该优先选择基于Java的配置,它比基于XML的配置更加强大、类型安全并且易于重构。**在本书中的例子中,当决定如何装配组件时,我都会遵循这样的指导意见。
ayer"
c:cd-ref=“compactDisc” />
```
采用这样的方式,两种配置——其中一个使用XML描述,另一个使用Java描述——被组合在了一起。类似地,你可能还希望创建一个更高层次的配置文件,这个文件不声明任何的bean,只是负责将两个或更多的配置组合起来。例如,你可以将CDConfig
bean从之前的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"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="soundsystem.CDConfig" /><import resource="cdplayer-config.xml" /></beans>
不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan>
或@ComponentScan
)。你会在本书的很多例子中看到这种技术。
2.6 小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
在本章中,我们看到了在Spring中装配bean的三种主要方式:**自动化配置、基于Java的显式配置以及基于XML的显式配置。**不管你采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。
我同时建议**尽可能使用自动化配置,以避免显式配置所带来的维护成本。但是,如果你确实需要显式配置Spring的话,应该优先选择基于Java的配置,它比基于XML的配置更加强大、类型安全并且易于重构。**在本书中的例子中,当决定如何装配组件时,我都会遵循这样的指导意见。
因为依赖注入是Spring中非常重要的组成部分,所以本章中介绍的技术在本书中所有的地方都会用到。基于这些基础知识,下一章将会介绍一些更为高级的bean装配技术,这些技术能够让你更加充分地发挥Spring容器的威力。
Spring实战(第4版)第2章 装配Bean相关推荐
- 《Spring实战》读书笔记-第4章 面向切面的Spring
<Spring实战>是学习Spring框架的一本非常经典的书籍,之前阅读了这本书,只是在书本上写写画画,最近整理了一下<Spring实战>的读书笔记,通过博客的方式进行记录分享 ...
- 《Spring实战》读书笔记-第3章 高级装配
<Spring实战>是学习Spring框架的一本非常经典的书籍,之前阅读了这本书,只是在书本上写写画画,最近整理了一下<Spring实战>的读书笔记,通过博客的方式进行记录分享 ...
- Spring实战第五版第二章字段校验返回视图报错解决及javax.validation完整pom依赖
Spring实战第五版第二章 2.3 校验表单输入 在编写"/design"页面post处理逻辑时,按照书本上直接'return "design"; '方式,会 ...
- Spring实战(第3版)
<Spring实战(第3版) > 基本信息 原书名:Spring in Actiong(third edition) 作者: (美)Craig Walls 译者: 耿渊 张卫滨 出版社:人 ...
- 《spring实战第四版》的读书笔记
<spring实战第四版>的读书笔记 1 概述 <Spring实战第四版>描述了Spring4架构的设计,看完了以后,最大感觉是Spring的IOC与aop理念实在是太强大了, ...
- Spring实战(第四版)
Spring实战(第四版) 链接:https://pan.baidu.com/s/1PhnJqOsQPz5hqe-zxkqPOg 提取码:eu15 复制这段内容后打开百度网盘手机App,操作更方便哦
- Spring实战第五版源码
Spring实战第五版源码 链接:https://pan.quark.cn/s/11a451b1aa84 提取码:uzGW
- Spring实战(第4版)
Spring实战第四版,在线阅读地址:https://potoyang.gitbook.io/spring-in-action-v4/,根据 pdf 整理完成.
- Spring 实战 第4版 读书笔记
第一部分:Spring的核心 1.第一章:Spring之旅 1.1.简化Java开发 创建Spring的主要目的是用来替代更加重量级的企业级Java技术,尤其是EJB.相对EJB来说,Spring提供 ...
- 第二章 装配Bean(Spring in action,3th)
第二章 装配Bean 创建应用对象之间协作关系的行为通常被称为装配(wiring),是依赖注入的本质. XML方式声明Bean ...
最新文章
- 用DataBindings属性绑定控件的值
- TCP/IP详解--第八章
- IDEA设置自定义代码模板
- 用Golang构建gRPC服务
- SLAM Cartographer(14)Global SLAM的主线业务
- mysql 安装后大_Window下MySql 5.6 安装后内存占用很高的问题
- 【转】Struts2中json插件的使用
- jenkins配置自动发送邮件
- vmlinuz的产生
- JAVA集合和guava集合使用和原理解析
- 小程序的好处是什么?
- chrome axure 插件安装
- 获取文件名,文件名后缀以及elementui多张图片回显
- 使用eNSP模拟华为交换机路由器防火墙创建VLAN实现单臂路由
- Runtime error常见原因总结(各种oj,vjudge等等吧)
- 阿里P8总结的Nacos入门笔记,从安装到进阶小白也能轻松学会
- 在Vue中搭建前端监控日志
- OpenCV学习笔记(六)—— OpenCV for Android打开相机
- wps中,文字编辑换行后空格变大
- mac系统ionic开发ios app从安装到打包