Spring从两个角度来实现自动化装配:
- 组件扫描( component scanning ):Spring会自动发现应用上下文中所创建的 bean。
- 自动装配( autowiring ):Spring自动满足 bean 之间的依赖
1、创建可被发现的bean
创建一个接口CompactDisc:
package soundsystem;
public interface CompactDisc{
void play();
}
创建带有@Component注解的CompactDisc接口的一个实现类,这个类会被Spring扫描到并自动创建bean
package soundsystem;
import ...
@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);
}
}
使用@ComponentScan注解启动组件扫描,@ComponentScan 默认会扫描与配置类相同的包以及其下所有的子包,这样,被@Component注解修饰的类就会被发现
package soundsystem;
import ...
@Configuration
@ComponentScan
public class CDPlayerConfig{
}
也可以使用XML配置的方式来启动组件扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans
...
...
>
<context:component-scan base-package="soundsystem"/>
</beans>
2.为组件扫描的 bean 命名
若直接使用 @Component 注解来声明一个 bean,bean 的名字默认为类名首字母小写。
例如,如下 SgtPeppers 类的默认 bean 名称为 sgtPeppers
@Component()
public class SgtPeppers implements CompactDisc{
...
}
可以在 @Component 注解中说明此 bean 的名称:
@Component("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
...
}
此外,也可以使用另外一种为 bean 命名的方式:
package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
...
}
Spring 支持将 @Named 作为 @Component 注解的替代方案,两者之间有一些细微的差异,但在大多数场景下,它们是可以相互替换的。
推荐使用 @Component 注解,因为 @Named 注解并不能清楚的表明它是做什么的
3.设置组件扫描的基础包
在之前的案列中,我们没有为 @ComponentScan 注解设置任何属性,这意味着,按照默认规则,它会以配置类所在包作为基础包( base package )来扫描组件.
有一个原因会促使我们要明确的设置基础包,那就是我们想要将配置类放在单独的包中,使其与其他的应用代码区分开来。
为了指定不同的包,只需要在 @ComponentScan 的 value 属性中指明包的名称:
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig{
}
或者,如果你想更加明确的表名你所设置的是基础包,那么你可以通过 basePackages 属性进行配置:
@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig{
}
如果你想扫描多个包,只需要将 basePackages 属性的值设置为要扫描包的一个数组即可:
@Configuration
@ComponentScan(basePackages={"soundsystem","video"})
public class CDPlayerConfig{
}
注意到,basePackages 属性的值是一个 String 类型的数组,这样配置没有问题,但却是不安全的,如果要重构代码的话,这些包的名字可能会被修改,从而导致包扫描出现错误,除了将包设置为简单的 String 类型之外,@ComponentScan 还提供了另一种方法,那就是将其指定为要扫描包中所含的类或接口。
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig{
}
你可以在包中设置一个专门用来进行包扫描的空标记接口,这样,可以避免对任何实际应用的代码进行重构后,包扫描出现错误。
4.通过为 bean 添加注解实现自动装配
@Component
public class CDPlayer implements MediaPlayer{
private CompactDisc cd;
@Autowired
public CDPlayer(CompacrDisc cd){
this.cd = cd;
}
public void play(){
cd.play();
}
}
以上示例的构造器上添加了 @Autowired 注解,这表明当 Spring 创建 CDPlayer bean 的时候,会通过这个构造器进行实例化并传入一个 CompactDisc 类型的 bean。
@Autowired 注解不仅能够用在构造器上,还可以用在属性设置的 Setter 方法上。
实际上,Setter 方法并没有什么特殊之处,@Autowired 注解可以用在类的任何方法上:如下,@Autowired 注解完全能够发挥作用
@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;
}
如果有多个 bean 都能满足依赖关系的话,Spring 也将会抛出一个异常,表明没有明确指定要选择那个 bean 进行装配。
处理自动装配的歧义性问题,请参阅我的另一篇文章:Spring. 处理自动装配的歧义性