Skip to content

Commit 5fa3e39

Browse files
committed
implement cached property resolver
1 parent 39757e1 commit 5fa3e39

File tree

3 files changed

+205
-125
lines changed

3 files changed

+205
-125
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties;
7+
8+
import java.util.Objects;
9+
import java.util.Optional;
10+
import java.util.concurrent.ConcurrentHashMap;
11+
import javax.annotation.Nullable;
12+
import org.springframework.core.env.Environment;
13+
14+
/**
15+
* A caching wrapper around Spring's {@link Environment} that caches property lookups to avoid
16+
* repeated expensive operations and property source traversal.
17+
*
18+
* <p>Thread-safe for concurrent access. Cached values persist indefinitely and are assumed to be
19+
* immutable after the first access. If properties can change at runtime, use {@link #clear()} to
20+
* invalidate the cache.
21+
*
22+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
23+
* at any time.
24+
*/
25+
final class CachedPropertyResolver {
26+
private final Environment environment;
27+
private final ConcurrentHashMap<CacheKey, Optional<?>> cache = new ConcurrentHashMap<>();
28+
29+
CachedPropertyResolver(Environment environment) {
30+
this.environment = Objects.requireNonNull(environment, "environment");
31+
}
32+
33+
/**
34+
* Gets a property value from the environment, using a cache to avoid repeated lookups.
35+
*
36+
* @param name the property name
37+
* @param targetType the target type to convert to
38+
* @return the property value, or null if not found
39+
*/
40+
@Nullable
41+
<T> T getProperty(String name, Class<T> targetType) {
42+
CacheKey key = new CacheKey(name, targetType);
43+
// CacheKey includes targetType, ensuring type match
44+
@SuppressWarnings("unchecked")
45+
Optional<T> result =
46+
(Optional<T>)
47+
cache.computeIfAbsent(
48+
key, k -> Optional.ofNullable(environment.getProperty(name, targetType)));
49+
return result.orElse(null);
50+
}
51+
52+
/**
53+
* Gets a string property value from the environment.
54+
*
55+
* @param name the property name
56+
* @return the property value, or null if not found
57+
*/
58+
@Nullable
59+
String getProperty(String name) {
60+
return getProperty(name, String.class);
61+
}
62+
63+
/** Clears all cached property values, forcing fresh lookups on subsequent calls. */
64+
void clear() {
65+
cache.clear();
66+
}
67+
68+
/** Cache key combining property name and target type. */
69+
private static final class CacheKey {
70+
private final String name;
71+
private final Class<?> targetType;
72+
private final int cachedHashCode;
73+
74+
CacheKey(String name, Class<?> targetType) {
75+
this.name = Objects.requireNonNull(name, "name");
76+
this.targetType = Objects.requireNonNull(targetType, "targetType");
77+
this.cachedHashCode = Objects.hash(name, targetType);
78+
}
79+
80+
@Override
81+
public boolean equals(Object obj) {
82+
if (this == obj) {
83+
return true;
84+
}
85+
if (!(obj instanceof CacheKey)) {
86+
return false;
87+
}
88+
CacheKey other = (CacheKey) obj;
89+
return name.equals(other.name) && targetType.equals(other.targetType);
90+
}
91+
92+
@Override
93+
public int hashCode() {
94+
return cachedHashCode;
95+
}
96+
}
97+
}

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java

