通过RxJava缓存API响应

通过RxJava缓存API响应,java,rx-java,rx-java2,Java,Rx Java,Rx Java2,因此,我尝试将http响应缓存到ConcurrentHashMap中。我已经设置了缓存类和Api客户机类,以返回可观测值,如下所示: public class UserCache { private static ConcurrentHashMap<Integer, User> cache = new ConcurrentHashMap<>(); public Observable<User> get(Integer key) {

因此,我尝试将http响应缓存到ConcurrentHashMap中。我已经设置了缓存类和Api客户机类,以返回可观测值,如下所示:

public class UserCache {

    private static ConcurrentHashMap<Integer, User> cache = new ConcurrentHashMap<>();

    public Observable<User> get(Integer key) {
        return Observable.create(observableEmitter -> {
            if(cache.contains(key)) observableEmitter.onNext(cache.get(key));
            observableEmitter.onComplete();
        });
    }

    public void update(Integer key, User user) {
        cache.putIfAbsent(key, user);
    }

    public boolean contains(Integer key) {
        return cache.contains(key);
    }
}
公共类用户缓存{
私有静态ConcurrentHashMap缓存=新ConcurrentHashMap();
公共可观测get(整数密钥){
返回可观察的。创建(可观察的提交者->{
if(cache.contains(key))observeItemer.onNext(cache.get(key));
ObservieMitter.onComplete();
});
}
公共无效更新(整数键,用户){
cache.putIfAbsent(键,用户);
}
公共布尔包含(整数键){
返回cache.contains(key);
}
}
ApiClient

public class ApiClient {

    private UserApi api;
    private static ApiClient apiClient;

    private ApiClient() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://jsonplaceholder.typicode.com")
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        api = retrofit.create(UserApi.class);
    }

    public Observable<User> get(int id) {
        return api.getUser(id);
    }

    public static ApiClient getInstance() {
        if(apiClient == null) apiClient = new ApiClient();
        return apiClient;
    }
}
公共类ApiClient{
私有用户接口;
私有静态ApiClient ApiClient;
私人ApiClient(){
HttpLoggingInterceptor拦截器=新的HttpLoggingInterceptor();
setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient客户端=新建OkHttpClient.Builder().addInterceptor(拦截器).build();
改装改装=新改装.Builder()
.baseUrl(“https://jsonplaceholder.typicode.com")
.客户(客户)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
api=改造.create(UserApi.class);
}
公共可观测get(int id){
返回api.getUser(id);
}
公共静态ApiClient getInstance(){
如果(apiClient==null)apiClient=new apiClient();
归还客户;
}
}
在应用程序类中

public class App {

    ApiClient apiSource = ApiClient.getInstance();

    UserCache userCache = new UserCache();

    public Observable<User> get(Integer key) {
        return Observable.concat(userCache.get(key), apiSource.get(key))
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.computation())
                .doOnNext(user -> {
                    userCache.update(user.id, user);
                })
                .subscribeOn(Schedulers.io());
    }

    public static void main(String[] args) throws InterruptedException {
        App app = new App();
        app.get(1).subscribe(System.out::println);
        Thread.sleep(3000);
        app.get(1).subscribe(System.out::println);
        Thread.sleep(1000);
    }
}
公共类应用程序{
ApiClient apiSource=ApiClient.getInstance();
UserCache UserCache=new UserCache();
公共可观测get(整数密钥){
返回Observable.concat(userCache.get(key),apiSource.get(key))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.doOnNext(用户->{
userCache.update(user.id,user);
})
.subscribeOn(Schedulers.io());
}
公共静态void main(字符串[]args)引发InterruptedException{
App App=新App();
app.get(1).subscribe(System.out::println);
睡眠(3000);
app.get(1).subscribe(System.out::println);
睡眠(1000);
}
}

我对
.concat
的理解是,如果第一个可观察对象(缓存)不发射任何东西,那么第二个可观察对象(api客户端)将开始发射。但是我不明白为什么
doOnNext(user->userCache.update(user.id,user))
没有更新缓存,因此当我检索同一个密钥时,会再次执行另一个api调用。

我不确定为什么
doOnNext
没有发出,但是,如果您使用RX,有一种方法需要更少的代码并消除竞争条件。在您给出的示例中,如果在空缓存上调用该方法两次,它将进行两次网络调用,最后一次将覆盖第一次。下面是我首选的方法,它可以防止这种情况发生,并且需要更少的代码:

private final ConcurrentMap<Integer, Observable<User>> userCache = Maps.newConcurrentMap();

public Observable<User> getUser(int id) {
    return userCache.computeIfAbsent(id, theId -> {
        ConnectableObservable<User> cachedObservable = getUserFromApi(id)
                .replay();
        cachedObservable.connect();
        return cachedObservable;
    }).doOnError(err -> userCache.remove(id));
}
private final ConcurrentMap userCache=Maps.newConcurrentMap();
公共可观察getUser(int-id){
返回userCache.computeIfAbsent(id,theId->{
ConnectableObservable CachedObjectable=getUserFromApi(id)
.replay();
cachedObservable.connect();
返回cachedObservable;
}).doon错误(错误->用户缓存.remove(id));
}
如您所见,我存储缓存的可观察对象。这样,如果在第一个调用仍在运行时进行第二个调用,它们将得到相同的结果,并且只缓存一次。之后的所有调用都直接从缓存的可观察对象获取它


但是,我们不想缓存错误(可能),因此我附加了一个
doError
,以确保包含错误(如网络故障)的任何观察值也不会被缓存。

还有一件事:API
getInstance()
不是线程安全的。如果我同时调用多个线程,您可能会得到多个API对象。如果您正在使用多个线程(看起来是这样的),您应该向方法中添加
synchronized
。有关更多信息,请参阅。此外,不建议创建自己的可观察对象。而不是
返回Observable.create(observableEmitter->{if(cache.contains(key))observableEmitter.onNext(cache.get(key));observableEmitter.onComplete();})
您也可以这样做:
Observable.just(userCache.get(id)).filter(Objects::nonNull)
出于好奇,为什么
getUserFromApi(id.cache()
不会给出相同的结果?因为对该对象的引用丢失了。您必须在Java中保留对原始可观察对象的引用,以便下次可以传递该引用。这就是为什么我们要把它们放在缓存里。