Spring Boot:@Component和@Qualifier的使用


#Spring Boot


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

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

使用@Component注解的类,如果是实现自某个接口,那么@Autowired注解的变量类型,直接用接口类型即可。

demo01 项目结构:

demo01
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── Demo01Application.java
    │   │       └── model
    │   │           ├── IBook.java
    │   │           ├── IPerson.java
    │   │           └── impl
    │   │               ├── BookImpl.java
    │   │               └── PersonImpl.java
    │   └── resources
    └── test
        ├── java
        └── resources

build.gradle:

buildscript {
    ext {
        springBootVersion = '2.1.3.RELEASE'
    }
    repositories {
        maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    compile group: 'org.projectlombok', name: 'lombok', version: '1.18.0'
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

接口 IBook:

package demo.model;

public interface IBook {

    public String getTitle();

}

接口 IPerson:

package demo.model;

public interface IPerson {

    public String getName();

    public IBook getBook();

}

实现类 BookImpl :

package demo.model.impl;

import demo.model.IBook;
import org.springframework.stereotype.Component;

@Component
public class BookImpl implements IBook {

    @Override
    public String getTitle() {
        return "书名";
    }

}

实现类 PersonImpl :

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PersonImpl implements IPerson {

    @Autowired
    IBook book;

    @Override
    public String getName() {
        return "人名";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

主类 Demo01Application:

package demo;

import demo.model.IBook;
import demo.model.IPerson;
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 Demo01Application implements CommandLineRunner {

    @Autowired
    private IBook book;

    @Autowired
    private IPerson person;

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

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

}

执行结果:

2018-08-01 09:34:40.060  INFO 79022 --- [           main] demo.Demo01Application                   : book.title: 书名
2018-08-01 09:34:40.061  INFO 79022 --- [           main] demo.Demo01Application                   : person.name: 人名
2018-08-01 09:34:40.061  INFO 79022 --- [           main] demo.Demo01Application                   : person.book.title: 书名

注意,@Componet 注解修饰的类不一定要实现某接口,例如:

@Component
public class Person {

    @Autowired
    IBook book;

    @Override
    public String getName() {
        return "人名";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

在需要用到 Person 的地方,直接用下面的方式注入即可:

@Autowired
private Person person;

示例2:一个错误的示例

如果同一个接口有多个用@Component 注解的实现类,Spring 注入时,不知道该使用哪个,会报错。

demo02 项目结构:

demo02
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── Demo02Application.java
    │   │       └── model
    │   │           ├── IBook.java
    │   │           ├── IPerson.java
    │   │           └── impl
    │   │               ├── BookImpl01.java
    │   │               ├── BookImpl02.java
    │   │               ├── PersonImpl01.java
    │   │               └── PersonImpl02.java
    │   └── resources
    └── test
        ├── java
        └── resources

实现类 BookImpl01:

package demo.model.impl;

import demo.model.IBook;
import org.springframework.stereotype.Component;

@Component
public class BookImpl01 implements IBook {
    
    @Override
    public String getTitle() {
        return "石头记";
    }

}

实现类 BookImpl02:

package demo.model.impl;

import demo.model.IBook;
import org.springframework.stereotype.Component;

@Component
public class BookImpl02 implements IBook {

    @Override
    public String getTitle() {
        return "红楼梦";
    }

}

实现类 PersonImpl01:

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PersonImpl01 implements IPerson {

    @Autowired
    IBook book;

    @Override
    public String getName() {
        return "曹夢阮";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

实现类 PersonImpl02:

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PersonImpl02 implements IPerson {

    @Autowired
    IBook book;

    @Override
    public String getName() {
        return "曹雪芹";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

主类 Demo02Application:

package demo;

import demo.model.IBook;
import demo.model.IPerson;
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 IBook book;

    @Autowired
    private IPerson person;

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

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

}

运行结果:

Description:

Field book in demo.Demo02Application required a single bean, but 2 were found:
	- bookImpl01: defined in file [/demo02/out/production/classes/demo/model/impl/BookImpl01.class]
	- bookImpl02: defined in file [/demo02/out/production/classes/demo/model/impl/BookImpl02.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

示例3:使用@Component时声明表示,使用 @Qualifier 区分使用同类型的多个 bean

我们想办法解决示例2遇到的问题。

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

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

demo03 项目结构:

demo03
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── Demo03Application.java
    │   │       └── model
    │   │           ├── IBook.java
    │   │           ├── IPerson.java
    │   │           └── impl
    │   │               ├── BookImpl01.java
    │   │               ├── BookImpl02.java
    │   │               ├── PersonImpl01.java
    │   │               └── PersonImpl02.java
    │   └── resources
    └── test
        ├── java
        └── resources

实现类 BookImpl01:

package demo.model.impl;

import demo.model.IBook;
import org.springframework.stereotype.Component;

@Component(value = "石头记")
public class BookImpl01 implements IBook {
    
    @Override
    public String getTitle() {
        return "石头记";
    }

}

实现类 BookImpl02:

package demo.model.impl;

import demo.model.IBook;
import org.springframework.stereotype.Component;

@Component("红楼梦")
public class BookImpl02 implements IBook {

    @Override
    public String getTitle() {
        return "红楼梦";
    }

}

实现类 PersonImpl01:

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("曹夢阮")
public class PersonImpl01 implements IPerson {

    @Autowired
    @Qualifier("石头记")
    IBook book;

    @Override
    public String getName() {
        return "曹夢阮";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

实现类 PersonImpl02:

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("曹雪芹")
public class PersonImpl02 implements IPerson {

    @Autowired
    @Qualifier("红楼梦")
    IBook book;

    @Override
    public String getName() {
        return "曹雪芹";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

主类 Demo03Application:

package demo;

import demo.model.IBook;
import demo.model.IPerson;
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 IBook book;

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

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

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

}

运行结果:

2018-08-01 09:48:51.364  INFO 79133 --- [           main] demo.Demo03Application                   : book.title: 石头记
2018-08-01 09:48:51.364  INFO 79133 --- [           main] demo.Demo03Application                   : person.name: 曹雪芹
2018-08-01 09:48:51.364  INFO 79133 --- [           main] demo.Demo03Application                   : person.book.title: 红楼梦

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

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

demo04 项目结构:

demo04
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── Demo04Application.java
    │   │       └── model
    │   │           ├── IBook.java
    │   │           ├── IPerson.java
    │   │           └── impl
    │   │               ├── BookImpl01.java
    │   │               ├── BookImpl02.java
    │   │               ├── PersonImpl01.java
    │   │               └── PersonImpl02.java
    │   └── resources
    └── test
        ├── java
        └── resources

实现类 BookImpl01:

package demo.model.impl;

import demo.model.IBook;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("石头记")
public class BookImpl01 implements IBook {

    @Override
    public String getTitle() {
        return "石头记";
    }

}

实现类 BookImpl02:

package demo.model.impl;

import demo.model.IBook;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("红楼梦")
public class BookImpl02 implements IBook {

    @Override
    public String getTitle() {
        return "红楼梦";
    }

}

实现类 PersonImpl01:

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("曹夢阮")
public class PersonImpl01 implements IPerson {

    @Autowired
    @Qualifier("石头记")
    IBook book;

    @Override
    public String getName() {
        return "曹夢阮";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

实现类 PersonImpl02:

package demo.model.impl;

import demo.model.IBook;
import demo.model.IPerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("曹雪芹")
public class PersonImpl02 implements IPerson {

    @Autowired
    @Qualifier("红楼梦")
    IBook book;

    @Override
    public String getName() {
        return "曹雪芹";
    }

    @Override
    public IBook getBook() {
        return book;
    }
}

示例5:根据函数名和类名进行装配

上面说到,自动装配,默认是根据类型注入的。其实如果遇到定义了实现同一接口的Bean的情况,还可以在自动装配时候,类型声明为具体的类类型,而非接口。

还有一个巧妙的方法是,仍然使用接口声明变量,但变量名保持和实现类一致(首字母小写)。

demo05 项目结构:

demo05
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── Demo05Application.java
    │   │       └── model
    │   │           ├── IBook.java
    │   │           ├── IPerson.java
    │   │           └── impl
    │   │               ├── BookImpl01.java
    │   │               ├── BookImpl02.java
    │   │               ├── PersonImpl01.java
    │   │               └── PersonImpl02.java
    │   └── resources
    └── test
        ├── java
        └── resources

主类 Demo05Application:

package demo;

import demo.model.IBook;
import demo.model.IPerson;
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 IBook bookImpl01;  // 对应 BookImpl01

    @Autowired
    private IPerson personImpl02;  // 对应 PersonImpl02

    @Override
    public void run(String... args) throws Exception {
        log.info("bookImpl01.title: {}", bookImpl01.getTitle());
        log.info("personImpl02.name: {}", personImpl02.getName());
        log.info("personImpl02.book01.title: {}", personImpl02.getBook().getTitle());
    }

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

}

执行结果:

2018-08-01 09:57:01.376  INFO 79146 --- [           main] demo.Demo05Application                   : bookImpl01.title: 石头记
2018-08-01 09:57:01.376  INFO 79146 --- [           main] demo.Demo05Application                   : personImpl02.name: 曹雪芹
2018-08-01 09:57:01.376  INFO 79146 --- [           main] demo.Demo05Application                   : personImpl02.book01.title: 红楼梦

不推荐这种做法。



( 本文完 )