Spring Boot:@Bean 和 @Qualifier 注解的使用


#Spring Boot#


代码中使用了 Lombok 和 Slf4j,关于这两个库的具体使用,可以参考:

示例1:使用 @Bean 定义 spring bean

自动装配,默认是根据类型注入的。

demo01 项目结构:

demo01
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AppConfig.java
    │   │       ├── Demo01Application.java
    │   │       └── model
    │   │           ├── Book.java
    │   │           └── Person.java
    │   └── resources
    └── test
        ├── java
        └── resources

Book 类:

package demo.model;

public class Book {
    public String title;
}

Person 类:

package demo.model;

public class Person {

    public String name;
    public Book book;
}

Spring容器会去管理使用@Bean注解的函数返回的类。@Bean要和@Configuration配合使用

AppConfig 类:

package demo;

import demo.model.Book;
import demo.model.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public Book getBook() {
        Book book = new Book();
        book.title = "书名";
        return book;
    }

    @Bean
    public Person getPerson(Book book) {  // 参数book也会自动注入
        Person person = new Person();
        person.name = "人名";
        person.book = book;
        return person;
    }

}

主类 Demo01Application:

package demo;

import demo.model.Book;
import demo.model.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j // 这是 lombok 提供的一个注解,lombok 会向 Demo01Application 类中注入一个 log 变量
public class Demo01Application implements CommandLineRunner {

    @Autowired
    private Book theBook;

    @Autowired
    private Person thePerson;

    @Override
    public void run(String... args) throws Exception {
        log.info("theBook.title: {}", theBook.title);
        log.info("thePerson.name: {}", thePerson.name);
        log.info("thePerson.theBook.title: {}", thePerson.book.title);
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo01Application.class, args);
    }

}

执行结果:

2018-08-01 07:37:56.612  INFO 77531 --- [           main] demo.Demo01Application                   : theBook.title: 书名
2018-08-01 07:37:56.612  INFO 77531 --- [           main] demo.Demo01Application                   : thePerson.name: 人名
2018-08-01 07:37:56.612  INFO 77531 --- [           main] demo.Demo01Application                   : thePerson.theBook.title: 书名

注意,上面的代码中我们通过下面的方式声明一个依赖 book 的 Person 类型的 bean:

@Bean
public Person getPerson(Book book) {  // 参数book也会自动注入
    Person person = new Person();
    person.name = "人名";
    person.book = book;
    return person;
}

对于这段代码,也可以这样实现:

@Bean
public Person getPerson() {
    Person person = new Person();
    person.name = "人名";
    person.book = getBook();  // 直接调用 getBook 方法
    return person;
}

@Bean 修饰的方法得到的bean默认得到的是单例;直接调用 @Bean 修饰的方法时,Spring 会做拦截,让其返回的结果保证是单例。

示例2:一个错误的示例

这个示例运行会报错。为什么?定义了多个返回同一类型的Bean,Spring 注入时,不知道该使用哪个Bean。

项目结构:

demo02
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AppConfig.java
    │   │       ├── Demo02Application.java
    │   │       └── model
    │   │           ├── Book.java
    │   │           └── Person.java
    │   └── resources
    └── test
        ├── java
        └── resources

Book 类和 Person 类实现和示例1相同。

AppConfig 类:

package demo;

import demo.model.Book;
import demo.model.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

	// 定义了两个返回Book类型的Bean
    
    @Bean
    public Book getBook01() {
        Book book = new Book();
        book.title = "石头记";
        return book;
    }

    @Bean
    public Book getBook02() {
        Book book = new Book();
        book.title = "红楼梦";
        return book;
    }
	
    // 定义了两个返回Person类型的Bean
    @Bean
    public Person getPerson01(Book book) {
        Person person = new Person();
        person.name = "曹夢阮";
        person.book = book;
        return person;
    }

    @Bean
    public Person getPerson02(Book book) {
        Person person = new Person();
        person.name = "曹雪芹";
        person.book = book;
        return person;
    }

}

主类 Demo02Application:

package demo;

import demo.model.Book;
import demo.model.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 会报错
 */
@SpringBootApplication
@Slf4j
public class Demo02Application implements CommandLineRunner {

    @Autowired
    private Book theBook;

    @Autowired
    private Person thePerson;

    @Override
    public void run(String... args) throws Exception {
        log.info("theBook.title: {}", theBook.title);
        log.info("thePerson.name: {}", thePerson.name);
        log.info("thePerson.theBook.title: {}", thePerson.book.title);
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo02Application.class, args);
    }

}

运行时会报错:

Field theBook in demo.Demo02Application required a single bean, but 2 were found:
	- getBook01: defined by method 'getBook01' in class path resource [demo/AppConfig.class]
	- getBook02: defined by method 'getBook02' in class path resource [demo/AppConfig.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

它提示我们可以用 @Primary 或者 @Qualifier 来解决。@Primary 就不说了,比较简单。我们看下 @Qualifier

示例3:使用 @Qualifier 区分使用同类型的多个 bean

@Bean注解可以声明一个标识。而@Qualifier既可以声明标识,也可以指明使用哪个标识的Bean。

这个示例,用@Bean注解声明一个标识,用@Qualifier指明使用哪个标识的Bean。

项目结构:

demo03
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AppConfig.java
    │   │       ├── Demo03Application.java
    │   │       └── model
    │   │           ├── Book.java
    │   │           └── Person.java
    │   └── resources
    └── test
        ├── java
        └── resources

类 AppConfig :

package demo;

import demo.model.Book;
import demo.model.Person;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(name="石头记")
    public Book getBook01() {
        Book book = new Book();
        book.title = "石头记";
        return book;
    }

    @Bean(name="红楼梦")
    public Book getBook02() {
        Book book = new Book();
        book.title = "红楼梦";
        return book;
    }

    @Bean(name="曹夢阮")
    public Person getPerson01(@Qualifier("石头记") Book book) {
        Person person = new Person();
        person.name = "曹夢阮";
        person.book = book;
        return person;
    }

    @Bean(name="曹雪芹")
    public Person getPerson02(@Qualifier("红楼梦") Book book) {
        Person person = new Person();
        person.name = "曹雪芹";
        person.book = book;
        return person;
    }

}

