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
pull/18546/head
dreis2211 5 years ago committed by Phillip Webb
parent 89e7d5fb01
commit 33d8bfa99d

@ -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");
}
}
}

Loading…
Cancel
Save