《Spring 实战》 读书笔记
第二章 装配Bean
创建应用对象之间关联关系的传统方法(通过构造器或者查找)通常会导致结构复杂的代码,这些代码
很难被服用也很难进行单元测试。如果情况不严重的话,这些对象所做的事情只是超出了它所应该做
的范围;而最坏的情况则是,这些对象彼此之间高度耦合,难以复用和测试。
在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反容器负责把需要相互协作的对象
引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证
组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。
什么是装配?什么是依赖注入的本质?
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
2.1Spring配置的可选方案
Spring装配bean的三种主要的装配机制:
1.在XML中进行显式配置;
2.在Java中进行显式配置;
3.隐式的bean发现机制和自动装配。
推荐使用的装配机制:
建议尽可能地使用自动装配的机制。显式配置越少越好;当必须要显式配置bean的时候(比如,有些
源码不是由你来维护的,而当你需要为这些代码配置bean的时候),推荐使用类型安全并且比XML更加
强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且JavaConfig中没有同样的实现时,才应该使用XML。
2.2自动化装配bean
2.2.1创建可被发现的bean
Spring从两个角度来实现自动化装配:
1.组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
2.自动装配(autowiring):Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。
@Component注解和@ComponentScan注解:
类上使用@Component注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
组件扫描默认是不启用的,需要显式配置一下Spring。
在类上使用@ComponentScan注解,能够在Spring中启动组件扫描。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。(例子,因为
CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,
查找带有@Component注解的类。)
使用XML来开启组件扫描:
可以使用Spring context命名空间的<context:component-scan>元素。
<context:component-scan base-package="soundsystem"/>
测试例子:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerText{
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull(){
//简单的测试方法断言cd属性是否为null
assertNotNull(cd);
}
}
使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。
注解@ContextConfiguration会告诉他需要在CDPlayerConfig中加载配置。因为CDPlayerConfig
类中包含了@ComponentScan,因此最终的应用上下文中应该包含CompactDisc bean。
带有@Autowired注解,以便于将CompactDisc bean注入到测试代码之中。
2.2.2为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。如果不明确指定bean的ID,Spring会根据类名将类名的第一个字母编程小写为其指定一个ID。
如果想为这个bean设置不同的ID,有两种方法:
1.将期望的ID作为值传递给@Component注解。
例如:
@Component("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
...
}
2.不使用@Component注解,使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID:
例如:
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc{
...
}
Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,他们是可以相互替换的。(多用@Component)
2.2.3设置组件扫描的基础包
设置组件扫描基础包的三种方式:
1.在@ComponentScan的value属性中指明包的名称:
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig{}
2.如果想要更加清晰地表明你所设置的是基础包,可以通过basePackages属性进行配置:
@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig{}
如果设置多个基础包,只需要将basePackages属性设置为要扫描包的一个数组即可:
@Configuration
@ComponentScan(basePackages={"soundsystem","video"})
public class CDPlayerConfig{}
上面的例子中,所设置的基础包是以String类型表示的。这种方法类型不安全,如果重构代码的
话,那么基础包就会出现错误了。
3.@ComponentScan提供将其指定为包中所包含的类或接口:
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.calss,DVDPlayer.class})
public class CDPlayerConfig{}
为basePackageClasses属性所设置的数组包含了类,这些类所在的包将会作为组件扫描的基础包。
2.2.4通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
@Autowired注解:
@Component
public class CDPlayer implements MediaPlayer{
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
public void play(){
cd.play();
}
}
在其构造器上添加了@Autowired注解,这表明Spring创建CDPlayer bean的时候,会通过这个构造
器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如说,如果CDPlayer有
一个setCompactDisc()方法,那么可以采用如下注解形式进行自动装配:
@Autowired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
@Autowired注解可以用在类的任何方法上。
假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。如果没有匹配的bean,
那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将
@Autowired的required属性设置为false:
@Autowired(required=false)
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
把required属性设置为false时,你需要谨慎对待,如果代码中没有进行null检查的话,这个处于未
装配状态的属性有可能会出现NullPointerException。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个
bean进行自动装配。产生了自动中配中的歧义性。
@Inject注解和@Autowired注解:
@Autowired注解是Spring特有的注解。可以使用@Inject注解进行替换。
@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named注解,在自动装配中,
Spring同时支持@Inject和@Aurowired。尽管@Inject和@Autowired之间有着一些细微的差别
,但是在大多数场景下,他们都是可以互相替换的。
2.3通过Java代码装配bean
如果想要将第三方库中的组件装配到你的应用中,这种情况下,没有办法在它的类上添加@Component和@Autowired注解,因此不能使用自动化装配的方案。需要进行显式配置。
显式配置的两种可选方案:
1.Java
2.XML
2.3.1创建配置类
@Configuration注解:
@Configuration
public class CDPlayerConfig{
}
创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类
是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
2.3.2声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。例如:
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。如果想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
2.3.3借助JavaConfig实现注入
在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如:
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
看起来,CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。因为sgtPeppers
()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean
,而不是每次都对其进行实际的调用。例如:
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer(){
return new CDPlayer(sgtPeppers());
}
Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在
调用sgtPeppers()时所创建的CompactDisc bean。因此,两个CDPlayer bean会得到相同的
SgtPeppers实例。
通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
这里,不用明确引用CompactDisc的@Bean方法。
通过这种方式应用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个
配置类之中。
带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。
2.4通过XML装配bean
2.4.1创建XML配置规范
在使用JavaConfig的时候,意味着要创建一个带有@Configuration注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以元素为根。
是该模式中的一个元素,它是所有Spring配置文件的根元素。
2.4.2声明一个简单的
元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDisc bean:
<bean class="soundsystem.SgtPeppers" />
借助id属性更为常用:
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
2.4.3借助构造器注入初始化bean
在Spring XML配置中,只有一种声明bean的方式:使用元素并指定class属性。Spring会从这里获取必要的信息来创建bean。
但是,在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:
1.<constructor-arg>元素
2.使用Spring3.0所引入的c-命名空间
2.5导入和混合配置
2.5.1在JavaConfig中引用XML配置
1.JavaConfig引用JavaConfig
一种方法是CDPlayerConfig中使用@Import注解导入CDConfig:
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig{
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
或者采用一种更好的方法,创建一个更高级别的SoundSystemConfig,在这个类中使用@Import将
两个配置类组合在一起:
@Configuration
@Import({CDPlayerConfig.class,CDConfig.class})
public class SoundSystemConfig{
}
2.在JavaConfig中引用XML
使用@ImportResource注解:
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig{
}
2.5.2在XML配置中引用JavaConfig
在XML中,我们可以使用元素来拆分XML配置。
元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。
元素能够用来将Java配置导入到XML配置中:
<bean class="soundsystem.CDConfig" />
<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
也可以使用第三个配置文件将这两个组合在一起:
<bean class="soundsystem.CDConfig" />
<import resource="cdplayer-config.xml" />
不管使用JavaConfig还是使用XML进行装配,通常都会创建一个根配置(rootconfiguration),这个配置会将两个或更多的配置类和/或XML文件组合起来。在根配置中启用组件扫描(通过context:component-scan或@ComponentScan)。
2.6小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证他们的依赖能够得到满足,这样的话,组件才能完成预订的任务。
Spring中装配bean的三种主要方式:
1.自动化配置
2.基于Java的显式配置
3.基于XML的显式配置
建议尽可能使用自动化配置,以避免显式配置所带来的的维护成本。但是,如果确实需要显式配置Spring的话,应该优先选择基于Java的配置,它比基于XML的配置更加强大、类型安全并且易于重构。