springboot04:条件注入与装配


springboot04:条件注入与装配

条件注入

多个满足条件时的自动注入

在之前使用spring开发时,我们通常使用自动注入@Autowired来对配置类中注入依赖,有些场景下仅仅靠这个注解不足以让Spring知道到底要注入哪个 bean。默认情况下,@Autowired 按类型装配 Spring Bean。如果容器中有多个相同类型的 bean,则框架将抛出 NoUniqueBeanDefinitionException,以提示有多个满足条件的 bean 进行自动装配。程序无法正确做出判断使用哪一个,如下面的例子:

@Component("fish")
public class Fish implements Animal { // 鱼
    public String format() {
        return "fish";
    }
}

@Component("bear") 
public class Bear implements Animal { // 熊
    public String format() { 
        return "bear";
    }
}

@Component
public class FoodService {
    @Autowired
    private Animal animal; // 此时spring不知道要注入哪一个类? 鱼 or 熊
    
    //todo 
}

此时将会报错,Spring 框架将抛出 NoUniqueBeanDefinitionException。这是因为 Spring 不知道要注入哪个 bean。通常对于这种情况,我们会使用@Qualifier来指定特定的Spring bean来进行注入,例子如下:

@Component
public class FoodService {
    @Autowired
    @Qualifier("bear") // 指定Bear Bean注入
    private Animal animal;
}

@Qualifier

通过将 @Qualifier 注解与我们想要使用的特定 Spring bean 的名称一起进行装配,Spring 框架就能从多个相同类型并满足装配要求的 bean 中找到我们想要的,避免让Spring脑裂。我们需要做的是@Component或者@Bean注解中声明的value属性以确定名称。 其实我们也可以在 Animal 实现类上使用 @Qualifier 注释,而不是在 @Component 或者 @Bean 中指定名称,也能达到相同的效果:

@Component
@Qualifier("fish")
public class Fish implements Animal { // 鱼
    public String format() {
        return "fish";
    }
}

@Component
@Qualifier("bear")
public class Bear implements Animal { // 熊
    public String format() { 
        return "bear";
    }
}

@Primary

还有另一个名为 @Primary 的注解,我们也可以用来发生依赖注入的歧义时决定要注入哪个 bean。当存在多个相同类型的 bean 时,此注解定义了首选项。除非另有说明,否则将使用与 @Primary 注释关联的 bean 。 我们来看一个例子:

@Bean
public Employee tomEmployee() {
    return new Employee("Tom");
}

@Bean
@Primary
public Employee johnEmployee() {
    return new Employee("john");
}

在此示例中,两个方法都返回相同的 Employee类型。Spring 将注入的 bean 是方法 johnEmployee 返回的 bean。这是因为它包含 @Primary 注解。当我们想要指定默认情况下应该注入特定类型的 bean 时,此注解很有用。

如果我们在某个注入点需要另一个 bean,我们需要专门指出它。我们可以通过 @Qualifier 注解来做到这一点。例如,我们可以通过使用 @Qualifier 注释来指定我们想要使用 tomEmployee 方法返回的 bean 。 值得注意的是,如果 @Qualifier@Primary 注释都存在,那么 @Qualifier 注释将具有优先权

基本上,@Primary 是定义了默认值,而 @Qualifier 则非常具体。 当然@Component 也可以使用@Primary 注解。例子如下:

@Component
@Primary
public class Fish implements Animal { // 鱼
    public String format() {
        return "fish";
    }
}

@Component
public class Bear implements Animal { // 熊
    public String format() { 
        return "bear";
    }
}

通过名称来自动注入

在使用 @Autowired 进行自动装配时,如果 Spring 没有其他提示,将会按照需要注入的变量名称来寻找合适的 bean。也可以解决依赖注入歧义的问题。让我们看一些基于我们最初的例子的代码:

@Component
public class FoodService {
    @Autowired
    private Animal fish; // 通过名称来注入

    //todo 
}

在这种情况下,Spring 将确定要注入的 beanfish,因为字段名称与我们在该 bean@Component或者 @Bean 注解中使用的值(默认 @Bean 使用方法名)相匹配。

条件装配

@Profile

Spring为应用程序准备了Profile这一概念,用来表示不同的环境。例如,我们分别定义开发、测试和生产这3个环境:

  • native
  • test
  • production

创建某个Bean时,Spring容器可以根据注解@Profile来决定是否创建。例如,以下配置:

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Profile("!test")
    ZoneId createZoneId() {
        return ZoneId.systemDefault();
    }

    @Bean
    @Profile("test")
    ZoneId createZoneIdForTest() {
        return ZoneId.of("America/New_York");
    }
}

