From cc3c3043f9a1d0aa8b49ccde44bf4742919137a5 Mon Sep 17 00:00:00 2001 From: Julian <33688601+cmdjulian@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:06:13 +0200 Subject: [PATCH] Add support for GraalVM native-image (#14) --- README.md | 4 ++++ build.gradle.kts | 1 + .../starter/mqtt/MqttAutoConfiguration.kt | 3 +++ .../de/smartsquare/starter/mqtt/MqttProperties.kt | 4 ++-- .../de/smartsquare/starter/mqtt/MqttSubscribe.kt | 2 ++ .../starter/mqtt/MqttSubscriberCollector.kt | 12 ++++++++++-- .../de/smartsquare/starter/mqtt/MqttHandlerTest.kt | 4 ++-- .../mqtt/MqttSubscriberCollectorIntegrationTests.kt | 2 +- .../starter/mqtt/MqttSubscriberCollectorTests.kt | 2 +- .../smartsquare/starter/mqtt/TestObjectProvider.kt | 10 ++++++++++ 10 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt diff --git a/README.md b/README.md index f4e0045..33333ec 100644 --- a/README.md +++ b/README.md @@ -163,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 diff --git a/build.gradle.kts b/build.gradle.kts index 468cb7d..6ed2a0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,6 +61,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 a209ec8..375d075 100644 --- a/src/main/kotlin/de/smartsquare/starter/mqtt/MqttProperties.kt +++ b/src/main/kotlin/de/smartsquare/starter/mqtt/MqttProperties.kt @@ -23,8 +23,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/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/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..df24c63 --- /dev/null +++ b/src/test/kotlin/de/smartsquare/starter/mqtt/TestObjectProvider.kt @@ -0,0 +1,10 @@ +package de.smartsquare.starter.mqtt + +import org.springframework.beans.factory.ObjectProvider + +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() +}