Apply TTL invocation caching on reactor types
Update `CachingOperationInvoker` so that TTL caching is applied directly to reactive types. Prior to this commit, a `Mono` would be cached, but the values that it emitted would not. See gh-18339
This commit is contained in:
@@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.invoker.cache;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -67,13 +70,20 @@ public class CachingOperationInvoker implements OperationInvoker {
|
||||
long accessTime = System.currentTimeMillis();
|
||||
CachedResponse cached = this.cachedResponse;
|
||||
if (cached == null || cached.isStale(accessTime, this.timeToLive)) {
|
||||
Object response = this.invoker.invoke(context);
|
||||
Object response = handleMonoResponse(this.invoker.invoke(context));
|
||||
this.cachedResponse = new CachedResponse(response, accessTime);
|
||||
return response;
|
||||
}
|
||||
return cached.getResponse();
|
||||
}
|
||||
|
||||
private Object handleMonoResponse(Object response) {
|
||||
if (response instanceof Mono) {
|
||||
return ((Mono) response).cache(Duration.ofMillis(this.timeToLive));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private boolean hasInput(InvocationContext context) {
|
||||
if (context.getSecurityContext().getPrincipal() != null) {
|
||||
return true;
|
||||
|
||||
@@ -17,15 +17,20 @@
|
||||
package org.springframework.boot.actuate.endpoint.invoker.cache;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.test.rule.OutputCapture;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -42,6 +47,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
*/
|
||||
public class CachingOperationInvokerTests {
|
||||
|
||||
@Rule
|
||||
public OutputCapture outputCapture = new OutputCapture();
|
||||
|
||||
@Test
|
||||
public void createInstanceWithTtlSetToZero() {
|
||||
assertThatIllegalArgumentException()
|
||||
@@ -62,6 +70,21 @@ public class CachingOperationInvokerTests {
|
||||
assertCacheIsUsed(parameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheInTtlWithMonoResponse() {
|
||||
MonoOperationInvoker target = new MonoOperationInvoker();
|
||||
InvocationContext context = new InvocationContext(mock(SecurityContext.class), Collections.emptyMap());
|
||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
||||
Object monoResponse = invoker.invoke(context);
|
||||
assertThat(monoResponse).isInstanceOf(Mono.class);
|
||||
Object response = ((Mono) monoResponse).block(Duration.ofSeconds(30));
|
||||
Object cachedMonoResponse = invoker.invoke(context);
|
||||
assertThat(cachedMonoResponse).isInstanceOf(Mono.class);
|
||||
Object cachedResponse = ((Mono) cachedMonoResponse).block(Duration.ofSeconds(30));
|
||||
assertThat(response).isSameAs(cachedResponse);
|
||||
assertThat(this.outputCapture.toString()).containsOnlyOnce("invoked");
|
||||
}
|
||||
|
||||
private void assertCacheIsUsed(Map<String, Object> parameters) {
|
||||
OperationInvoker target = mock(OperationInvoker.class);
|
||||
Object expected = new Object();
|
||||
@@ -119,4 +142,18 @@ public class CachingOperationInvokerTests {
|
||||
verify(target, times(2)).invoke(context);
|
||||
}
|
||||
|
||||
private static class MonoOperationInvoker implements OperationInvoker {
|
||||
|
||||
@Override
|
||||
public Object invoke(InvocationContext context) throws MissingParametersException {
|
||||
return Mono.fromCallable(this::printInvocation);
|
||||
}
|
||||
|
||||
private Mono<String> printInvocation() {
|
||||
System.out.println("MonoOperationInvoker invoked");
|
||||
return Mono.just("test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user