Spring Boot MQTT servisi, bir cihaz filosunu iş sistemlerinizin geri kalanına bağlamanın yaygın bir yoludur: cihazlar MQTT üzerinden yayın yapar, web uygulamalarınız ve iş ortaklarınız ise tanıdık bir REST API kullanır. Bu eğitimde Spring Integration'ın MQTT 5 adaptörlerini kullanarak telemetriyi TLS üzerinden tüketecek, her cihazın son okumasını tutacak, bunu /api/devices adresinde sunacak ve cihazlara komut göndereceksiniz; hepsi tek bir paylaşılan MQTT bağlantısı üzerinden.
Mimari
Servis iki dünyanın arasında yer alır:
| Yön | Bileşen | Topic veya uç nokta |
|---|---|---|
| Cihazdan servise | Inbound adaptör Mqttv5PahoMessageDrivenChannelAdapter |
<prefix>/devices/+/telemetry |
| Servisten istemcilere | GET /api/devices, GET /api/devices/{id} |
REST |
| İstemciden servise | POST /api/devices/{id}/commands |
REST |
| Servisten cihaza | Outbound handler Mqttv5PahoMessageHandler |
<prefix>/devices/{id}/cmd |
Üretim ortamı için en önemli ayrıntı bağlantıdır. Inbound adaptör ve outbound handler her biri kendi MQTT client'ını oluşturursa iki client ID'ye ve iki bağlantıya ihtiyaç duyarlar. ConnectMQ dahil pek çok barındırılan broker, bir kimlik bilgisi setine bağlı tek bir client ID verir; bu nedenle aynı ID'ye sahip iki bağlantı birbirini sürekli olarak düşürür. Spring Integration 6 bunu her iki bileşenin paylaştığı bir ClientManager ile çözer. Bu eğitim onu en baştan kullanır.
Proje Kurulumu
pom.xml dosyasında şu bağımlılıklarla bir Spring Boot 3 projesi (Java 17+) oluşturun:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.mqttv5.client</artifactId>
<version>1.2.5</version>
</dependency>
</dependencies>
Paho MQTT 5 client, spring-integration-mqtt için isteğe bağlı bir bağımlılıktır; bu yüzden onu açıkça eklemeniz gerekir. Bunu unutmak, başlangıçta ClassNotFoundException: org.eclipse.paho.mqttv5... hatasının en yaygın nedenidir.
Yapılandırma
src/main/resources/application.yml:
mqtt:
url: ${MQTT_URL:ssl://broker.connectmq.com:8883}
client-id: ${MQTT_CLIENT_ID:spring-s8d2lf}
username: ${MQTT_USERNAME:}
password: ${MQTT_PASSWORD:}
prefix: ${MQTT_PREFIX:connectmq/demo/s8d2lf}
Varsayılan değerler, boş olmayan ve benzersiz herhangi bir client ID ile anonim istemcileri kabul eden broker.connectmq.com adresindeki ücretsiz herkese açık MQTT broker'ı gösterir. Herkese açık alandaki topic'ler herkese görünür; bu yüzden rastgele bir önek ve yalnızca test verisi kullanın. 8883 portuyla birlikte ssl:// şeması TLS'yi etkinleştirir; broker'ın Let's Encrypt sertifikasına JDK kutudan çıktığı haliyle güvenir.
MQTT Yapılandırma Sınıfı
package com.example.mqttdemo;
import java.nio.charset.StandardCharsets;
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.mqtt.core.Mqttv5ClientManager;
import org.springframework.integration.mqtt.inbound.Mqttv5PahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.Mqttv5PahoMessageHandler;
import org.springframework.messaging.MessageHandler;
@Configuration
@IntegrationComponentScan
public class MqttConfig {
@Bean
public Mqttv5ClientManager mqttClientManager(
@Value("${mqtt.url}") String url,
@Value("${mqtt.client-id}") String clientId,
@Value("${mqtt.username}") String username,
@Value("${mqtt.password}") String password) {
MqttConnectionOptions options = new MqttConnectionOptions();
options.setServerURIs(new String[] { url });
options.setAutomaticReconnect(true);
options.setCleanStart(false);
options.setSessionExpiryInterval(600L);
options.setKeepAliveInterval(30);
if (!username.isBlank()) {
options.setUserName(username);
options.setPassword(password.getBytes(StandardCharsets.UTF_8));
}
return new Mqttv5ClientManager(options, clientId);
}
@Bean
public Mqttv5PahoMessageDrivenChannelAdapter mqttInbound(
Mqttv5ClientManager clientManager,
@Value("${mqtt.prefix}") String prefix) {
Mqttv5PahoMessageDrivenChannelAdapter adapter =
new Mqttv5PahoMessageDrivenChannelAdapter(clientManager, prefix + "/devices/+/telemetry");
adapter.setQos(1);
return adapter;
}
@Bean
public IntegrationFlow telemetryFlow(Mqttv5PahoMessageDrivenChannelAdapter mqttInbound,
ReadingStore store) {
return IntegrationFlow.from(mqttInbound)
.handle(message -> store.accept(message))
.get();
}
@Bean
@ServiceActivator(inputChannel = "mqttOutbound")
public MessageHandler mqttOutboundHandler(Mqttv5ClientManager clientManager) {
Mqttv5PahoMessageHandler handler = new Mqttv5PahoMessageHandler(clientManager);
handler.setAsync(true);
handler.setDefaultQos(1);
return handler;
}
}
Hem adaptör hem de handler aynı Mqttv5ClientManager örneğini alır; böylece uygulama tek bir client ID ile tam olarak bir MQTT bağlantısı açar. Otomatik yeniden bağlanma bağlantı seçeneklerinde yer alır; setCleanStart(false) ve bir oturum sona erme aralığıyla broker, kısa ağ kesintilerinde aboneliği korur.
Outbound Gateway
Bir messaging gateway, uygulamanın geri kalanına yayın yapmak için sade bir Java arayüzü sunar:
package com.example.mqttdemo;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
@MessagingGateway(defaultRequestChannel = "mqttOutbound")
public interface MqttGateway {
void publish(@Header(MqttHeaders.TOPIC) String topic, String payload);
}
v5 handler, String payload'ı UTF-8 byte'lara dönüştürür; MqttHeaders.TOPIC header'ı ise her mesaj için hedef topic'i belirler.
Son Okumaları Saklamak
package com.example.mqttdemo;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
public class ReadingStore {
public record DeviceReading(String deviceId, JsonNode data, Instant receivedAt) { }
private static final Logger log = LoggerFactory.getLogger(ReadingStore.class);
private final Map<String, DeviceReading> latest = new ConcurrentHashMap<>();
private final ObjectMapper mapper;
public ReadingStore(ObjectMapper mapper) {
this.mapper = mapper;
}
public void accept(Message<?> message) {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC, String.class);
String[] parts = topic.split("/");
String deviceId = parts[parts.length - 2];
String json = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
try {
latest.put(deviceId, new DeviceReading(deviceId, mapper.readTree(json), Instant.now()));
} catch (JsonProcessingException e) {
log.warn("Ignoring invalid JSON from {}: {}", deviceId, e.getOriginalMessage());
}
}
public Collection<DeviceReading> all() {
return latest.values();
}
public Optional<DeviceReading> get(String deviceId) {
return Optional.ofNullable(latest.get(deviceId));
}
}
Inbound adaptör varsayılan olarak byte[] payload'lar iletir ve alınan topic MqttHeaders.RECEIVED_TOPIC header'ında gelir. Cihaz ID'si, telemetry öncesindeki segmenttir.
REST Controller
package com.example.mqttdemo;
import java.util.Collection;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/devices")
public class DeviceController {
private static final Pattern SAFE_ID = Pattern.compile("[A-Za-z0-9_-]{1,64}");
private final ReadingStore store;
private final MqttGateway gateway;
private final String prefix;
public DeviceController(ReadingStore store, MqttGateway gateway,
@Value("${mqtt.prefix}") String prefix) {
this.store = store;
this.gateway = gateway;
this.prefix = prefix;
}
@GetMapping
public Collection<ReadingStore.DeviceReading> all() {
return store.all();
}
@GetMapping("/{id}")
public ResponseEntity<ReadingStore.DeviceReading> one(@PathVariable String id) {
return ResponseEntity.of(store.get(id));
}
@PostMapping("/{id}/commands")
public ResponseEntity<Void> command(@PathVariable String id, @RequestBody String body) {
if (!SAFE_ID.matcher(id).matches()) {
return ResponseEntity.badRequest().build();
}
gateway.publish(prefix + "/devices/" + id + "/cmd", body);
return ResponseEntity.accepted().build();
}
}
ID kontrolü süs değildir. Bu kontrol olmadan bir çağıran, /, + veya # içeren bir ID gönderip hiç açmayı düşünmediğiniz topic'lere yayın yapabilir.
Bir Mesaj Uygulamada Nasıl Akar?
Tek bir telemetri mesajını baştan sona izlemek faydalıdır, çünkü Spring Integration birkaç bean'in arkasında pek çok altyapıyı gizler.
- Cihaz
{"temperature":22.5}mesajını<prefix>/devices/sensor-1/telemetrytopic'ine yayınlar. - Broker mesajı, paylaşılan client manager aracılığıyla inbound adaptörün oluşturduğu abonelik dahil, eşleşen tüm aboneliklere yönlendirir.
- Adaptör payload byte'larını bir Spring
Messageiçine sarar veRECEIVED_TOPIC,RECEIVED_QOSveRECEIVED_RETAINEDgibi header'lar ekler. telemetryFlow, mesajı JSON'u ayrıştırıp o cihazın önceki okumasını değiştirenReadingStore.acceptmetoduna iletir./api/devices/sensor-1adresine yapılan bir REST çağrısı map'i okur ve son okumayı JSON olarak döndürür.
Komutlar ters yönde ilerler: controller gateway'i çağırır, gateway mqttOutbound kanalına bir mesaj gönderir, service activator bunu outbound handler'a verir ve handler aynı paylaşılan bağlantı üzerinden yayın yapar. Outbound handler asenkron olduğu için REST çağrısı broker onayını beklemeden 202 Accepted döndürür. API'nizin teslimatı doğrulaması gerekiyorsa handler'ı senkron moda alın ve ek gecikmeyi kabul edin.
Hata yönetimi
Varsayılan olarak akış içindeki bir istisna loglanır ve mesaj atılır. Bir prototipin ötesindeki her şey için inbound adaptöre adapter.setErrorChannelName("mqttErrors") ile bir hata kanalı ve topic ile payload'ı loglayan küçük bir akış ekleyin; böylece bir cihazdan gelen hatalı biçimli bir mesaj sessizce yok sayılmak yerine görünür olur. Handler'ı hızlı tutun: veritabanı yazma işlemleri eklerseniz, yavaş bir veritabanının mesaj teslimatını bekletmemesi için adaptör ile store arasında bir executor channel kullanmayı düşünün.
Uçtan Uca Test
Uygulamayı ./mvnw spring-boot:run ile başlatın, ardından bir cihazı simüle edin:
mosquitto_pub -h broker.connectmq.com -p 1883 -i dev-s8d2lf \
-t 'connectmq/demo/s8d2lf/devices/sensor-1/telemetry' -m '{"temperature":22.5,"humidity":48}'
REST API'yi sorgulayın:
curl -s http://localhost:8080/api/devices
curl -s http://localhost:8080/api/devices/sensor-1
Bir komut gönderin ve cihaz topic'ine ulaşmasını izleyin:
mosquitto_sub -h broker.connectmq.com -p 1883 -i cmdwatch-s8d2lf \
-t 'connectmq/demo/s8d2lf/devices/+/cmd' -v &
curl -s -X POST http://localhost:8080/api/devices/sensor-1/commands \
-H 'Content-Type: application/json' -d '{"fan":"on"}'
Spring Boot MQTT için Üretim Notları
- Verilen kimlik bilgilerini kullanın. ConnectMQ portalında bir broker oluşturun ve
MQTT_CLIENT_ID,MQTT_USERNAMEileMQTT_PASSWORDdeğişkenlerini üretilen değerlere ayarlayın; client ID birebir eşleşmelidir. - Bir örnek, bir client ID. Birkaç replikaya ölçeklenirseniz her birinin kendi kimlik bilgilerine ihtiyacı olur. Aynı telemetrinin yük dengeli tüketimi için MQTT 5 shared subscription'lar ücretli Serverless planlarda mevcuttur.
- Önemli olanı kalıcı hale getirin. Bellek içi map yeniden başlatmada kaybolur. Geçmiş için okumaları akış içinde bir veritabanına yazın veya bu servisin yanına özel bir veri alım aracı abone edin.
- QoS'u bilinçli seçin. Telemetri ve komutlar için QoS 1 doğru varsayılandır; MQTT QoS seviyeleri açıklaması yazısına bakın. Free plan QoS 1'e kadar destekler.
- Sağlık durumunu sunun. Spring Boot Actuator ekleyin ve MQTT bağlantısı beklenenden uzun süre kapalı kaldığında uyarı verin.
Sorun Giderme
| Belirti | Neden | Çözüm |
|---|---|---|
org.eclipse.paho.mqttv5 için ClassNotFoundException |
Paho v5 bağımlılığı eksik | org.eclipse.paho.mqttv5.client ekleyin |
| Bağlantı birkaç saniyede bir gidip geliyor | Aynı ID'ye sahip iki client | Tek bir Mqttv5ClientManager paylaşın |
Not authorized |
Kimlik bilgisi veya client ID uyuşmazlığı | Değerleri portaldan yeniden kopyalayın |
Payload'da ClassCastException |
Payload tipi değişmiş | byte[] kullanmaya devam edin veya setPayloadType değerini tutarlı yapılandırın |
/api/devices içinde veri yok |
Topic filtresi uyuşmazlığı | Öneki ve + konumunu kontrol edin |
Adaptörlerin tam başvurusu Spring Integration MQTT belgelerinde yer alır.
İlgili Rehberler
Spring olmadan sade bir Java client'a ihtiyacınız varsa Java MQTT client eğitimi Paho ile HiveMQ client'ı karşılaştırır. Go MQTT istemcisi eğitimi aynı deseni Go ile kurar; MQTT istemci kütüphaneleri rehberi ise diğer ekosistemleri ele alır.
Spring Boot'u ConnectMQ'ya Bağlayın
Yönetilen bir MQTT broker, siz API'ye odaklanırken Spring Boot servisinize kararlı ve şifreli bir uç nokta sağlar. ConnectMQ, ücretli planlarda oturum sona erme süresi ve shared subscription'larla MQTT 5'i destekler; cihazlar görünümü ve canlı topic gezgini filonuzun ne gönderdiğini kontrol etmenize yardımcı olur. Barındırma seçeneklerine daha geniş bir bakış için bulut MQTT broker rehberine göz atın. app.connectmq.com adresinde kredi kartı gerekmeden ücretsiz bir Serverless broker oluşturun ve yukarıdaki ortam değişkenlerini ayarlayın.
Sık Sorulan Sorular
Spring Boot'ta MQTT nasıl kullanılır?
spring-integration-mqtt ve Paho MQTT 5 client'ı ekleyin; ardından gelen mesajlar için bir Mqttv5PahoMessageDrivenChannelAdapter, giden mesajlar için bir Mqttv5PahoMessageHandler yapılandırın.
Inbound ve outbound MQTT adaptörleri tek bir bağlantıyı paylaşabilir mi?
Evet. Spring Integration 6'dan itibaren her ikisine de aynı Mqttv5ClientManager örneğini verin; bu, tek bir client ID ve bağlantı kullanır.
Spring Integration MQTT 5'i destekliyor mu?
Evet; Eclipse Paho MQTT 5 client üzerine kurulu, Mqttv5 önekli adaptörler aracılığıyla.
Spring Boot'u bir MQTT broker'a TLS üzerinden nasıl bağlarım?
Bağlantı seçeneklerinde bir ssl://host:8883 sunucu URI'si kullanın. Herkesçe güvenilen sertifikalara sahip broker'lar JDK'nın varsayılan trust store'u ile çalışır.