主类 Demo03Application :

package demo;

import demo.model.Book;
import demo.model.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class Demo03Application implements CommandLineRunner {

    @Autowired
    @Qualifier("石头记")
    private Book book;

    @Autowired
    @Qualifier("曹雪芹")
    private Person person;

    @Override
    public void run(String... args) throws Exception {
        log.info("book.title: {}", book.title);
        log.info("person.name: {}", person.name);
        log.info("person.book.title: {}", person.book.title);
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo03Application.class, args);
    }

}

运行结果:

2018-08-01 08:37:56.032  INFO 77719 --- [           main] demo.Demo03Application                   : book.title: 石头记
2018-08-01 08:37:56.032  INFO 77719 --- [           main] demo.Demo03Application                   : person.name: 曹雪芹
2018-08-01 08:37:56.033  INFO 77719 --- [           main] demo.Demo03Application                   : person.book.title: 红楼梦

示例4:使用 @Qualifier 为bean声明标识

这个示例,用@Qualifier注解声明一个标识,也用@Qualifier指明使用哪个标识的Bean。

项目结构:

demo04
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AppConfig.java
    │   │       ├── Demo04Application.java
    │   │       └── model
    │   │           ├── Book.java
    │   │           └── Person.java
    │   └── resources
    └── test
        ├── java
        └── resources

类 AppConfig:

package demo;

import demo.model.Book;
import demo.model.Person;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    @Qualifier("石头记")
    public Book getBook01() {
        Book book = new Book();
        book.title = "石头记";
        return book;
    }

    @Bean
    @Qualifier("红楼梦")
    public Book getBook02() {
        Book book = new Book();
        book.title = "红楼梦";
        return book;
    }

    @Bean
    @Qualifier("曹夢阮")
    public Person getPerson01(@Qualifier("石头记") Book book) {
        Person person = new Person();
        person.name = "曹夢阮";
        person.book = book;
        return person;
    }

    @Bean
    @Qualifier("曹雪芹")
    public Person getPerson02(@Qualifier("红楼梦") Book book) {
        Person person = new Person();
        person.name = "曹雪芹";
        person.book = book;
        return person;
    }

}

主类 Demo04Application:

package demo;

import demo.model.Book;
import demo.model.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class Demo04Application implements CommandLineRunner {

    @Autowired
    @Qualifier("石头记")
    private Book book;

    @Autowired
    @Qualifier("曹雪芹")
    private Person person;

    @Override
    public void run(String... args) throws Exception {
        log.info("book.title: {}", book.title);
        log.info("person.name: {}", person.name);
        log.info("person.book.title: {}", person.book.title);
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo04Application.class, args);
    }

}

运行结果:

2018-08-01 08:42:17.432  INFO 77730 --- [           main] demo.Demo04Application                   : book.title: 石头记
2018-08-01 08:42:17.432  INFO 77730 --- [           main] demo.Demo04Application                   : person.name: 曹雪芹
2018-08-01 08:42:17.432  INFO 77730 --- [           main] demo.Demo04Application                   : person.book.title: 红楼梦

示例5:根据变量名和函数名进行自动装配

上面说到,自动装配,默认是根据类型注入的。其实如果遇到定义了多个返回同一类型的Bean的情况,还会尝试将自动装配的变量名和@Bean注解的函数名进行匹配。

demo05
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── AppConfig.java
    │   │       ├── Demo05Application.java
    │   │       └── model
    │   │           ├── Book.java
    │   │           └── Person.java
    │   └── resources
    └── test
        ├── java
        └── resources
package demo;

import demo.model.Book;
import demo.model.Person;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public Book book01() {
        Book book = new Book();
        book.title = "石头记";
        return book;
    }

    @Bean
    public Book book02() {
        Book book = new Book();
        book.title = "红楼梦";
        return book;
    }

    @Bean
    public Person person01(@Qualifier("book01") Book book) {
        Person person = new Person();
        person.name = "曹夢阮";
        person.book = book;
        return person;
    }

    @Bean
    public Person person02(@Qualifier("book02") Book book) {
        Person person = new Person();
        person.name = "曹雪芹";
        person.book = book;
        return person;
    }

}

package demo;

import demo.model.Book;
import demo.model.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class Demo05Application implements CommandLineRunner {

    @Autowired
    private Book book01;  // 和 public Book book01() 中的函数名对应

    @Autowired
    private Person person02;  // 和 public Person person02(@Qualifier("book02") Book book)  中的函数名对应

    @Override
    public void run(String... args) throws Exception {
        log.info("book01.title: {}", book01.title);
        log.info("person02.name: {}", person02.name);
        log.info("person02.book01.title: {}", person02.book.title);
    }

    public static void main(String[] args) {
        SpringApplication.run(Demo05Application.class, args);
    }

}

更进一步,我们可以将 AppConfig 类找那个的 person01 、person02 函数的参数也做修改:

    @Bean
    public Person person01(Book book01) {
        Person person = new Person();
        person.name = "曹夢阮";
        person.book = book01;
        return person;
    }

    @Bean
    public Person person02(Book book02) {
        Person person = new Person();
        person.name = "曹雪芹";
        person.book = book02;
        return person;
    }

不推荐这种做法。


( 本文完 )