Spring Webflux 并发访问共享资源

huangapple go评论78阅读模式
英文:

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&lt;Response&gt; performRequest(Request request) {
        return preAuthorize()
                .flatMap(token -&gt; client.performRequest(request, token));
    }

    private Mono&lt;String&gt; preAuthorize() {
        authLock.lock();
        try {
            if (existsActualToken()) {
                return Mono.just(authToken);
            }
            return refreshToken();
        } finally {
            authLock.unlock();
        }
    }

    private boolean existsActualToken() {
        return authToken != null
                &amp;&amp; tokenExpirationDatetime.isAfter(LocalDateTime.now());
    }

    private Mono&lt;String&gt; refreshToken() {
        return client.authorize()
                .doOnNext(response -&gt; {
                    this.authToken = response.token();
                    this.tokenExpirationDatetime = response.expirationDatetime();
                })
                .map(Response::token);
    }
}

答案1

得分: 1

以下是您要翻译的内容:

作为缓存的替代方案,Spring WebFlux 包含了一个 Mono 实例方法,用于提供带有 Mono&lt;T&gt; 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&lt;String&gt; auth;

    @EventListener(ApplicationReadyEvent.class)
    public void run() {
        Flux.&lt;Request&gt;generate(sink -&gt; sink.next(new Request()))
                .flatMap(request -&gt; auth.map(request::setAuth))
                .delayElements(Duration.ofSeconds(1))
                .doOnNext(request -&gt; System.out.println(&quot;created request with auth: %s&quot;.formatted(request.getAuth())))
                .collectList()
                .block();
    }
}

@Data
class Request {
    String auth;
}

@RestController
class Controller {
    @GetMapping(&quot;/token&quot;)
    public Mono&lt;String&gt; createAuth() {
        return Mono.just(&quot;auth&quot;)
                .doOnNext(x -&gt; System.out.println(&quot;created auth&quot;));
    }
}

@Configuration
class Config {
    @Bean
    public WebClient webClient(WebClient.Builder webClientBuilder) {
        return webClientBuilder.build();
    }

    @Bean
    public Mono&lt;String&gt; auth(WebClient webClient) {
        return webClient.get()
                .uri(&quot;http://localhost:8080/token&quot;)
                .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&lt;T&gt; 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&lt;String&gt; auth;
@EventListener(ApplicationReadyEvent.class)
public void run() {
Flux.&lt;Request&gt;generate(sink -&gt; sink.next(new Request()))
.flatMap(request -&gt; auth.map(request::setAuth))
.delayElements(Duration.ofSeconds(1))
.doOnNext(request -&gt; System.out.println(&quot;created request with auth: %s&quot;.formatted(request.getAuth())))
.collectList()
.block();
}
}
@Data
class Request {
String auth;
}
@RestController
class Controller {
@GetMapping(&quot;/token&quot;)
public Mono&lt;String&gt; createAuth() {
return Mono.just(&quot;auth&quot;)
.doOnNext(x -&gt; System.out.println(&quot;created auth&quot;));
}
}
@Configuration
class Config {
@Bean
public WebClient webClient(WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}
@Bean
public Mono&lt;String&gt; auth(WebClient webClient) {
return webClient.get()
.uri(&quot;http://localhost:8080/token&quot;)
.retrieve().bodyToMono(String.class)
.cache(Duration.ofSeconds(10));
}
}

huangapple
  • 本文由 发表于 2023年7月18日 14:24:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76710015.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定