diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java index b2b9d6bbee..91c5ec490c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.metrics; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -81,15 +82,15 @@ public class MetricsEndpoint { public MetricResponse metric(@Selector String requiredMetricName, @Nullable List tag) { List tags = parseTags(tag); - List meters = new ArrayList<>(); - collectMeters(meters, this.registry, requiredMetricName, tags); + Collection meters = findFirstMatchingMeters(this.registry, + requiredMetricName, tags); if (meters.isEmpty()) { return null; } Map samples = getSamples(meters); Map> availableTags = getAvailableTags(meters); tags.forEach((t) -> availableTags.remove(t.getKey())); - Meter.Id meterId = meters.get(0).getId(); + Meter.Id meterId = meters.iterator().next().getId(); return new MetricResponse(requiredMetricName, meterId.getDescription(), meterId.getBaseUnit(), asList(samples, Sample::new), asList(availableTags, AvailableTag::new)); @@ -112,18 +113,23 @@ public class MetricsEndpoint { return Tag.of(parts[0], parts[1]); } - private void collectMeters(List meters, MeterRegistry registry, String name, + private Collection findFirstMatchingMeters(MeterRegistry registry, String name, Iterable tags) { if (registry instanceof CompositeMeterRegistry) { - ((CompositeMeterRegistry) registry).getRegistries() - .forEach((member) -> collectMeters(meters, member, name, tags)); - } - else { - meters.addAll(registry.find(name).tags(tags).meters()); + return findFirstMatchingMeters((CompositeMeterRegistry) registry, name, tags); } + return registry.find(name).tags(tags).meters(); } - private Map getSamples(List meters) { + private Collection findFirstMatchingMeters(CompositeMeterRegistry composite, + String name, Iterable tags) { + return composite.getRegistries().stream() + .map((registry) -> findFirstMatchingMeters(registry, name, tags)) + .filter((matching) -> !matching.isEmpty()).findFirst() + .orElse(Collections.emptyList()); + } + + private Map getSamples(Collection meters) { Map samples = new LinkedHashMap<>(); meters.forEach((meter) -> mergeMeasurements(samples, meter)); return samples; @@ -138,7 +144,7 @@ public class MetricsEndpoint { return Statistic.MAX.equals(statistic) ? Double::max : Double::sum; } - private Map> getAvailableTags(List meters) { + private Map> getAvailableTags(Collection meters) { Map> availableTags = new HashMap<>(); meters.forEach((meter) -> mergeAvailableTags(availableTags, meter)); return availableTags; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java index 9cf73189a9..f73d0bf649 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java @@ -91,6 +91,42 @@ public class MetricsEndpointTests { assertThat(getCount(response)).hasValue(4.0); } + @Test + public void findFirstMatchingMetersFromNestedRegistries() { + CompositeMeterRegistry composite = new CompositeMeterRegistry(); + SimpleMeterRegistry firstLevel0 = new SimpleMeterRegistry(); + CompositeMeterRegistry firstLevel1 = new CompositeMeterRegistry(); + SimpleMeterRegistry secondLevel = new SimpleMeterRegistry(); + composite.add(firstLevel0); + composite.add(firstLevel1); + firstLevel1.add(secondLevel); + secondLevel.counter("cache", "result", "hit", "host", "1").increment(2); + secondLevel.counter("cache", "result", "miss", "host", "1").increment(2); + secondLevel.counter("cache", "result", "hit", "host", "2").increment(2); + MetricsEndpoint endpoint = new MetricsEndpoint(composite); + MetricsEndpoint.MetricResponse response = endpoint.metric("cache", + Collections.emptyList()); + assertThat(response.getName()).isEqualTo("cache"); + assertThat(availableTagKeys(response)).containsExactly("result", "host"); + assertThat(getCount(response)).hasValue(6.0); + response = endpoint.metric("cache", Collections.singletonList("result:hit")); + assertThat(availableTagKeys(response)).containsExactly("host"); + assertThat(getCount(response)).hasValue(4.0); + } + + @Test + public void matchingMeterNotFoundInNestedRegistries() { + CompositeMeterRegistry composite = new CompositeMeterRegistry(); + CompositeMeterRegistry firstLevel = new CompositeMeterRegistry(); + SimpleMeterRegistry secondLevel = new SimpleMeterRegistry(); + composite.add(firstLevel); + firstLevel.add(secondLevel); + MetricsEndpoint endpoint = new MetricsEndpoint(composite); + MetricsEndpoint.MetricResponse response = endpoint.metric("invalid.metric.name", + Collections.emptyList()); + assertThat(response).isNull(); + } + @Test public void metricTagValuesAreDeduplicated() { this.registry.counter("cache", "host", "1", "region", "east", "result", "hit"); diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 832b0bf45b..9bd9058217 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -652,6 +652,21 @@ details. +[[boot-features-encrypting-properties]] +=== Encrypting Properties +Spring Boot does not provide any built in support for encrypting property values, however, +it does provide the hook points necessary to modify values contained in the Spring +`Environment`. The `EnvironmentPostProcessor` interface allows you to manipulate the +`Environment` before the application starts. See <> +for details. + +If you're looking for a secure way to store credentials and passwords, the +https://cloud.spring.io/spring-cloud-vault/[Spring Cloud Vault] project provides +support for storing externalized configuration in +https://www.vaultproject.io/[HashiCorp Vault]. + + + [[boot-features-external-config-yaml]] === Using YAML Instead of Properties http://yaml.org[YAML] is a superset of JSON and, as such, is a convenient format for @@ -6959,6 +6974,10 @@ that the driver exits after each test and that a new instance is injected. If yo not want this behavior, you can add `@Scope("singleton")` to your `WebDriver` `@Bean` definition. +WARNING: The `webDriver` scope created by Spring Boot will replace any user defined scope +of the same name. If you define your own `webDriver` scope you may find it stops working +when you use `@WebMvcTest`. + If you have Spring Security on the classpath, `@WebMvcTest` will also scan `WebSecurityConfigurer` beans. Instead of disabling security completely for such tests, you can use Spring Security's test support. More details on how to use Spring Security's `MockMvc` support can be found in diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 1d3c879023..598dce8f79 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -749,16 +749,23 @@ listings for Maven and Gradle: .Gradle [source,groovy,indent=0,subs="attributes"] ---- + configurations { + developmentOnly + runtimeClasspath { + extendsFrom developmentOnly + } + } dependencies { - compile("org.springframework.boot:spring-boot-devtools") + developmentOnly("org.springframework.boot:spring-boot-devtools") } ---- NOTE: Developer tools are automatically disabled when running a fully packaged application. If your application is launched from `java -jar` or if it is started from a special classloader, then it is considered a "`production application`". Flagging the -dependency as optional in Maven or using `compileOnly` in Gradle is a best practice that -prevents devtools from being transitively applied to other modules that use your project. +dependency as optional in Maven or using a custom`developmentOnly` configuration in +Gradle (as shown above) is a best practice that prevents devtools from being transitively +applied to other modules that use your project. TIP: Repackaged archives do not contain devtools by default. If you want to use a <>, you need to disable the