Приложение Spring boot не удалось запустить с ошибкой «Приложению требуется один компонент, но найдено 2»

Я пишу приложение Spring Boot, используя зависимость spring-integration-mqtt, где я использую bean-компонент CommandLineRunner для запуска MQTTSubscriber при запуске приложения.

Однако, когда я запускаю приложение, я получаю следующую ошибку:

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

Я заметил, что ошибка исчезает, когда я удаляю из своего кода следующую часть:

@Autowired
Runnable MessageListener;

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

Это мой класс 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);
            }
        };
    }
}

Кроме того, у меня также есть следующий класс AppConfig для настройки TaskExecutor:

@Component
public class AppConfig {

    @Bean
    @Primary
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
}

И это bean-компонент MessageListener, который не может быть автоматически подключен:

@Component
public class MessageListener implements Runnable {

    @Autowired
    MQTTSubscriberBase subscriber;

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

    }
}

Кроме того, у меня есть следующая конфигурация для настройки 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();
}
public interface MQTTSubscriberBase {
    public static final Logger logger = LoggerFactory.getLogger(MQTTSubscriberBase.class);

    public void subscribeMessage(String topic);
    public void disconnect();
}
@Component
public class MQTTSubscriber extends MQTTConfig implements MqttCallback, MQTTSubscriberBase {
    private String brokerUrl = null;
    private String colon = ":";
    private String clientId = "demoClient2";

    private MqttClient client = null;
    private MqttConnectOptions options = null;
    private MemoryPersistence persistence = null;

    private static final Logger logger = LoggerFactory.getLogger(MQTTSubscriber.class);

    public MQTTSubscriber() {
        this.config();
    }

    @Override
    public void connectionLost(Throwable cause) {
        logger.info("Connection lost");
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        String time = new Timestamp(System.currentTimeMillis()).toString();
        System.out.println();
        System.out.println("********************************************************");
        System.out.println("Message arrived at " + time + "Topic: " + topic + " Message: " + new String(message.getPayload()));
        System.out.println("********************************************************");
        System.out.println();
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        // Not required for subscriber
    }


    @Override
    public void subscribeMessage(String topic) {
        try {
            this.client.subscribe(topic, this.qos);
        } catch (MqttException exception) {
            logger.error("ERROR", exception);
        }
    }

    @Override
    public void disconnect() {
        try {
            this.client.disconnect();
        } catch (MqttException exception) {
            logger.error("ERROR: ", exception);
        }
    }


    @Override
    public void config(String broker, Integer port, Boolean ssl, Boolean withUserNamePass) {
        String protocol = this.TCP;
        if (true == ssl) {
            protocol = this.SSL;
        }

        this.brokerUrl = protocol + broker + this.colon + port;
        this.persistence = new MemoryPersistence();
        this.options = new MqttConnectOptions();

        try {
            this.client = new MqttClient(this.brokerUrl, clientId, persistence);
            this.options.setCleanSession(true);
            if (true == withUserNamePass) {
                if (this.password != null) {
                    this.options.setPassword(this.password.toCharArray());
                }
                if (this.userName != null) {
                    this.options.setUserName(this.userName);
                }
            }
            this.client.connect(this.options);
            this.client.setCallback(this);
        }
        catch(MqttException exception) {
            this.logger.error("ERROR ", exception);
        }
    }

    @Override
    public void config() {
        this.brokerUrl = this.TCP + this.broker + this.colon + this.port;
        this.persistence = new MemoryPersistence();
        this.options = new MqttConnectOptions();

        try {
            this.client = new MqttClient(brokerUrl, clientId, persistence);
            this.options.setCleanSession(true);
            this.client.connect(options);
            this.client.setCallback(this);
        }
        catch(MqttException exception) {
            logger.error("ERROR", exception);
        }
    }

}

И это зависимости, которые я использую:

<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
    <relativePath/>
  </parent>
  <groupId>im.sma.mqtt</groupId>
  <artifactId>mqtt-client</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demo</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.integration</groupId>
      <artifactId>spring-integration-mqtt</artifactId>
      <version>5.1.2.RELEASE</version>
    </dependency>

  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Почему я не могу ввести бин и что делать?

В сообщении об ошибке уже сказано, как вы можете ее решить, так что именно вы спрашиваете? Что-то непонятно?

g00glen00b 25.01.2019 22:16

Одним из возможных решений было объявить один из двух bean-компонентов как @Primary, я пробовал, этот вариант все еще не работает. То, как я использую интерфейс CommandLineRunner, — это выполнение кода команды при запуске приложения и поддержание его работы.

Shaukat Mahmood Ahmad 25.01.2019 22:24

Вы пытались добавить @Qualifier("messageListener") рядом с аннотацией @Autowired для поля messageListener?

g00glen00b 25.01.2019 23:09

@ g00glen00b Да, я пробовал, и это работает, большое спасибо, вы спасаете жизнь.

Shaukat Mahmood Ahmad 26.01.2019 00:48

@ g00glen00b Я также нашел другое решение: если использовать прямую зависимость от клиента paho ieorg.eclipse.paho.client.mqttv3 (из репозитория eclipse в repo.eclipse.org/content/repositories/paho-releases) вместо spring-integration-mqtt, тогда все работает нормально даже без использования @Qualifier ("messageListener") аннотация.

