最可怕的敌人,就是没有坚强的信念。
——罗曼·罗兰
任何一个成功的应用都是由多个为了实现一个业务目标而相互协作的组件构成的。这些组件必须相互了解,并且相互协作来完成工作。创建应用对象之间关联关系的传统方法通常会导致结构复杂的代码,这些代码很难被复用也很难进行单元测试。
在Spring中对象无需自己查找或创建与其关联的其他对象(即所依赖的对象),而是通过容器负责将相互协作的对象引用赋予给各个对象。创建应用之间相互协作关系的行为通常称为装配,这也是DI的本质。
在应用程序中,Spring怎么知道你需要创建的bean并将其装配在一起呢? Spring提供了三种主要的装配机制:
隐式的bean发现机制和自动装配
Spring从两个角度实现自动化装配:
1)组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
2)自动装配(autowaring):Spring自动满足bean之间的依赖。
1.创建可被发现的bean
1)定义一个CD接口,它可将CD播放器的任意实现与CD本身的耦合降低到最小的程度。
package chapter2.practice1; public interface CompactDisc { void play(); }
2)创建CD的实现,需要注意的是@component这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建Bean。
package chapter2.practice1; import org.springframework.stereotype.Component; @Component public class SgtPeppers implements CompactDisc { private String title = "死了都要爱"; private String artist = "阿信"; public void play() { System.out.println("Playing " + title + " by " + artist); } }
3)组件扫描默认是不启用的,我们需要显示配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
package chapter2.practice1; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class CDPlayerConfig { /** * @ComponentScan 在Sring中启用组件扫描。默认会扫描与CDPlayerConfig类相同的包,即Spring将会扫描chapter2包及这个 * 包下的所有子包,查找带有@Component注解的类。 */ }
4)为了测试组件扫描的功能,我们创建一个简单的JUnit测试,它会创建Spring上下文,并判断bean是否被真的创建出来了。
package chapter2.practice1; 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 { @Autowired private CompactDisc cd; @Test public void play() { cd.play(); } }
2.为组建扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID,具体来讲,这个bean的ID就是将雷鸣的第一个字母变成小写。我们也可以通过下面的方式为这个bean指定期望的ID:
1)将期望的ID作为值传递给@Component注解,即@Component("期望的ID值")
2)使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID。
3.设置组件扫描的基础包
按照默认规则,@ComponentScan会以配置类所在的包作为基础包来扫描组件。有一个原因会促使我们明确地设置基础包,那就是我们想要将配置类放在单独的包中,使其与其它应用代码区分开来。如果是这样的话,默认的基础包就不能满足要求了。那么我们怎么指定一个或多个基础扫描包呢?
1)在@ComponentScan的value属性中指定包的名称,但是该方法只能指定一个基础包;
2)在@ComponentScan的basePackages属性中指定一个或多个包的名称;
3)在@ComponentScan的basePackageClasses属性中指定类,这些类所在的包将会作为组件扫描的基础包。
1) 2) 所设置的基础包是以String类型表示的,是类型不安全的,如果重构代码的话,那么所制定的基础包可能会出现错误。3)中设置的是组建类,但是可以考虑在包中创建一个用来进行扫描的空标记接口,这样可以避免代码重构可能出现的错误。
4.通过为bean添加注解实现自动装配
在一个应用程序中,很多对象会依赖其他对象相互协作完成任务,我们需要一种方法能够将组建扫描得到的bean和它们依赖的对象装配在一起,自动装配就是Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。
package chapter2.practice1; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired public void setCd(CompactDisc cd) { this.cd = cd; } @Autowired public CDPlayer(CompactDisc cd) { this.cd = cd; } public void play() { cd.play(); } }
4.1 @Autowired注解能够用在构造器上,还能用在属性的Set方法上,在Spring初始化bean后,它会尽可能的满足bean的依赖。实际上,@Autowired注解可以用在类的任何方法上,Spring都会尝试满足方法参数上所声明的依赖。
1)假如有且只有一个bean匹配依赖需求的话,那么这个bean就会被装配进来;
2)如果没有匹配的bean,那么应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,可以将@Autowired的requested属性设置为false,将其设置为false时,Spring会尝试执行自动装配,如果没有匹配的bean,Spring会让这个bean处于未装配的状态;如果在你的代码中没有进行null检查,那么有可能会出现空指针异常。
3)如果有多个bean能够满足依赖关系,那么Spring会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
4.2 @Autowired是Spring特有的注解,你还可以用@Inject替换它。@Inject注解来源于Java依赖注入规范。