依赖注入
未使用依赖注入
- 这个骑士只能使用剑,不能使用其他武器
- 需要使用其他武器时,只能创建另一个骑士类
- 无法进行单元测试
public class Knight implements Solider{
private class Weapon weapon;
public Knight() {
weapon = new Sword();
}
public void executeTask() {
weapon.attack();
}
}
使用依赖注入
- 骑士使用的武器从外部获取,骑士没有与任何特定的武器发生耦合
- 骑士在执行任务的时候可以灵活地使用各种武器
- 可以轻松地进行单元测试,只需要传入一个mock weapon即可
public class Knight implements Solider {
private class Weapon weapon;
public Knight(Weapon weapon) {
this.weapon = weapon;
}
public void executeTask() {
weapon.attack();
}
}
装配bean
自动装配
自动扫描
public interface CompactDisc {
void play();
}
- @component注解表明该类会作为组件类,并告知Spring要为这个类创建bean,所以没有必要显式的配置SgtPeppersBean
- @Component注解创建的bean的默认ID是类名开头字母小写,也就是sgtPeppers
- 可以自定义beanID,用法:@Component(“beanID”)
@component
public class SgtPeppers implements CompactDisc {
public void play() {
System.out.println("I'm sgtPeppers");
}
}
- 组件扫描默认不启用,我们需要显式配置一下启用组件扫描
- 创建一个配置类,使用@ComponentScan注解
- 配置类会扫描所在包以及子包下所有带有@Component注解的类,并为这些类创建一个bean
- 如果你想将配置类单独放在一个包里面,默认的基础包机制失效,可以手动指定扫描的包,给该注解的basePackages传递想要扫描的包名即可
- basePackageClasses传入具体class对象,会扫描这些类所在的包,可以避免上面的String(String是类型不安全的,写代码容易出bug)
- 问题:SpringBoot是如何开启组件扫描的呢?
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {
}
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}
自动装配
用法
- 使用@Autowired注解来实现自动装配
- @Autowired可以用在任意方法之上,比如构造器方法,setter方法或者普通方法
- 自动装配也能用在属性上
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
@Autowired
public void setCompactDisc(CompactDisc cd) {
this.cd = cd;
}
@Autowired
public void insertDisc(CompactDisc cd) {
this.cd = cd;
}
原理
- 不管是什么方法,Spring都会尝试满足方法参数上所声明的依赖
- @Autowired先按类型查找bean
- 如果找不到则报错,可以声明required=false,这样找不到时,Spring会让这个bean处于未装配状态,容易报空指针异常
- 如果找到多个bean,需要借助其他注解来解决自动装配中的歧义性
通过Java代码装配Bean
- 使用场景:将第三方库的组件装配到你的应用中时,你是没办法在它的类上添加@Component和@Autowired注解的
- 声明简单的bean,如下:
- @Configuration注解表明这是一个配置类
- @Bean会告诉Spring该方法会返回一个对象,该对象要注册为Spring容器中的bean
- 默认情况下beanID与带有@Bean注解的方法名时一样的,也可以用@Bean注解中的name属性指定一个不同的beanID
@Configuration
public class CDPlayerConfig{
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}
- 声明复杂的Bean:CDPlayer,CDPlayer依赖sgtPeppers
- 方式一:因为sgtPeppers()方法上添加了@Bean,Spring会拦截所有对它的调用,并确保直接返回该方法所创建的Bean,这种方式的限制是sgtPeppers()方法和cdPlayer()方法必须放在同一个配置类中
- 方式二:当Spring调用cdPlayer()方法创建一个bean时,它会自动装配一个CompactDisc,这种方式的好处是CompactDisc这个bean如何被注入到Spring的没有限制
- 方式三:使用setter方法注入,可以看到使用带有@Bean注解的方法注入bean时,可以采用任何的java代码来创建bean,方法参数中需要的bean,Spring都会自动传递进来
@Configuration
public class CDPlayerConfig{
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
CDPlayer cdPlayer = new CDPlayer();
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
}
通过XML装配Bean(省略)
自动装配的歧义性
演示歧义性
演示一:实现
- 自动装配bean时,容器中如果找到多个类型相同的bean会阻碍Spring的自动装配,代码如下:
public interface Dessert {}
@Component
public class Cookies implements Dessert {
}
@Component
public class Cake implements Dessert {
}
@Component
public class IceCream implements Dessert {
}
- 依赖注入
- 方式一:注入的Dessert类型的Bean在容器中可以找到三个:Cookies, IceCream, Cake,自动装配失败
- 方式二:如果把注入的类型改成Dessert的子类,自动装配成功
@Component
public class Kitchen {
private Dessert dessert;
@Autowired
public void dessert(Dessert dessert) {
this.dessert = dessert;
}
public void dessert(Cake dessert) {
this.dessert = dessert;
}
}
演示二:继承
- 把实现改成继承,方式一的注入依然报错;方式二的注入依然成功
- 如果在父类中也加上@Component
- 上述方式一自动装配成功
- 但是,通过ApplicationContext.getBean(Dessert.class)方法获取bean报错,提示有四个相同类型的bean:Dessert,Cookies,Cake和IceCream
@Component
public class Dessert {}
@Component
public class Cookies extends Dessert {
}
@Component
public class Cake extends Dessert {
}
@Component
public class IceCream extends Dessert {
}
解决歧义性
标示首选的bean
- 有效配置
- @Primary能够与@Component组合用在组件扫描的bean上
- @Primary也能够与@Bean组合用在java配置的bean声明中
- 在xml配置中,<
bean
>标签有一个primary属性用来指定首选的bean,设置为true即可
- 无效配置
限定自动装配的bean
- @Primary只是标示了一个优先的可选方案,当优先可选方案失效(同上),装配依旧失败
- @Qualifier(“beanId”),默认的限定符是beanId
- @Component默认beanId是类名小写,也可指定beanId:@Component(“beanId”)
- @Bean默认beanId是方法名小写,也可指定beanId:@Bean(“beanId”)
- @Qualifier可以为bean自定义限定符,这样在重构代码(修改类名)的时候,就不会报错
@Component
@Qualifier("cold")
public class IceCream implements Dessert {}
@Bean
@Qualifier("cold")
pubic Dessert iceCream() {
return new IceCream();
}
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}