条件注解
@ComponentScan 包扫描机制
SpringBoot 中在一类上打上 @Component
一类的注解,就能将组件添加到容器中的原理就是依靠 @ComponentScan
包扫描机制实现的。在 SpringBoot 启动类中的 @SpringBootApplication
默认就有该注解,它默认扫描的包位置就是,启动类同级以及下面的路径。
如同这里是可以扫描到 BannerController
, 但是如果将 BannerController 移动到启动类上级,那么启动是正常的,但是访问是报错的:
报错的原因就是不能扫描到上级路径下的组件,导致在容器中找不到 @RestController
,但是如果在启动类上添加一个 @ComponentScan("com.ethan")
,则访问正常:
原因分析:添加了这个注解后就会显示去扫描 "com.ethan" 包以及下面所有路径的组件。
策略模式的几种实现方案
- byname 切换bean name 保证同一接口的实现类只有一个
@Qualifier("beanName")
指定bean- 有选择的只注入一个Bean,注释掉某个 Bean 上的
@Component
注解 - 使用
@Primary
注解
推荐使用第四种方式,当然如果平时就是自己开发的过程中,想使用哪种都行,但是如果是开发了一个复用的库的话,还是建议使用第四种方式,下面是四种方式的分别演示。
目前有三个英雄类,分别是 Hero1
、Hero2
、Hero3
, 都实现了一个ISkill
接口
ISkill
public interface ISkill {
void r();
}
Hero1
@Component
public class Hero1 implements ISkill {
private String name;
private Integer age;
public Hero1(String name, Integer age) {
this.name = name;
this.age = age;
}
public Hero1() {
System.out.println("实例化Hero1对象");
}
@Override
public void r() {
System.out.println("Hero1......");
}
}
Hero2
@Component
public class Hero2 implements ISkill {
@Override
public void r() {
System.out.println("hero2......");
}
}
Hero3
@Component
public class Hero3 implements ISkill {
@Override
public void r() {
System.out.println("hero3......");
}
}
有选择的只注入一个Bean,注释掉某个 Bean 上的@Component
注解
Controller
通过BannerController
进行调用的时候,如果我们需要调用 Hero1
,那么我们可以先将Hero2和Hero3
类上的@Component
注释掉,然后在BannerController
中,通过字段注入的时候,名称就定义为 private ISkill hero1;
@RestController
@RequestMapping("/v1/banner")
public class BannerController {
@Autowired
private ISkill hero1;
private final ISkill hero1;
@GetMapping("/test")
public String test() {
hero1.r();
return "Hello China";
}
}
@Qualifier("beanName")指定bean
如果Hero1、Hero2、Hero3
类上的@Component
注解都还存在的时候,在BannerController
中注入的是,直接使用接口的名称,但是需要使用哪个英雄,就使用@Qualifier()
强制指定。
@RestController
@RequestMapping("/v1/banner")
public class BannerController {
@Autowired
@Qualifier("hero2")
private ISkill iSkill;
@GetMapping("/test")
public String test() {
iSkill.r();
return "Hello China";
}
}
byname 切换bean name 保证同一接口的实现类只有一个
还有一种方式就是在BannerController
中直接修改注入接口的名称,需要哪个英雄,就修改为哪个英雄
@Autowired
@Qualifier("hero2")
private ISkill hero1; // hero2 hero3
// 这类修改为什么名称,那么下面调用方法就使用修改为的名称进行调用
...
hero1.r(); // hero2 hero3
使用@Primary
注解
还有一种就使用@Primary
注解,最推荐的也是这种方式,例如现在我们需要再去扩展一个英雄Hero
,除了有现在Hero1
英雄的技能外,还要有自己的扩展功能,就可以如下实现:
@Component
@Primary
public class Hero3 extends Hero1 implements ISkill {
@Override
public void q() {
System.out.println("Hero3 英雄新增的技能......");
}
}
这样前面代码中都不需要修改,调用的就是Hero3
Conditional条件注解
自定义条件注解需要使用 @Conditional + Condition 接口实现,如下:
public class Hero1Conditional implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String name = conditionContext.getEnvironment().getProperty("hero.conditional");
return "gabriel".equalsIgnoreCase(name);
}
}
@Configuration
public class HeroConfiguration {
@Bean
@Conditional(Hero1Conditional.class)
public ISkill hero1() {
return new Hero1("gabriel", 18);
}
@Bean
@Conditional(Hero2Conditional.class)
public ISkill hero2() {
return new Hero2();
}
@Bean
@Conditional(Hero3Conditional.class)
public ISkill hero3() {
return new Hero3();
}
}