From 7256daa137818d73e93d26bdb20ec1127990769f Mon Sep 17 00:00:00 2001 From: cmdjulian Date: Sat, 6 Apr 2024 23:28:53 +0200 Subject: [PATCH 1/4] support graalvm native image --- README.md | 3 ++- build.gradle.kts | 1 + .../starter/mqtt/MqttAutoConfiguration.kt | 3 +++ .../starter/mqtt/MqttProperties.kt | 4 ++-- .../smartsquare/starter/mqtt/MqttSubscribe.kt | 2 ++ .../mqtt-starter/reflect-config.json | 21 +++++++++++++++++++ 6 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json diff --git a/README.md b/README.md index 74e1387..316d246 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # :honeybee: HiveMQ Spring Boot Starter -Use an automatically configured mqtt 3 or 5 client in your Spring Boot project. +Use an automatically configured mqtt 3 or 5 client in your Spring Boot project. This starter supports GraalVM native +image and provides a simple annotation-based message consumer. ## Getting Started diff --git a/build.gradle.kts b/build.gradle.kts index 6e714c1..f7338a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,6 +59,7 @@ kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 allWarningsAsErrors = true + javaParameters = true } } diff --git a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttAutoConfiguration.kt b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttAutoConfiguration.kt index 969ed7e..67664b5 100644 --- a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttAutoConfiguration.kt +++ b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttAutoConfiguration.kt @@ -9,6 +9,7 @@ import com.hivemq.client.mqtt.mqtt5.Mqtt5Client import io.reactivex.Scheduler import io.reactivex.schedulers.Schedulers import org.slf4j.LoggerFactory +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding import org.springframework.boot.autoconfigure.AutoConfiguration import org.springframework.boot.autoconfigure.AutoConfigureAfter import org.springframework.boot.autoconfigure.condition.ConditionalOnClass @@ -30,6 +31,7 @@ import java.util.concurrent.Executor @Import(MqttSubscriberCollector::class) @ConditionalOnClass(MqttClient::class) @ConditionalOnProperty("mqtt.enabled", matchIfMissing = true) +@RegisterReflectionForBinding(MqttProperties::class) @EnableConfigurationProperties(MqttProperties::class) class MqttAutoConfiguration { @@ -126,6 +128,7 @@ class MqttAutoConfiguration { fun immediateMqttScheduler(): Scheduler = Schedulers.computation() @Bean + @ConditionalOnMissingBean fun mqttMessageAdapter(objectMapper: ObjectMapper): MqttMessageAdapter = DefaultMqttMessageAdapter(objectMapper) /** diff --git a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttProperties.kt b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttProperties.kt index 7aa9cea..6a4c278 100644 --- a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttProperties.kt +++ b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttProperties.kt @@ -22,8 +22,8 @@ data class MqttProperties( /** * The port the mqtt broker is available under. */ - @field:Min(1) - @field:Max(65535) + @get:Min(1) + @get:Max(65535) val port: Int = 0, /** diff --git a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscribe.kt b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscribe.kt index 53485d7..e2487a4 100644 --- a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscribe.kt +++ b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscribe.kt @@ -1,10 +1,12 @@ package de.smartsquare.starter.mqtt import com.hivemq.client.mqtt.datatypes.MqttQos +import org.springframework.aot.hint.annotation.Reflective /** * Marker annotation for methods that should receive messages from the mqtt broker. */ +@Reflective @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) annotation class MqttSubscribe( diff --git a/src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json b/src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json new file mode 100644 index 0000000..b3ec3f6 --- /dev/null +++ b/src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json @@ -0,0 +1,21 @@ +[ + { + "name": "de.smartsquare.starter.mqtt.MqttProperties$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "CGLIB$SET_THREAD_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + } +] From 3f6776717fa93957535490479e9f3958f9f070b5 Mon Sep 17 00:00:00 2001 From: cmdjulian Date: Sun, 14 Apr 2024 18:36:38 +0200 Subject: [PATCH 2/4] MR discussions --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 271cb67..33333ec 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # :honeybee: HiveMQ Spring Boot Starter -Use an automatically configured mqtt 3 or 5 client in your Spring Boot project. This starter supports GraalVM native -image and provides a simple annotation-based message consumer. +Use an automatically configured mqtt 3 or 5 client in your Spring Boot project. ## Getting Started @@ -164,6 +163,10 @@ class TestService(private val mqttClient: Mqtt3Client) { } ``` +### GraalVM + +This starter supports GraalVM out of the box. There is nothing special to do. + ### Upgrade Guide #### 0.15.0 -> 0.16.0 From 31006fd7aeb0143d6abee9c724d22824ebb1fd3b Mon Sep 17 00:00:00 2001 From: cmdjulian Date: Tue, 16 Apr 2024 00:50:24 +0200 Subject: [PATCH 3/4] replace @Lazy with ObjectProvider --- .../starter/mqtt/MqttSubscriberCollector.kt | 12 +++++++++-- .../mqtt-starter/reflect-config.json | 21 ------------------- .../starter/mqtt/MqttHandlerTest.kt | 4 ++-- ...MqttSubscriberCollectorIntegrationTests.kt | 2 +- .../mqtt/MqttSubscriberCollectorTests.kt | 2 +- .../starter/mqtt/TestObjectProvider.kt | 11 ++++++++++ 6 files changed, 25 insertions(+), 27 deletions(-) delete mode 100644 src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json create mode 100644 src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt diff --git a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollector.kt b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollector.kt index d3408e3..8c2dbe4 100644 --- a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollector.kt +++ b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollector.kt @@ -3,18 +3,26 @@ package de.smartsquare.starter.mqtt import com.hivemq.client.mqtt.datatypes.MqttQos import com.hivemq.client.mqtt.datatypes.MqttTopicFilter import org.slf4j.LoggerFactory +import org.springframework.beans.factory.ObjectProvider import org.springframework.beans.factory.config.BeanPostProcessor -import org.springframework.context.annotation.Lazy import org.springframework.core.annotation.AnnotationUtils import java.lang.reflect.Method /** * Helper class to find all beans with methods annotated with [MqttSubscribe]. */ -class MqttSubscriberCollector(@Lazy private val config: MqttProperties) : BeanPostProcessor { +class MqttSubscriberCollector(configProvider: ObjectProvider) : BeanPostProcessor { private val logger = LoggerFactory.getLogger(javaClass) + /** + * It's really important to use lazy initialization here, because the bean value inside this provider is not + * available during construction time and only requested lazy up on the first found subscriber. + * At this point, the bean is already available and can be used. + * We cache the result here to avoid multiple expensive lookups from the underlying bean factory. + */ + private val config: MqttProperties by lazy { configProvider.`object` } + /** * MultiMap of beans to its methods annotated with [MqttSubscribe] and the annotation itself. */ diff --git a/src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json b/src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json deleted file mode 100644 index b3ec3f6..0000000 --- a/src/main/resources/META-INF/native-image/de.smartsquare/mqtt-starter/reflect-config.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "name": "de.smartsquare.starter.mqtt.MqttProperties$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "CGLIB$SET_THREAD_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - } -] diff --git a/src/test/kotlin/de/smartsquare/starter/mqtt/MqttHandlerTest.kt b/src/test/kotlin/de/smartsquare/starter/mqtt/MqttHandlerTest.kt index fb65bb1..30d1a65 100644 --- a/src/test/kotlin/de/smartsquare/starter/mqtt/MqttHandlerTest.kt +++ b/src/test/kotlin/de/smartsquare/starter/mqtt/MqttHandlerTest.kt @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test object TestMqttSubscriberCollector { - operator fun invoke(bean: Any): MqttSubscriberCollector = MqttSubscriberCollector(MqttProperties()).apply { + operator fun invoke(bean: Any) = MqttSubscriberCollector(TestObjectProvider(MqttProperties())).apply { postProcessAfterInitialization(bean, "testBean") } } @@ -21,7 +21,7 @@ class MqttHandlerTest { private val messageErrorHandler = MqttMessageErrorHandler() @Test - fun `invoke correct method for multiple subsciber methods`() { + fun `invoke correct method for multiple subscriber methods`() { val subscriber = object { var invoked = false diff --git a/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorIntegrationTests.kt b/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorIntegrationTests.kt index 2fd585d..d05e083 100644 --- a/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorIntegrationTests.kt +++ b/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorIntegrationTests.kt @@ -24,7 +24,7 @@ class MqttSubscriberCollectorIntegrationTests { class PostProcessorConfiguration { @Bean - fun annotationCollector() = MqttSubscriberCollector(MqttProperties()) + fun annotationCollector() = MqttSubscriberCollector(TestObjectProvider(MqttProperties())) @Bean fun subscriber() = Subscriber() diff --git a/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorTests.kt b/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorTests.kt index a8bd1ee..ccc71f3 100644 --- a/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorTests.kt +++ b/src/test/kotlin/de/smartsquare/starter/mqtt/MqttSubscriberCollectorTests.kt @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test class MqttSubscriberCollectorTests { - private val annotationCollector = MqttSubscriberCollector(MqttProperties(group = "group")) + private val annotationCollector = MqttSubscriberCollector(TestObjectProvider(MqttProperties(group = "group"))) @Test fun `processes bean`() { diff --git a/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt b/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt new file mode 100644 index 0000000..576276b --- /dev/null +++ b/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt @@ -0,0 +1,11 @@ +package de.smartsquare.starter.mqtt + +import org.springframework.beans.factory.NoSuchBeanDefinitionException +import org.springframework.beans.factory.ObjectProvider + +class TestObjectProvider(private val data: T?) : ObjectProvider { + override fun getObject(vararg args: Any?): T = data ?: throw NoSuchBeanDefinitionException("No bean found") + override fun getObject(): T = data ?: throw NoSuchBeanDefinitionException("No bean found") + override fun getIfAvailable(): T? = data + override fun getIfUnique(): T = getObject() +} From 3bc7b061e8b15bc64d91698803472b5b77182534 Mon Sep 17 00:00:00 2001 From: cmdjulian Date: Tue, 16 Apr 2024 22:58:36 +0200 Subject: [PATCH 4/4] MR comments --- .../de/smartsquare/starter/mqtt/TestObjectProvider.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt b/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt index 576276b..df24c63 100644 --- a/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt +++ b/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt @@ -1,11 +1,10 @@ package de.smartsquare.starter.mqtt -import org.springframework.beans.factory.NoSuchBeanDefinitionException import org.springframework.beans.factory.ObjectProvider -class TestObjectProvider(private val data: T?) : ObjectProvider { - override fun getObject(vararg args: Any?): T = data ?: throw NoSuchBeanDefinitionException("No bean found") - override fun getObject(): T = data ?: throw NoSuchBeanDefinitionException("No bean found") - override fun getIfAvailable(): T? = data +class TestObjectProvider(private val data: T) : ObjectProvider { + override fun getObject(vararg args: Any?): T = data + override fun getObject(): T = data + override fun getIfAvailable(): T = data override fun getIfUnique(): T = getObject() }