From 6f1bcc4bfa872f6882f51b0d34a66bbc95e53903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Fri, 3 Oct 2025 15:48:57 +0200 Subject: [PATCH] Polish "Add support for Neo4j Java Driver 6.0.0" See gh-47381 --- .../reference/pages/actuator/metrics.adoc | 7 +++ module/spring-boot-neo4j/build.gradle | 4 +- .../autoconfigure/Neo4jAutoConfiguration.java | 45 ++------------- .../neo4j/autoconfigure/Neo4jProperties.java | 13 ----- .../Neo4jObservationAutoConfiguration.java | 49 ++++++++++++++++ .../observation/package-info.java | 23 ++++++++ ...itional-spring-configuration-metadata.json | 8 +++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../Neo4jAutoConfigurationTests.java | 13 +---- ...eo4jObservationAutoConfigurationTests.java | 56 +++++++++++++++++++ 10 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfiguration.java create mode 100644 module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/package-info.java create mode 100644 module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfigurationTests.java diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc index 67027624999..5b738008eea 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc @@ -1042,6 +1042,13 @@ management: +[[actuator.metrics.supported.neo4j]] +=== Neo4j Metrics + +Auto-configuration registers a javadoc:org.neo4j.driver.observation.micrometer.MicrometerObservationProvider[] for the auto-configured javadoc:org.neo4j.driver.Driver[]. + + + [[actuator.metrics.supported.jetty]] === Jetty Metrics diff --git a/module/spring-boot-neo4j/build.gradle b/module/spring-boot-neo4j/build.gradle index 7f846c191d3..2d1ac21c271 100644 --- a/module/spring-boot-neo4j/build.gradle +++ b/module/spring-boot-neo4j/build.gradle @@ -35,8 +35,9 @@ dependencies { optional(project(":core:spring-boot-docker-compose")) optional(project(":core:spring-boot-testcontainers")) optional(project(":module:spring-boot-health")) - optional("org.testcontainers:neo4j") + optional(project(":module:spring-boot-micrometer-observation")) optional("org.neo4j.driver:neo4j-java-driver-observation-micrometer") + optional("org.testcontainers:neo4j") dockerTestImplementation(project(":core:spring-boot-test")) dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) @@ -49,6 +50,7 @@ dependencies { testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-testcontainers"))) + testImplementation("io.micrometer:micrometer-observation-test") testImplementation("io.projectreactor:reactor-test") testRuntimeOnly("ch.qos.logback:logback-classic") diff --git a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java index 97d108eac2d..9e011967e54 100644 --- a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java +++ b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java @@ -21,12 +21,9 @@ import java.net.URI; import java.time.Duration; import java.util.List; import java.util.Locale; -import java.util.ServiceLoader; import java.util.concurrent.TimeUnit; -import io.micrometer.observation.ObservationRegistry; import org.jspecify.annotations.Nullable; -import org.neo4j.bolt.connection.BoltConnectionProviderFactory; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.AuthTokens; @@ -35,7 +32,6 @@ import org.neo4j.driver.Config.TrustStrategy; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.internal.Scheme; -import org.neo4j.driver.observation.micrometer.MicrometerObservationProvider; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -48,7 +44,6 @@ import org.springframework.boot.neo4j.autoconfigure.Neo4jProperties.Authenticati import org.springframework.boot.neo4j.autoconfigure.Neo4jProperties.Pool; import org.springframework.boot.neo4j.autoconfigure.Neo4jProperties.Security; import org.springframework.context.annotation.Bean; -import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -67,20 +62,6 @@ import org.springframework.util.StringUtils; @EnableConfigurationProperties(Neo4jProperties.class) public final class Neo4jAutoConfiguration { - private static final boolean HAS_DRIVER_METRICS; - - static { - boolean metricsObservationProviderFound = true; - try { - Class.forName("org.neo4j.driver.observation.micrometer.MicrometerObservationProvider", false, - Neo4jAutoConfiguration.class.getClassLoader()); - } - catch (ClassNotFoundException ex) { - metricsObservationProviderFound = false; - } - HAS_DRIVER_METRICS = metricsObservationProviderFound; - } - @Bean @ConditionalOnMissingBean(Neo4jConnectionDetails.class) PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties, @@ -90,12 +71,10 @@ public final class Neo4jAutoConfiguration { @Bean @ConditionalOnMissingBean - Driver neo4jDriver(Neo4jProperties properties, Environment environment, Neo4jConnectionDetails connectionDetails, - ObjectProvider configBuilderCustomizers, - ObjectProvider observationRegistryProvider) { - + Driver neo4jDriver(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails, + ObjectProvider configBuilderCustomizers) { Config config = mapDriverConfig(properties, connectionDetails, - configBuilderCustomizers.orderedStream().toList(), observationRegistryProvider); + configBuilderCustomizers.orderedStream().toList()); AuthTokenManager authTokenManager = connectionDetails.getAuthTokenManager(); if (authTokenManager != null) { return GraphDatabase.driver(connectionDetails.getUri(), authTokenManager, config); @@ -105,10 +84,9 @@ public final class Neo4jAutoConfiguration { } Config mapDriverConfig(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails, - List customizers, - ObjectProvider observationRegistryProvider) { + List customizers) { Config.ConfigBuilder builder = Config.builder(); - configurePoolSettings(builder, properties.getPool(), observationRegistryProvider); + configurePoolSettings(builder, properties.getPool()); URI uri = connectionDetails.getUri(); String scheme = (uri != null) ? uri.getScheme() : "bolt"; configureDriverSettings(builder, properties, isSimpleScheme(scheme)); @@ -118,16 +96,10 @@ public final class Neo4jAutoConfiguration { private boolean isSimpleScheme(String scheme) { String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH); - if (!ServiceLoader.load(BoltConnectionProviderFactory.class) - .stream() - .anyMatch((p) -> p.get().supports(lowerCaseScheme))) { - throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme)); - } return !Scheme.isSecurityScheme(lowerCaseScheme); } - private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool, - ObjectProvider observationRegistryProvider) { + private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool) { if (pool.isLogLeakedSessions()) { builder.withLeakedSessionsLogging(); } @@ -139,11 +111,6 @@ public final class Neo4jAutoConfiguration { builder.withMaxConnectionLifetime(pool.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS); builder.withConnectionAcquisitionTimeout(pool.getConnectionAcquisitionTimeout().toMillis(), TimeUnit.MILLISECONDS); - observationRegistryProvider.ifAvailable((orp) -> { - if (pool.isMetricsEnabled() && HAS_DRIVER_METRICS) { - builder.withObservationProvider(MicrometerObservationProvider.builder(orp).build()); - } - }); } private void configureDriverSettings(Config.ConfigBuilder builder, Neo4jProperties properties, diff --git a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jProperties.java b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jProperties.java index 93b0a10e920..ff96ab80a1f 100644 --- a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jProperties.java +++ b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jProperties.java @@ -150,11 +150,6 @@ public class Neo4jProperties { public static class Pool { - /** - * Whether to enable metrics. - */ - private boolean metricsEnabled = false; - /** * Whether to log leaked sessions. */ @@ -223,14 +218,6 @@ public class Neo4jProperties { this.connectionAcquisitionTimeout = connectionAcquisitionTimeout; } - public boolean isMetricsEnabled() { - return this.metricsEnabled; - } - - public void setMetricsEnabled(boolean metricsEnabled) { - this.metricsEnabled = metricsEnabled; - } - } public static class Security { diff --git a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfiguration.java b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfiguration.java new file mode 100644 index 00000000000..9a62bf346db --- /dev/null +++ b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.neo4j.autoconfigure.observation; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import org.neo4j.driver.Config.ConfigBuilder; +import org.neo4j.driver.observation.micrometer.MicrometerObservationProvider; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.neo4j.autoconfigure.ConfigBuilderCustomizer; +import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * Auto-configuration for Neo4j observability. + * + * @author Stephane Nicoll + * @since 4.0.0 + */ +@AutoConfiguration(before = Neo4jAutoConfiguration.class, + afterName = "org.springframework.boot.micrometer.observation.autoconfigure.ObservationAutoConfiguration") +@ConditionalOnBean(ObservationRegistry.class) +@ConditionalOnClass({ ConfigBuilder.class, MicrometerObservationProvider.class, Observation.class }) +public final class Neo4jObservationAutoConfiguration { + + @Bean + @ConditionalOnBean(ObservationRegistry.class) + ConfigBuilderCustomizer neo4jObservationCustomizer(ObservationRegistry registry) { + return (builder) -> builder.withObservationProvider(MicrometerObservationProvider.builder(registry).build()); + } + +} diff --git a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/package-info.java b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/package-info.java new file mode 100644 index 00000000000..b865e781942 --- /dev/null +++ b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/observation/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for Neo4j observation. + */ +@NullMarked +package org.springframework.boot.neo4j.autoconfigure.observation; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-neo4j/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/module/spring-boot-neo4j/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 84461cc911e..1444e8722c3 100644 --- a/module/spring-boot-neo4j/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/module/spring-boot-neo4j/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -7,6 +7,14 @@ "description": "Whether to enable Neo4j health check.", "defaultValue": true }, + { + "name": "spring.neo4j.pool.metrics-enabled", + "type": "java.lang.Boolean", + "deprecation": { + "reason": "Use 'management.metrics.enable' to restrict certain metrics.", + "level": "error" + } + }, { "name": "spring.neo4j.uri", "defaultValue": "bolt://localhost:7687" diff --git a/module/spring-boot-neo4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-neo4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index b39844ff111..c3878ba7355 100644 --- a/module/spring-boot-neo4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/module/spring-boot-neo4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration org.springframework.boot.neo4j.autoconfigure.health.Neo4jHealthContributorAutoConfiguration +org.springframework.boot.neo4j.autoconfigure.observation.Neo4jObservationAutoConfiguration diff --git a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java index 83298f89c25..756e28234c1 100644 --- a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java +++ b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java @@ -21,9 +21,7 @@ import java.io.IOException; import java.net.URI; import java.time.Duration; import java.util.Arrays; -import java.util.stream.Stream; -import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -34,7 +32,6 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Config.ConfigBuilder; import org.neo4j.driver.Driver; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration.PropertiesNeo4jConnectionDetails; @@ -105,7 +102,7 @@ class Neo4jAutoConfigurationTests { this.contextRunner.withPropertyValues("spring.neo4j.uri=" + invalidScheme + "://localhost:4711") .run((ctx) -> assertThat(ctx).hasFailed() .getFailure() - .hasMessageContaining("'%s' is not a supported scheme.", invalidScheme)); + .hasMessageContaining("Unsupported scheme: %s", invalidScheme)); } @Test @@ -320,13 +317,7 @@ class Neo4jAutoConfigurationTests { private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) { return new Neo4jAutoConfiguration().mapDriverConfig(properties, - new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers), - new ObjectProvider<>() { - @Override - public Stream stream() { - return Stream.empty(); - } - }); + new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers)); } } diff --git a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfigurationTests.java b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfigurationTests.java new file mode 100644 index 00000000000..c027d2a41d9 --- /dev/null +++ b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/observation/Neo4jObservationAutoConfigurationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.neo4j.autoconfigure.observation; + +import io.micrometer.observation.tck.TestObservationRegistry; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.Driver; +import org.neo4j.driver.internal.observation.NoopObservationProvider; +import org.neo4j.driver.observation.micrometer.MicrometerObservationProvider; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Neo4jObservationAutoConfiguration}. + * + * @author Stephane Nicoll + */ +class Neo4jObservationAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(Neo4jObservationAutoConfiguration.class)); + + @Test + void whenThereIsAnObservationRegistryThenMicrometerObservationProviderIsAdded() { + this.contextRunner.withBean(TestObservationRegistry.class, TestObservationRegistry::create) + .withConfiguration(AutoConfigurations.of(Neo4jAutoConfiguration.class)) + .run((context) -> assertThat(context.getBean(Driver.class)).extracting("observationProvider") + .isInstanceOf(MicrometerObservationProvider.class)); + } + + @Test + void whenThereIsNoObservationRegistryThenConfigBuilderCustomizationBacksOff() { + this.contextRunner.withConfiguration(AutoConfigurations.of(Neo4jAutoConfiguration.class)) + .run((context) -> assertThat(context.getBean(Driver.class)).extracting("observationProvider") + .isInstanceOf(NoopObservationProvider.class)); + } + +}