Skip to content

Commit 869a8c2

Browse files
committed
Merge branch '2.1.x'
Closes gh-18411
2 parents fab80d6 + a1fb1bc commit 869a8c2

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,32 @@
1616

1717
package org.springframework.boot.actuate.endpoint.invoker.cache;
1818

19+
import java.time.Duration;
1920
import java.util.Map;
2021
import java.util.Objects;
2122

23+
import reactor.core.publisher.Flux;
24+
import reactor.core.publisher.Mono;
25+
2226
import org.springframework.boot.actuate.endpoint.InvocationContext;
2327
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
2428
import org.springframework.util.Assert;
29+
import org.springframework.util.ClassUtils;
2530
import org.springframework.util.ObjectUtils;
2631

2732
/**
2833
* An {@link OperationInvoker} that caches the response of an operation with a
2934
* configurable time to live.
3035
*
3136
* @author Stephane Nicoll
37+
* @author Christoph Dreis
38+
* @author Phillip Webb
3239
* @since 2.0.0
3340
*/
3441
public class CachingOperationInvoker implements OperationInvoker {
3542

43+
private static final boolean IS_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Mono", null);
44+
3645
private final OperationInvoker invoker;
3746

3847
private final long timeToLive;
@@ -68,8 +77,8 @@ public Object invoke(InvocationContext context) {
6877
CachedResponse cached = this.cachedResponse;
6978
if (cached == null || cached.isStale(accessTime, this.timeToLive)) {
7079
Object response = this.invoker.invoke(context);
71-
this.cachedResponse = new CachedResponse(response, accessTime);
72-
return response;
80+
cached = createCachedResponse(response, accessTime);
81+
this.cachedResponse = cached;
7382
}
7483
return cached.getResponse();
7584
}
@@ -85,6 +94,13 @@ private boolean hasInput(InvocationContext context) {
8594
return false;
8695
}
8796

97+
private CachedResponse createCachedResponse(Object response, long accessTime) {
98+
if (IS_REACTOR_PRESENT) {
99+
return new ReactiveCachedResponse(response, accessTime, this.timeToLive);
100+
}
101+
return new CachedResponse(response, accessTime);
102+
}
103+
88104
/**
89105
* Apply caching configuration when appropriate to the given invoker.
90106
* @param invoker the invoker to wrap
@@ -124,4 +140,25 @@ Object getResponse() {
124140

125141
}
126142

143+
/**
144+
* {@link CachedResponse} variant used when Reactor is present.
145+
*/
146+
static class ReactiveCachedResponse extends CachedResponse {
147+
148+
ReactiveCachedResponse(Object response, long creationTime, long timeToLive) {
149+
super(applyCaching(response, timeToLive), creationTime);
150+
}
151+
152+
private static Object applyCaching(Object response, long timeToLive) {
153+
if (response instanceof Mono) {
154+
return ((Mono<?>) response).cache(Duration.ofMillis(timeToLive));
155+
}
156+
if (response instanceof Flux) {
157+
return ((Flux<?>) response).cache(Duration.ofMillis(timeToLive));
158+
}
159+
return response;
160+
}
161+
162+
}
163+
127164
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package org.springframework.boot.actuate.endpoint.invoker.cache;
1818

1919
import java.security.Principal;
20+
import java.util.Arrays;
2021
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.Map;
2324

2425
import org.junit.jupiter.api.Test;
26+
import reactor.core.publisher.Flux;
27+
import reactor.core.publisher.Mono;
2528

2629
import org.springframework.boot.actuate.endpoint.InvocationContext;
2730
import org.springframework.boot.actuate.endpoint.SecurityContext;
31+
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
2832
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
2933

3034
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,6 +43,8 @@
3943
* Tests for {@link CachingOperationInvoker}.
4044
*
4145
* @author Stephane Nicoll
46+
* @author Christoph Dreis
47+
* @author Phillip Webb
4248
*/
4349
class CachingOperationInvokerTests {
4450

@@ -62,6 +68,30 @@ void cacheInTtlWithNullParameters() {
6268
assertCacheIsUsed(parameters);
6369
}
6470

71+
@Test
72+
void cacheInTtlWithMonoResponse() {
73+
MonoOperationInvoker.invocations = 0;
74+
MonoOperationInvoker target = new MonoOperationInvoker();
75+
InvocationContext context = new InvocationContext(mock(SecurityContext.class), Collections.emptyMap());
76+
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
77+
Object response = ((Mono<?>) invoker.invoke(context)).block();
78+
Object cachedResponse = ((Mono<?>) invoker.invoke(context)).block();
79+
assertThat(MonoOperationInvoker.invocations).isEqualTo(1);
80+
assertThat(response).isSameAs(cachedResponse);
81+
}
82+
83+
@Test
84+
void cacheInTtlWithFluxResponse() {
85+
FluxOperationInvoker.invocations = 0;
86+
FluxOperationInvoker target = new FluxOperationInvoker();
87+
InvocationContext context = new InvocationContext(mock(SecurityContext.class), Collections.emptyMap());
88+
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
89+
Object response = ((Flux<?>) invoker.invoke(context)).blockLast();
90+
Object cachedResponse = ((Flux<?>) invoker.invoke(context)).blockLast();
91+
assertThat(FluxOperationInvoker.invocations).isEqualTo(1);
92+
assertThat(response).isSameAs(cachedResponse);
93+
}
94+
6595
private void assertCacheIsUsed(Map<String, Object> parameters) {
6696
OperationInvoker target = mock(OperationInvoker.class);
6797
Object expected = new Object();
@@ -122,4 +152,32 @@ void targetInvokedWhenCacheExpires() throws InterruptedException {
122152
verify(target, times(2)).invoke(context);
123153
}
124154

155+
private static class MonoOperationInvoker implements OperationInvoker {
156+
157+
static int invocations;
158+
159+
@Override
160+
public Object invoke(InvocationContext context) throws MissingParametersException {
161+
return Mono.fromCallable(() -> {
162+
invocations++;
163+
return Mono.just("test");
164+
});
165+
}
166+
167+
}
168+
169+
private static class FluxOperationInvoker implements OperationInvoker {
170+
171+
static int invocations;
172+
173+
@Override
174+
public Object invoke(InvocationContext context) throws MissingParametersException {
175+
return Flux.fromIterable(() -> {
176+
invocations++;
177+
return Arrays.asList("spring", "boot").iterator();
178+
});
179+
}
180+
181+
}
182+
125183
}

0 commit comments

Comments
 (0)