Share internal StandardExecutionContext delegates

This commit makes sure that the per-operation execution context for
caching and event listening does not recreate the default internal
delegates, but rather get initialized with a shared state.

This reduces the number of instances created per operation execution,
reducing the GC pressure as a result. This also makes sure that any
cache, such as the one in StandardTypeLocator, is reused.

Closes gh-31617
This commit is contained in:
Stéphane Nicoll
2023-11-17 12:20:28 +01:00
parent 7006d0a80e
commit ec905cb073
12 changed files with 288 additions and 33 deletions

View File

@@ -31,9 +31,12 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -47,7 +50,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
public class ExpressionEvaluatorTests {
private final CacheOperationExpressionEvaluator eval = new CacheOperationExpressionEvaluator();
private final StandardEvaluationContext originalEvaluationContext = new StandardEvaluationContext();
private final CacheOperationExpressionEvaluator eval = new CacheOperationExpressionEvaluator(
new CacheEvaluationContextFactory(this.originalEvaluationContext));
private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
@@ -82,7 +88,7 @@ public class ExpressionEvaluatorTests {
Collection<ConcurrentMapCache> caches = Collections.singleton(new ConcurrentMapCache("test"));
EvaluationContext evalCtx = this.eval.createEvaluationContext(caches, method, args,
target, target.getClass(), method, CacheOperationExpressionEvaluator.NO_RESULT, null);
target, target.getClass(), method, CacheOperationExpressionEvaluator.NO_RESULT);
Collection<CacheOperation> ops = getOps("multipleCaching");
Iterator<CacheOperation> it = ops.iterator();
@@ -140,14 +146,17 @@ public class ExpressionEvaluatorTests {
return createEvaluationContext(result, null);
}
private EvaluationContext createEvaluationContext(Object result, BeanFactory beanFactory) {
private EvaluationContext createEvaluationContext(Object result, @Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
this.originalEvaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
AnnotatedClass target = new AnnotatedClass();
Method method = ReflectionUtils.findMethod(
AnnotatedClass.class, "multipleCaching", Object.class, Object.class);
Object[] args = new Object[] {new Object(), new Object()};
Collection<ConcurrentMapCache> caches = Collections.singleton(new ConcurrentMapCache("test"));
return this.eval.createEvaluationContext(
caches, method, args, target, target.getClass(), method, result, beanFactory);
caches, method, args, target, target.getClass(), method, result);
}

View File

@@ -31,6 +31,7 @@ import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.ResolvableTypeProvider;
import org.springframework.core.annotation.Order;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -322,7 +323,7 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
given(this.context.getBean("testBean")).willReturn(this.sampleEvents);
ApplicationListenerMethodAdapter listener = new ApplicationListenerMethodAdapter(
"testBean", GenericTestEvent.class, method);
listener.init(this.context, new EventExpressionEvaluator());
listener.init(this.context, new EventExpressionEvaluator(new StandardEvaluationContext()));
GenericTestEvent<String> event = createGenericTestEvent("test");