如果当前的Profile设置为test,则Spring容器会调用createZoneIdForTest()创建ZoneId,否则,调用createZoneId()创建ZoneId。注意到@Profile("!test")表示非test环境。

在运行程序时,加上JVM参数-Dspring.profiles.active=test就可以指定以test环境启动。

实际上,Spring允许指定多个Profile,例如:

-Dspring.profiles.active=test,master

可以表示test环境,并使用master分支代码。

要满足多个Profile条件,可以这样写:

@Bean
@Profile({ "test", "master" }) // 满足test或master
ZoneId createZoneId() {
    ...
}

Spring Boot对Profiles的支持在于,可以在application.yml中为每个环境进行配置。下面是一个示例配置:

spring:
  application:
    name: ${APP_NAME:unnamed}
  datasource:
    url: jdbc:hsqldb:file:testdb
    username: sa
    password:
    dirver-class-name: org.hsqldb.jdbc.JDBCDriver
    hikari:
      auto-commit: false
      connection-timeout: 3000
      validation-timeout: 3000
      max-lifetime: 60000
      maximum-pool-size: 20
      minimum-idle: 1

pebble:
  suffix:
  cache: false

server:
  port: ${APP_PORT:8080}

---

spring:
  profiles: test

server:
  port: 8000

---

spring:
  profiles: production

server:
  port: 80

pebble:
  cache: true

注意到分隔符---,最前面的配置是默认配置,不需要指定Profile,后面的每段配置都必须以spring.profiles: xxx开头,表示一个Profile。上述配置默认使用8080端口,但是在test环境下,使用8000端口,在production环境下,使用80端口,并且启用Pebble的缓存。

@Conditional

使用Profile能根据不同的Profile进行条件装配,但是Profile控制粒度比较糙,如果想要精细控制,例如,配置本地存储,AWS存储和阿里云存储,将来很可能会增加Azure存储等,用Profile就很难实现。

根据条件创建bean

除了根据@Profile条件来决定是否创建某个Bean外,Spring还可以根据@Conditional决定是否创建某个Bean。@Conditional 通常作用于配置类上(@Configuration),作用于配置类的@Bean方法。

下面来自定义一个根据Condition来实现自动装配bean的实例。

首先创建一个基础的bean对象,在Configuration类中建立两个名字不同bean方法,编写一个继承Condition的类,然后在Configuration类中进行条件配置。

public class Object01 {
    private Integer i1;
    private String s1;
    ... setter、getter、toString 方法
}
@Configuration
public class MyConfiguration {
    @Bean("Object01_1")
    public Object01 object011(){
        return new Object01();
    }
    //如果满足ConditionOnMissingBean的条件,才会创建Object01_2这个Bean。
    @Bean("Object01_2")
    @Conditional({ConditionOnMissingBean.class}) 
    public Object01 object012(){
        return new Object01();
    }
}
// 当Object01_1已经创建则不创建Object01_2这个bean
public class ConditionOnMissingBean implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        BeanDefinitionRegistry registry = context.getRegistry();
        // 如果Object01_1这个bean已经创建,则matches返回false
        return !registry.isBeanNameInUse("Object01_1");
    }
}
// 控制类,检验结果
@Controller
public class MyController {
    @Autowired
    private ApplicationContext Ioc;
    @RequestMapping("/r1")
    @ResponseBody
    public String r1(){
        System.out.println(Ioc.containsBean("Object01_1"));//查看容器是否创建了Object01_1
        System.out.println(Ioc.containsBean("Object01_2"));//查看容器是否创建了Object01_2
        return "r1";
    }
}

常见的Condition类

在org.springframework.boot.autoconfigure包下

  • @ConditionalOnProperty:如果配置文件中有指定的配置,条件生效;
    • name为key,havingValue为value
    • prefix为属性名的前缀部分,value为属性名最后一部分,matchIfMissing默认为false
      • 当matchIfMissing设定为true,则无论属性名是否设定都返回true(不影响havingValue)
  • @ConditionalOnBean:如果有指定的Bean,条件生效;
  • @ConditionalOnClass:如果有指定的Class,条件生效;
  • @ConditionalOnMissingBean:如果没有指定的Bean,条件生效;
  • @ConditionalOnMissingClass:如果没有指定的Class,条件生效;
  • @ConditionalOnWebApplication:在Web环境中条件生效;
  • @ConditionalOnExpression:根据表达式判断条件是否生效。

以上使用方法见这:使用Conditional

参考资料

Spring 注解 @Qualifier 详细解析

使用条件装配

使用Conditional


文章作者: 银色回廊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 银色回廊 !
评论
  目录