我有以下场景:我有一个名为Foo
的对象,我的应用程序感兴趣。这个Foo
对象来自远程服务,我想在本地缓存此实例并保持使用它直到一定时间过去。
到目前为止,我已尝试创建一个FooState
类,其中包含Foo
的实例以及一个时间戳,指示以毫秒为单位提取Foo
的时间:
public class FooState {
private Foo foo;
private long timestamp;
/* Constructor and getters */
}
现在,到目前为止,我已经提出了使用concat的代码:
public Observable<Foo> foo() {
return Observable.concat(local(), remote())
.takeFirst(fooState -> fooState.getTimestamp() >= System.currentTimeMillis())
.map(fooState -> fooState.getFoo())
.defaultIfEmpty(new Foo());
}
private Observable<FooState> local() {
return Observable.just(cache.hasFooState() ? cache.getFooState() : new FooState(null, 0));
}
private Observable<FooState> remote() {
return api.getFoo()
.map(foo -> new FooState(foo, System.currentTimeMillis() + ONE_DAY_MILLIS)
.doOnNext(fooState -> {
cache.save(fooState);
});
}
基本上,如果有缓存值,我想只要时间戳未过期就使用它。如果时间戳已过期或没有缓存值,我想从远程服务获取并缓存结果。
是否有更简洁的方法来实现此用例?我是RxJava的新手,我想知道是否有任何Rx-gurus知道更好的方法来处理这种情况。
答案 0 :(得分:0)
您还可以使用内置的 .timestamp()运算符和 Timestamped&lt; T&gt; 类。
BehaviorSubject<Timestamped<Foo>> subject = BehaviorSubject.create();
Observable<Foo> serviceCall() {
return subject.filter(new Func1<Timestamped<Foo>, Boolean>() {
@Override
public Boolean call(Timestamped<Foo> tsFoo) {
return tsFoo.getTimestampMillis() < expiry;
}
}).switchIfEmpty(serviceCall().timestamp().doOnNext(new Action1<Timestamped<Foo>>() {
@Override
public void call(Timestamped<Foo> tsFoo) {
subject.onNext(tsFoo);
}
})
.map(new Func1<Timestamped<Object>, Foo>() {
@Override
public Foo call(Timestamped<Object> tsFoo) {
return tsFoo.getValue();
}
});
}
答案 1 :(得分:0)
我想出了类似于concat
的方法,因为它似乎最适合这个用例,但是基于Maybe
(RxJava 2)构建我的解决方案,它不会发出如果没有缓存或缓存条目已过期,则从本地获取任何内容。
public class RxJavaUnitTestJava {
public class Foo extends Object {
String source = "n/a";
}
public class FooState {
private Foo foo;
private long timestamp;
public FooState(Foo foo, long timestamp) {
this.foo = foo;
this.timestamp = timestamp;
}
}
private static long EXPIRATION = 0l;
private FooState cachedFooState = null;
private Maybe<Foo> local() {
return Maybe.fromCallable(() -> {
System.out.println("checking local");
if (cachedFooState != null && cachedFooState.timestamp + EXPIRATION > System.currentTimeMillis()) {
System.out.println("taking unexpired local");
cachedFooState.foo.source = "local"; // mark source of this foo
return cachedFooState.foo;
} else {
System.out.println("not taking local");
return null;
}
});
}
private Maybe<Foo> remote() {
return Maybe.fromCallable(() -> {
System.out.println("checking remote");
Foo foo = new Foo();
foo.source = "remote"; // mark source of this foo
FooState fooState = new FooState(foo, System.currentTimeMillis());
cachedFooState = fooState;
return fooState.foo;
});
}
private Observable<Foo> foo() {
return Maybe.concat(local(), remote())
.take(1)
.toObservable();
}
@Test
public void testNoCachedLocal() {
cachedFooState = null;
foo()
.doOnNext(foo -> System.out.println("doOnNext: foo.source: " + foo.source))
.doOnComplete(() -> System.out.println("onComplete"))
.test()
.assertValueCount(1)
.assertComplete();
}
@Test
public void testExpiredLocal() {
cachedFooState = new FooState(new Foo(), System.currentTimeMillis());
EXPIRATION = 0l;
foo()
.doOnNext(foo -> System.out.println("doOnNext: foo.source: " + foo.source))
.doOnComplete(() -> System.out.println("onComplete"))
.test()
.assertValueCount(1)
.assertComplete();
}
@Test
public void testUnExpiredLocal() {
cachedFooState = new FooState(new Foo(), System.currentTimeMillis());
EXPIRATION = TimeUnit.SECONDS.toMillis(30);
foo()
.doOnNext(foo -> System.out.println("doOnNext: foo.source: " + foo.source))
.doOnComplete(() -> System.out.println("onComplete"))
.test()
.assertValueCount(1)
.assertComplete();
}
}