Spring Bean 的命名

1、概览

当有多个相同类型的实现时,需要对 Spring Bean 进行不同的命名。这是因为如果 Bean 没有唯一的名称,Spring 在注入 Bean 时会出现歧义。

通过控制 Bean 的命名,可以告诉 Spring 我们想将哪个 Bean 注入到目标对象中。

本文将带你了解 Spring Bean 命名策略,以及如何为同一类型的 Bean 赋予多个名称。

2、默认 Bean 命名策略

Spring 为创建 Bean 提供了多种注解,可以在不同的级别使用。例如,可以在 Bean 类上放置一些注解,在创建 Bean 的方法上放置另一些注解。

首先,来看看 Spring 的默认命名策略。当只指定注解而不指定任何值时,Spring 是如何命名 Bean 的?

2.1、类级注解

首先从在类级别使用的注解的默认命名策略开始。Spring 会使用类名为 Bean 命名,并将第一个字母转换为小写。

举个例子:

@Service
public class LoggingService {
}

如上,Spring 为 LoggingService 类创建了一个 Bean,并使用 loggingService 名称进行了注册。

这种默认的命名策略适用于所有用于创建 Spring Bean 的类级别注解,例如 @Component@Service@Controller

2.2、方法级注解

Spring 提供了 @Bean@Qualifier 等注解,可用于创建 Bean 的方法。

让我们通过一个示例来了解 @Bean 注解的默认命名策略:

@Configuration
public class AuditConfiguration {
    @Bean
    public AuditService audit() {
          return new AuditService();
    }
}

在这个配置类中,Spring 以 audit 为名注册了一个 AuditService 类型的 Bean。当在方法上使用 @Bean 注解时,Spring 会将方法名用作 Bean 名。

还可以在方法上使用 @Qualifier 注解,如下。

3、自定义 Bean 的命名

当需要在同一个 Spring Context 中创建多个相同类型的 Bean 时,可以为 Bean 自定义名称,并使用这些名称来引用它们。

来看看如何为 Spring Bean 自定义名称:

@Component("myBean")
public class MyCustomComponent {
}

这一次,Spring 将创建 MyCustomComponent 类型的 Bean,并命名为 myBean

由于我们显式地给 Bean 赋予了名称,Spring 将使用这个名称,然后就可以用它来引用或访问 Bean 了。

@Component("myBean") 类似,也可以使用 @Service("myService")@Controller("myController")@Bean("myCustomBean") 等其他注解指定名称,然后 Spring 将以给定的名称注册 Bean。

4、使用 @Bean 和 @Qualifier 命名 Bean

4.1、@Bean 的 value 值

如前所述,@Bean 注解应用于方法级别,默认情况下,Spring 将方法名称用作 Bean 名称。

这个默认的 Bean 名称可以被覆盖 - 可以使用 @Bean 注解来指定值。

@Configuration
public class MyConfiguration {
    @Bean("beanComponent")
    public MyCustomComponent myComponent() {
        return new MyCustomComponent();
    }
}

此时,当我们要获取 MyCustomComponent 类型的 Bean 时,可以使用 beanComponent 这个名称来引用该 Bean。

Spring @Bean 注解通常在配置类方法中声明。它可以通过直接调用同一类中的其他 @Bean 方法来引用它们。

4.2、@Qualifier 的 value 值

还可以使用 @Qualifier 注解来命名 Bean。

首先,创建一个接口 Animal,它将会由多个类实现:

public interface Animal {
    String name();
}

现在,定义一个实现类 Cat,并为其添加 @Qualifier 注解,其 value 值为 cat

@Component 
@Qualifier("cat") 
public class Cat implements Animal { 
    @Override 
     public String name() { 
        return "Cat"; 
     } 
}

再添加一个 Animal 的实现 Dog,并用 value 值为 dog@Qualifier 对其进行注解:

@Component
@Qualifier("dog")
public class Dog implements Animal {
    @Override
    public String name() {
        return "Dog";
    }
}

现在,编写一个 PetShow 类,在其中注入两个不同的 Animal 实例:

@Service 
public class PetShow { 
    private final Animal dog; 
    private final Animal cat; 

    public PetShow (@Qualifier("dog")Animal dog, @Qualifier("cat")Animal cat) { 
      this.dog = dog; 
      this.cat = cat; 
    }
    public Animal getDog() { 
      return dog; 
    }
    public Animal getCat() { 
      return cat; 
    }
}

PetShow 类中,通过在构造函数参数上使用 @Qualifier 注解,并在每个注解的 value 属性中使用限定的 Bean 名称,注入了 Animal 类型的两个实现。每当我们使用该限定名称时,Spring 就会将具有该限定名称的 Bean 注入到目标 Bean 中。

5、验证 Bean 名称

到目前为止,我们已经看到了为 Spring Bean 命名的不同示例。现在的问题是,我们如何验证或测试这一点?

可以通过一个单元测试来验证这种行为:

@ExtendWith(SpringExtension.class)
public class SpringBeanNamingUnitTest {
    private AnnotationConfigApplicationContext context;
    
    @BeforeEach
    void setUp() {
        context = new AnnotationConfigApplicationContext();
        context.scan("com.baeldung.springbean.naming");
        context.refresh();
    }
    
    @Test
    void givenMultipleImplementationsOfAnimal_whenFieldIsInjectedWithQualifiedName_thenTheSpecificBeanShouldGetInjected() {
        PetShow petShow = (PetShow) context.getBean("petShow");
        assertThat(petShow.getCat().getClass()).isEqualTo(Cat.class);
        assertThat(petShow.getDog().getClass()).isEqualTo(Dog.class);
    }

在此 JUnit 测试中,在 setUp 方法中初始化了 AnnotationConfigApplicationContext,该方法用于获取 Bean。

然后,只需使用标准断言验证 Spring Bean 的类。

6、总结

本文介绍了 Spring 中 Bean 默认的命令策略,以及如何自定义 Bean 的命名。还介绍了如何为同一类型的 Bean 赋予多个名称。


Ref:https://www.baeldung.com/spring-bean-names