Lines changed: 57 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515
import java.util.HashMap;
1616
import java.util.List;
1717
import java.util.Map;
18-
import java.util.Optional;
19-
import java.util.concurrent.ConcurrentHashMap;
20-
import java.util.function.Function;
2118
import javax.annotation.Nullable;
2219
import org.springframework.core.env.Environment;
2320
import org.springframework.expression.ExpressionParser;
@@ -28,7 +25,7 @@
2825
* any time.
2926
*/
3027
public class SpringConfigProperties implements ConfigProperties {
31-
private final Environment environment;
28+
private final CachedPropertyResolver cachedEnvironment;
3229

3330
private final ExpressionParser parser;
3431
private final OtlpExporterProperties otlpExporterProperties;
@@ -37,23 +34,6 @@ public class SpringConfigProperties implements ConfigProperties {
3734
private final ConfigProperties customizedListProperties;
3835
private final Map<String, List<String>> listPropertyValues;
3936

40-
private final ConcurrentHashMap<String, Optional<String>> cachedStringValues =
41-
new ConcurrentHashMap<>();
42-
private final ConcurrentHashMap<String, Optional<Boolean>> cachedBooleanValues =
43-
new ConcurrentHashMap<>();
44-
private final ConcurrentHashMap<String, Optional<Integer>> cachedIntValues =
45-
new ConcurrentHashMap<>();
46-
private final ConcurrentHashMap<String, Optional<Long>> cachedLongValues =
47-
new ConcurrentHashMap<>();
48-
private final ConcurrentHashMap<String, Optional<Double>> cachedDoubleValues =
49-
new ConcurrentHashMap<>();
50-
private final ConcurrentHashMap<String, Optional<List<String>>> cachedListValues =
51-
new ConcurrentHashMap<>();
52-
private final ConcurrentHashMap<String, Optional<Duration>> cachedDurationValues =
53-
new ConcurrentHashMap<>();
54-
private final ConcurrentHashMap<String, Optional<Map<String, String>>> cachedMapValues =
55-
new ConcurrentHashMap<>();
56-
5737
static final String DISABLED_KEY = "otel.java.disabled.resource.providers";
5838
static final String ENABLED_KEY = "otel.java.enabled.resource.providers";
5939

@@ -64,7 +44,7 @@ public SpringConfigProperties(
6444
OtelResourceProperties resourceProperties,
6545
OtelSpringProperties otelSpringProperties,
6646
ConfigProperties otelSdkProperties) {
67-
this.environment = environment;
47+
this.cachedEnvironment = new CachedPropertyResolver(environment);
6848
this.parser = parser;
6949
this.otlpExporterProperties = otlpExporterProperties;
7050
this.resourceProperties = resourceProperties;
@@ -168,150 +148,103 @@ public static ConfigProperties create(
168148
fallback);
169149
}
170150

171-
@Nullable
172-
private static <T> T getCachedValue(
173-
ConcurrentHashMap<String, Optional<T>> cache,
174-
String name,
175-
Function<String, T> valueFunction) {
176-
return cache
177-
.computeIfAbsent(name, key -> Optional.ofNullable(valueFunction.apply(key)))
178-
.orElse(null);
179-
}
180-
181151
@Nullable
182152
@Override
183153
public String getString(String name) {
184-
return getCachedValue(
185-
cachedStringValues,
186-
name,
187-
key -> {
188-
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key);
189-
String value = environment.getProperty(normalizedName, String.class);
190-
if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) {
191-
// SDK autoconfigure module defaults to `grpc`, but this module aligns with
192-
// recommendation in specification to default to `http/protobuf`
193-
return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF;
194-
}
195-
return or(value, otelSdkProperties.getString(key));
196-
});
154+
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name);
155+
String value = cachedEnvironment.getProperty(normalizedName, String.class);
156+
if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) {
157+
// SDK autoconfigure module defaults to `grpc`, but this module aligns with
158+
// recommendation in specification to default to `http/protobuf`
159+
return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF;
160+
}
161+
return or(value, otelSdkProperties.getString(name));
197162
}
198163

199164
@Nullable
200165
@Override
201166
public Boolean getBoolean(String name) {
202-
return getCachedValue(
203-
cachedBooleanValues,
204-
name,
205-
key ->
206-
or(
207-
environment.getProperty(
208-
ConfigUtil.normalizeEnvironmentVariableKey(key), Boolean.class),
209-
otelSdkProperties.getBoolean(key)));
167+
return or(
168+
cachedEnvironment.getProperty(
169+
ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class),
170+
otelSdkProperties.getBoolean(name));
210171
}
211172

212173
@Nullable
213174
@Override
214175
public Integer getInt(String name) {
215-
return getCachedValue(
216-
cachedIntValues,
217-
name,
218-
key ->
219-
or(
220-
environment.getProperty(
221-
ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class),
222-
otelSdkProperties.getInt(key)));
176+
return or(
177+
cachedEnvironment.getProperty(
178+
ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class),
179+
otelSdkProperties.getInt(name));
223180
}
224181

