英文:
Spring Webflux concurrent access to shared resource
问题
我正在尝试实现一个调用外部API的服务。有两种类型的请求:
client.authorize()
获取一个新的有效期为1小时的令牌,client.performRequest(..)
从API获取一些数据,需要通过 client.authorize()
请求获取的授权令牌。Client类使用了Spring Reactive的WebClient
。
在这种情况下,使用ReentrantLock
来同步检查和刷新令牌的代码块是否可以。或者它会破坏响应式模型吗?也许在响应式编程中有更方便的方法来组织对共享资源的访问?
@Service
@RequiredArgsConstructor
public class Service {
private final Client client;
@Setter(AccessLevel.NONE)
private Lock authLock = new ReentrantLock();
@Setter(AccessLevel.NONE)
private String authToken;
@Setter(AccessLevel.NONE)
private LocalDateTime tokenExpirationDatetime;
public Mono<Response> performRequest(Request request) {
return preAuthorize()
.flatMap(token -> client.performRequest(request, token));
}
private Mono<String> preAuthorize() {
authLock.lock();
try {
if (existsActualToken()) {
return Mono.just(authToken);
}
return refreshToken();
} finally {
authLock.unlock();
}
}
private boolean existsActualToken() {
return authToken != null
&& tokenExpirationDatetime.isAfter(LocalDateTime.now());
}
private Mono<String> refreshToken() {
return client.authorize()
.doOnNext(response -> {
this.authToken = response.token();
this.tokenExpirationDatetime = response.expirationDatetime();
})
.map(Response::token);
}
}
英文:
I'm trying to implement a service that call to external API. There are two types of requests:
client.authorize()
acquires a new token with lifetime 1 hour, client.performRequest(..)
gets some data from API, requires authorization token acquired by client.authorize()
request. Client class utilizes Spring Reactive WebClient
.
Is it ok, to use ReentrantLock
for synchronizing a block of code that checks and refreshes token if it's not fresh? Or it breaks reactive model? Maybe there are more convinient ways to organize access to common resource in reactive programming?
@Service
@RequiredArgsConstructor
public class Service {
private final Client client;
@Setter(AccessLevel.NONE)
private Lock authLock = new ReentrantLock();
@Setter(AccessLevel.NONE)
private String authToken;
@Setter(AccessLevel.NONE)
private LocalDateTime tokenExpirationDatetime;
public Mono<Response> performRequest(Request request) {
return preAuthorize()
.flatMap(token -> client.performRequest(request, token));
}
private Mono<String> preAuthorize() {
authLock.lock();
try {
if (existsActualToken()) {
return Mono.just(authToken);
}
return refreshToken();
} finally {
authLock.unlock();
}
}
private boolean existsActualToken() {
return authToken != null
&& tokenExpirationDatetime.isAfter(LocalDateTime.now());
}
private Mono<String> refreshToken() {
return client.authorize()
.doOnNext(response -> {
this.authToken = response.token();
this.tokenExpirationDatetime = response.expirationDatetime();
})
.map(Response::token);
}
}
答案1
得分: 1
以下是您要翻译的内容:
作为缓存的替代方案,Spring WebFlux 包含了一个 Mono 实例方法,用于提供带有 Mono<T> cache(Duration ttl)
的简单缓存。
要使用此功能,您必须确保每次检索缓存值时都使用相同的 Mono<T> 实例,以实际使用缓存。有几种方法可以实现这一点,但可能最实际的方法是通过配置一个用于获取令牌的 Mono 的 bean。以下是一个应用这种方法的小应用程序:
Test.java:
@SpringBootApplication
public class Test {
public static void main(String[] args) {
SpringApplication.run(Test.class, args);
}
}
@Component
@RequiredArgsConstructor
class Runner {
private final Mono<String> auth;
@EventListener(ApplicationReadyEvent.class)
public void run() {
Flux.<Request>generate(sink -> sink.next(new Request()))
.flatMap(request -> auth.map(request::setAuth))
.delayElements(Duration.ofSeconds(1))
.doOnNext(request -> System.out.println("created request with auth: %s".formatted(request.getAuth())))
.collectList()
.block();
}
}
@Data
class Request {
String auth;
}
@RestController
class Controller {
@GetMapping("/token")
public Mono<String> createAuth() {
return Mono.just("auth")
.doOnNext(x -> System.out.println("created auth"));
}
}
@Configuration
class Config {
@Bean
public WebClient webClient(WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}
@Bean
public Mono<String> auth(WebClient webClient) {
return webClient.get()
.uri("http://localhost:8080/token")
.retrieve().bodyToMono(String.class)
.cache(Duration.ofSeconds(10));
}
}
英文:
As a caching alternative, Spring-webflux contains an instance method in Mono to provide a simple cache with Mono<T> cache(Duration ttl)
.
To use this, you must ensure that you are using the same instance of Mono<T> for each retrivement of the cached value to actually use the cache. There are several ways to achieve this, but perhaps the most practical way is through configuring a bean for the Mono which fetches the token. A small application which applies this:
Test.java:
@SpringBootApplication
public class Test {
public static void main(String[] args) {
SpringApplication.run(Test.class, args);
}
}
@Component
@RequiredArgsConstructor
class Runner {
private final Mono<String> auth;
@EventListener(ApplicationReadyEvent.class)
public void run() {
Flux.<Request>generate(sink -> sink.next(new Request()))
.flatMap(request -> auth.map(request::setAuth))
.delayElements(Duration.ofSeconds(1))
.doOnNext(request -> System.out.println("created request with auth: %s".formatted(request.getAuth())))
.collectList()
.block();
}
}
@Data
class Request {
String auth;
}
@RestController
class Controller {
@GetMapping("/token")
public Mono<String> createAuth() {
return Mono.just("auth")
.doOnNext(x -> System.out.println("created auth"));
}
}
@Configuration
class Config {
@Bean
public WebClient webClient(WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}
@Bean
public Mono<String> auth(WebClient webClient) {
return webClient.get()
.uri("http://localhost:8080/token")
.retrieve().bodyToMono(String.class)
.cache(Duration.ofSeconds(10));
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论