当你希望你的bean在特殊条件下才能装配时,比如在声明了特定的bean时,或者配置了特定的环境变量的时候。那么就可以使用 @Conditional注解,可以用在 @Bean注解下。
比如
@Configuration
public class MagicBean {
@Bean
@Conditional(MagiExistCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
}
public class MagiExistCondition implements Condition{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
//检查magic属性是否存在
return environment.containsProperty("magic");
}
}
我们可以看到 @Conditional(MagiExistCondition.class)中的MagiExistCondition实现了Condition接口,而它只有一个matches方法,只有matches方法返回true的时候,magicBean 才会创建。
下面这个例子用到了选择性注解和条件注解
①、创建接口
条件接口
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnSystemCondition.class}) //这里不再是Import接口,而是Conditional
public @interface ConditionalSystem {
String system();
}
选择接口
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({HelloWorldConfigSelecltor.class}) //HelloWorldConfigSelecltor 自定义的配置类
public @interface HelloWorldSelector {
boolean isLinux() default false;
}
选择配置类
//选择性装配
public class HelloWorldConfigSelecltor implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
Map<String, Object> attributesMap = annotationMetadata.getAnnotationAttributes(HelloWorldSelector.class.getName());
//获取接口中islinux值,根据不同的值装配不同的配置
boolean islinux = (boolean) attributesMap.get("isLinux");
if(islinux) {
return new String[]{HelloWorldConfig.class.getName()};
} else {
return new String[]{HelloWorldConfig2.class.getName()};
}
}
}
条件配置类
public class OnSystemCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> attributesMap = annotatedTypeMetadata.getAnnotationAttributes(ConditionalSystem.class.getName());
System.out.println("------------------?system-->" + attributesMap.get("system"));
//条件配置,返回true是才装配
return "linux".equals(attributesMap.get("system").toString());
}
}
条件配置
@ConditionalSystem(system = "windows") //条件装配
public class HelloWorldConfig {
@Bean
Lfh lfh () {
System.out.println("-------------->初始化");
return new Lfh();
}
}
@ConditionalSystem(system = "linux") //条件装配
public class HelloWorldConfig2 {
@Bean
Lfh lfh () {
System.out.println("-------------->初始化22222222222222");
return new Lfh();
}
}
启动加载
@EnableCaching
@MapperScan("com.example.mapper")
//@HelloWorld //自定义注解 启动时装配
@HelloWorldSelector(isLinux = false) //自定义注解 启动时装配 false ,装配HelloWorldConfig2
@SpringBootApplication
public class SpringbootLearnApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootLearnApplication.class, args);
}
}
启动时isLinux = false,根据选择装配,加载HelloWorldConfig2,而根据HelloWorldConfig2上的注解进行条件判断,linux 等于@ConditionalSystem(system = “linux”) 中的system,返回true。
由于 @Autowired是按类型进行装配的。那么当某个接口有多个实现时,就会抛出NoUniqueBeanDefinitionException。
1、在某个实现类上加上 @Primary注解,表明首选bean。
@Component
@Primary
public class Cake implements Dessert{
}
这种方法有限制。当你在多个实现类上加了@Primary注解,还会出现歧义。
2、@Qualifier
@Autowired
@Qualifier("cake")
private Dessert dessert;
Qualifier的参数就是需要注入的bean的ID。
提到Qualifier,必须谈一下 @Resource。
@Resource属于J2EE,默认是名称进行装配。如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
那么也可以写成
@Resource(name="cake")
private Dessert dessert;
限制: 与 bean的name紧耦合。
创建限定符
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@Qualifier //代表时Qualifier注解
public @interface Soft {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@Qualifier
public @interface Code {
}
添加在需要创建bean的类上
@Component
@Code
@Soft
public class Cake implements Dessert{
}
自动装配
@Autowired
@Code
@Soft
private Dessert dessert;
这样就会低耦合,可以随意重构是实现类。