225182
@Nullable
226183
@Override
227184
public Long getLong(String name) {
228-
return getCachedValue(
229-
cachedLongValues,
230-
name,
231-
key ->
232-
or(
233-
environment.getProperty(
234-
ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class),
235-
otelSdkProperties.getLong(key)));
185+
return or(
186+
cachedEnvironment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class),
187+
otelSdkProperties.getLong(name));
236188
}
237189

238190
@Nullable
239191
@Override
240192
public Double getDouble(String name) {
241-
return getCachedValue(
242-
cachedDoubleValues,
243-
name,
244-
key ->
245-
or(
246-
environment.getProperty(
247-
ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class),
248-
otelSdkProperties.getDouble(key)));
193+
return or(
194+
cachedEnvironment.getProperty(
195+
ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class),
196+
otelSdkProperties.getDouble(name));
249197
}
250198

251199
@SuppressWarnings("unchecked")
252200
@Override
253201
public List<String> getList(String name) {
254-
return getCachedValue(
255-
cachedListValues,
256-
name,
257-
key -> {
258-
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key);
202+
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name);
259203

260-
List<String> list = listPropertyValues.get(normalizedName);
261-
if (list != null) {
262-
List<String> c = customizedListProperties.getList(key);
263-
if (!c.isEmpty()) {
264-
return c;
265-
}
266-
if (!list.isEmpty()) {
267-
return list;
268-
}
269-
}
204+
List<String> list = listPropertyValues.get(normalizedName);
205+
if (list != null) {
206+
List<String> c = customizedListProperties.getList(name);
207+
if (!c.isEmpty()) {
208+
return c;
209+
}
210+
if (!list.isEmpty()) {
211+
return list;
212+
}
213+
}
270214

271-
List<String> envValue =
272-
(List<String>) environment.getProperty(normalizedName, List.class);
273-
return or(envValue, otelSdkProperties.getList(key));
274-
});
215+
List<String> envValue =
216+
(List<String>) cachedEnvironment.getProperty(normalizedName, List.class);
217+
return or(envValue, otelSdkProperties.getList(name));
275218
}
276219

277220
@Nullable
278221
@Override
279222
public Duration getDuration(String name) {
280-
return getCachedValue(
281-
cachedDurationValues,
282-
name,
283-
key -> {
284-
String value = getString(key);
285-
if (value == null) {
286-
return otelSdkProperties.getDuration(key);
287-
}
288-
return DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value))
289-
.getDuration(key);
290-
});
223+
String value = getString(name);
224+
if (value == null) {
225+
return otelSdkProperties.getDuration(name);
226+
}
227+
return DefaultConfigProperties.createFromMap(Collections.singletonMap(name, value))
228+
.getDuration(name);
291229
}
292230

293231
@SuppressWarnings("unchecked")
294232
@Override
295233
public Map<String, String> getMap(String name) {
296-
return getCachedValue(
297-
cachedMapValues,
298-
name,
299-
key -> {
300-
Map<String, String> otelSdkMap = otelSdkProperties.getMap(key);
234+
Map<String, String> otelSdkMap = otelSdkProperties.getMap(name);
301235

302-
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key);
303-
// maps from config properties are not supported by Environment, so we have to fake it
304-
Map<String, String> specialMap = getSpecialMapProperty(normalizedName, otelSdkMap);
305-
if (specialMap != null) {
306-
return specialMap;
307-
}
236+
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name);
237+
// maps from config properties are not supported by Environment, so we have to fake it
238+
Map<String, String> specialMap = getSpecialMapProperty(normalizedName, otelSdkMap);
239+
if (specialMap != null) {
240+
return specialMap;
241+
}
308242

309-
String value = environment.getProperty(normalizedName);
310-
if (value == null) {
311-
return otelSdkMap;
312-
}
313-
return (Map<String, String>) parser.parseExpression(value).getValue();
314-
});
243+
String value = cachedEnvironment.getProperty(normalizedName);
244+
if (value == null) {
245+
return otelSdkMap;
246+
}
247+
return (Map<String, String>) parser.parseExpression(value).getValue();
315248
}
316249

317250
@Nullable

0 commit comments

Comments
 (0)