Skip to content

Commit db46db5

Browse files
committed
#1723 - Fix potential resource leak in DummyInvocationUtils.
We now constrain the cache of controller proxy instances to 256 elements using Spring's ConcurrentLruCache to avoid instances created via DummyInvocationUtils.methodOn(Class<?>, Object…). The parameters are part of the cache key and used to expand the type-level mappings. If those vary for each call and a request creates a lot of links (>100000) the memory consumption grows significantly, first and foremost indefinitely. Using the ThreadLocal will still make sure that the cache is local to a current request, so the proxies can actually be reused as the method invocations used to record the mappings would interfere for concurrent requests otherwise. Removed obsolete generic parameter on the CacheKey type.
1 parent c4b4e6d commit db46db5

File tree

1 file changed

+16
-15
lines changed

1 file changed

+16
-15
lines changed

src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717

1818
import java.lang.reflect.Method;
1919
import java.util.Arrays;
20-
import java.util.HashMap;
2120
import java.util.Iterator;
22-
import java.util.Map;
2321
import java.util.Objects;
2422

2523
import org.aopalliance.intercept.MethodInterceptor;
@@ -28,6 +26,7 @@
2826
import org.springframework.aop.target.EmptyTargetSource;
2927
import org.springframework.lang.Nullable;
3028
import org.springframework.util.Assert;
29+
import org.springframework.util.ConcurrentLruCache;
3130
import org.springframework.util.ReflectionUtils;
3231

3332
/**
@@ -37,7 +36,14 @@
3736
*/
3837
public class DummyInvocationUtils {
3938

40-
private static final ThreadLocal<Map<CacheKey<?>, Object>> CACHE = ThreadLocal.withInitial(HashMap::new);
39+
private static final ThreadLocal<ConcurrentLruCache<CacheKey, Object>> CACHE = ThreadLocal
40+
.withInitial(() -> new ConcurrentLruCache<CacheKey, Object>(256,
41+
it -> {
42+
43+
InvocationRecordingMethodInterceptor interceptor = new InvocationRecordingMethodInterceptor(it.type,
44+
it.arguments);
45+
return getProxyWithInterceptor(it.type, interceptor, it.type.getClassLoader());
46+
}));
4147

4248
/**
4349
* Method interceptor that records the last method invocation and creates a proxy for the return value that exposes
@@ -127,12 +133,7 @@ public static <T> T methodOn(Class<T> type, Object... parameters) {
127133

128134
Assert.notNull(type, "Given type must not be null!");
129135

130-
return (T) CACHE.get().computeIfAbsent(CacheKey.of(type, parameters), it -> {
131-
132-
InvocationRecordingMethodInterceptor interceptor = new InvocationRecordingMethodInterceptor(it.type,
133-
it.arguments);
134-
return getProxyWithInterceptor(it.type, interceptor, type.getClassLoader());
135-
});
136+
return (T) CACHE.get().get(CacheKey.of(type, parameters));
136137
}
137138

138139
/**
@@ -202,19 +203,19 @@ private static <T> T getProxyWithInterceptor(Class<?> type, InvocationRecordingM
202203
return (T) factory.getProxy(classLoader);
203204
}
204205

205-
private static final class CacheKey<T> {
206+
private static final class CacheKey {
206207

207-
private final Class<T> type;
208+
private final Class<?> type;
208209
private final Object[] arguments;
209210

210-
private CacheKey(Class<T> type, Object[] arguments) {
211+
private CacheKey(Class<?> type, Object[] arguments) {
211212

212213
this.type = type;
213214
this.arguments = arguments;
214215
}
215216

216-
public static <T> CacheKey<T> of(Class<T> type, Object[] arguments) {
217-
return new CacheKey<T>(type, arguments);
217+
public static CacheKey of(Class<?> type, Object[] arguments) {
218+
return new CacheKey(type, arguments);
218219
}
219220

220221
/*
@@ -232,7 +233,7 @@ public boolean equals(@Nullable Object o) {
232233
return false;
233234
}
234235

235-
CacheKey<?> cacheKey = (CacheKey<?>) o;
236+
CacheKey cacheKey = (CacheKey) o;
236237

237238
return Objects.equals(this.type, cacheKey.type) //
238239
&& Arrays.equals(this.arguments, cacheKey.arguments);

0 commit comments

Comments
 (0)