Shaukat Mahmood Ahmad 26.01.2019 00:51

@ g00glen00b g00glen00b будет действительно полезно, если вы сможете объяснить решение «@Qualifier» в контексте упомянутой проблемы.

Shaukat Mahmood Ahmad 26.01.2019 00:57

Я создал подробный ответ, охватывающий объяснение решений, предложенных Spring, и, кроме того, несколько решений, которые также могут работать, например, удаление библиотеки интеграции Spring, как вы упомянули.

g00glen00b 26.01.2019 13:43

@ g00glen00b, да, я прочитал ваш подробный ответ, кстати, почему я получаю отрицательные баллы за этот вопрос?

Shaukat Mahmood Ahmad 26.01.2019 15:27

Я не минусовал вас, но я предполагаю, что люди могут подумать, что это вопрос с небольшими усилиями, потому что (1) решение находится в сообщении об ошибке (2) в вопросе много кода, который на самом деле не связан к фактической причине. Это также причина, по которой я сначала спросил, не было ли что-то неясно в сообщении об ошибке. Если вы не знаете, что означает @Primary/@Qualifier, то сообщение об ошибке не совсем полезно, и, похоже, так оно и было.

g00glen00b 26.01.2019 17:20
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
9
6 507
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема в том, что вы используете довольно общий интерфейс для автоподключения, Runnable.

В связи с этим кажется, что существует два bean-компонента, которые соответствуют интерфейсу Runnable:

  1. Одним из них является класс MessageListener, который вы создали сами.
  2. Другой — DefaultHeaderChannelRegistry, представленный как bean-компонент integrationHeaderChannelRegistry. Предположительно, это выставлено, потому что у вас есть интеграция Spring в вашем пути к классам.

Проблема в том, что из-за этого контейнер Spring IoC не может понять, какой bean-компонент он должен внедрить, и предоставляет некоторые решения.

Маркировка одного из bean-компонентов как @Primary

Это можно использовать в сценариях, где один из этих компонентов будет использоваться в 99% сценариев. Пометив класс MessageListener как @Primary, он будет иметь приоритет при попытке внедрить его, например:

@Primary // Add this
@Component
public class MessageListener implements Runnable {
    // ...
}

Обновление потребителя для приема нескольких bean-компонентов

Этот сценарий полезен, когда вам нужна ссылка на все bean-компоненты Runnable. В вашем случае это, вероятно, не решение, но в некоторых ситуациях вы можете захотеть получить все компоненты определенного типа. Для этого вы можете сделать:

@Autowired
private List<Runnable> runnables; // Change the type to List<Runnable>

Использование @Qualifier для идентификации bean-компонента

Другая возможность — использовать @Qualifier, чтобы указать точное имя bean-компонента, который вы хотите внедрить. В вашем случае вы можете выбрать messageListener или integrationHeaderChannelRegistry. Например:

@Autowired
@Qualifier("messageListener")
private Runnable mesageListener;

Это, вероятно, лучшее решение в вашем случае, которое предлагается.


Есть также несколько дополнительных решений, которые я хочу предоставить.

Использование определенного типа

Если вы измените тип поля autowired на MessageListener, не будет путаницы, в какой bean-компонент следует ввести, поскольку существует только один bean-компонент типа MessageListener:

@Autowired
private MessageListener mesageListener;

Не использовать библиотеку интеграции Spring

Показанный вами код не имеет ничего общего с интеграцией Spring. Если ваша единственная цель — настроить клиент MQTT, вы можете отказаться от пакета spring-integration-mqtt и вместо этого использовать простую реализацию клиента MQTT, например Затмение Пахо.

Поскольку другой bean-компонент создавался автоматически, потому что вы добавили библиотеку spring-integration-mqtt, ее удаление прекратит создание bean-компонента, и это также решит проблему.

Спасибо @g00glen00b за подробный ответ, это было действительно полезно. Я упомянул последнее решение в своем предыдущем комментарии, т.е. проблема не возникает, если я добавляю прямую зависимость от клиентской библиотеки Eclipse Paho Java. Еще раз большое спасибо за ваш подробный ответ со всеми техническими подробностями.

Shaukat Mahmood Ahmad 26.01.2019 15:24

У меня была эта проблема в моем коде в течение недели, и сегодня я нашел это справедливо, потому что я дал одно и то же имя двум методам:

   @Bean(name = "sqlServer")
public DataSource sqlServerDataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("spring.ds-sql.driverClassName"));
    dataSource.setUrl(env.getProperty("spring.ds-sql.url"));
    dataSource.setUsername(env.getProperty("spring.ds-sql.username"));
    dataSource.setPassword(env.getProperty("spring.ds-sql.password"));
    return dataSource;
}

@Bean(name = "sqlJdbc")
public JdbcTemplate sqlServerDataSource(@Qualifier("sqlServer") DataSource dsSqlServer) {
    return new JdbcTemplate(dsSqlServer);
}

Надеюсь, это поможет людям, имеющим такой же случай.

Удачи

Другие вопросы по теме