Spring Boot 应用程序无法启动,并出现错误“应用程序需要一个 bean,但找到了 2 个”

Sha*_*mad -2 java spring mqtt spring-boot

我正在使用spring-integration-mqtt依赖项编写 Spring Boot 应用程序,其中我使用CommandLineRunnerbeanMQTTSubscriber在应用程序启动时启动。

但是,当我运行该应用程序时,出现以下错误:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-01-26 01:48:40.386 ERROR 59171 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 
***************************
APPLICATION FAILED TO START
***************************
Description:
Field MessageListener in im.sma.mqtt.mqttclient.DemoApplication required a single bean, but 2 were found:
    - messageListener: defined in file [/Users/sma/sandbox/slidecab/mqtt/mqtt-client/target/classes/im/sma/mqtt/mqttclient/config/MessageListener.class]
    - integrationHeaderChannelRegistry: defined in null
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
Run Code Online (Sandbox Code Playgroud)

我注意到当我从代码中删除以下部分时错误消失了:

@Autowired
Runnable MessageListener;

@Bean
public CommandLineRunner schedulingRunner(TaskExecutor executor) {
    return new CommandLineRunner() {
        public void run(String... args) throws Exception {
            executor.execute(MessageListener);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

这是我DemoApplication发生错误的班级:

@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
    @Autowired
    Runnable MessageListener;

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(DemoApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    @Bean
    public CommandLineRunner schedulingRunner(TaskExecutor executor) {
        return new CommandLineRunner() {
            public void run(String... args) throws Exception {
                executor.execute(MessageListener);
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

此外,我还有以下AppConfig课程来设置TaskExecutor

@Component
public class AppConfig {

    @Bean
    @Primary
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是MessageListener它无法自动装配的 bean:

@Component
public class MessageListener implements Runnable {

    @Autowired
    MQTTSubscriberBase subscriber;

    @Override
    public void run() {
        while(true) {
            subscriber.subscribeMessage("demoTopic2019");
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

另外,我有以下配置来设置MQTTSubscriber

public abstract class MQTTConfig {
    protected final String broker = "localhost";
    protected final int qos = 2;
    protected Boolean hasSSL = false; /* By default SSL is disabled */
    protected Integer port = 1883; /* Default port */
    protected final String userName = "guest" ;//"testUserName";
    protected final String password = "guest";//"demoPassword";
    protected final String TCP = "tcp://";
    protected final String SSL = "ssl://";

    protected abstract void config(String broker, Integer port, Boolean ssl, Boolean withUserNamePass);

    protected abstract void config();
}
Run Code Online (Sandbox Code Playgroud)
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-01-26 01:48:40.386 ERROR 59171 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 
***************************
APPLICATION FAILED TO START
***************************
Description:
Field MessageListener in im.sma.mqtt.mqttclient.DemoApplication required a single bean, but 2 were found:
    - messageListener: defined in file [/Users/sma/sandbox/slidecab/mqtt/mqtt-client/target/classes/im/sma/mqtt/mqttclient/config/MessageListener.class]
    - integrationHeaderChannelRegistry: defined in null
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
Run Code Online (Sandbox Code Playgroud)
@Autowired
Runnable MessageListener;

@Bean
public CommandLineRunner schedulingRunner(TaskExecutor executor) {
    return new CommandLineRunner() {
        public void run(String... args) throws Exception {
            executor.execute(MessageListener);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

这些是我正在使用的依赖项:

@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
    @Autowired
    Runnable MessageListener;

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(DemoApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    @Bean
    public CommandLineRunner schedulingRunner(TaskExecutor executor) {
        return new CommandLineRunner() {
            public void run(String... args) throws Exception {
                executor.execute(MessageListener);
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么我不能注入bean,我应该采取什么行动?

g00*_*00b 5

问题是您正在使用一个非常通用的接口来进行自动装配,即Runnable.

因此,似乎存在两个与Runnable接口匹配的 bean :

  1. 一个是MessageListener你自己创建的类
  2. 另一个被DefaultHeaderChannelRegistry称为integrationHeaderChannelRegistry. 大概它被公开是因为您的类路径上有 Spring 集成。

问题是由于这个原因,Spring IoC 容器无法弄清楚它应该注入什么 bean,它提供了一些解决方案。

将其中一个豆子标记为 @Primary

这可以用于 99% 的场景中将使用这些 bean 之一的场景。通过将MessageListener类标记为@Primary,它会在尝试注入时优先,例如:

@Primary // Add this
@Component
public class MessageListener implements Runnable {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

更新消费者以接受多个 bean

当您想要引用所有Runnablebean时,此方案很有用。就您而言,这可能不是解决方案,但在某些情况下,您可能希望获取某种类型的所有 bean。要做到这一点,你可以这样做:

@Autowired
private List<Runnable> runnables; // Change the type to List<Runnable>
Run Code Online (Sandbox Code Playgroud)

使用@Qualifier识别豆

另一种可能性是用于@Qualifier指定要注入的 bean 的确切名称。在您的情况下,您可以在messageListener或之间进行选择integrationHeaderChannelRegistry。例如:

@Autowired
@Qualifier("messageListener")
private Runnable mesageListener;
Run Code Online (Sandbox Code Playgroud)

在您提出的案例中,这可能是最佳解决方案。


我还想提供一些其他解决方案。

使用特定类型

如果您将自动装配字段的类型更改为MessageListener,则不会混淆应该注入哪个 bean,因为只有一个类型为 的 bean MessageListener

@Autowired
private MessageListener mesageListener;
Run Code Online (Sandbox Code Playgroud)

不使用 Spring 集成库

到目前为止,您显示的代码与 Spring 集成无关。如果您的唯一目标是设置 MQTT 客户端,您可能需要考虑放弃该spring-integration-mqtt包并使用简单的 MQTT 客户端实现,如Eclipse Paho

由于您添加了spring-integration-mqtt库,其他 bean 正在自动创建,删除它会停止导致 bean 被创建,这也将解决问题。