近来响应式编程成为一种流行的模式,涌现出很多支持各种编程语言的库和框架和相关的博文文章。像Facebook,SoundCloud,Microsoft,Netflix等大公司也开始支持和使用这种模式。所以我们这些程序员需要弄清楚关于响应式编程的一些问题。为什么人们会对于响应式编程如此狂热?什么事响应式编程?使用它会对于我们的项目有哪些帮助?我们应该去学习和使用它吗?

同时,Java作为一门支持多线程、高效可靠的流行编程语言,被广泛的应用于建立复杂的应用程序(搜索引擎,通过数据库为复杂的网络应用提供支持的服务端应用等)。但是Java也有一些令人诟病的地方——仅使用内置工具很难写一些简单的并发程序,使用Java编程会经常写大量的样式化代码。并且如果你使用异步方式(例如使用Future),会很容易进入“CallBack hell”,当然几乎所有的编程语言都有这个通病。

CallBack hell:Java进行异步编程的时候往往会使用CallBack的方式来通知结果,在复杂的业务中会因为CallBack的层层嵌套导致整个代码难以理解。

换句话说,Java足够强大能够让你完成一些好的应用,但是也会遇到困扰。好消息是已经有一个解决问题的办法——使用相应式编程。

这里我们将会介绍RxJava这个项目,一个开源的基于Java语言的相应式编程实现。使用RxJava写代码跟通常的思考方式有一些不同,但是会让你能够用简单且结构鲜明的代码来处理复杂的业务逻辑。

这里我们会介绍:
- 什么事响应式编程
- 学习和使用响应式编程的原因
- 开始使用RxJava和熟悉这个模式框架
- 一个RxJava的小例子

什么事响应式编程?

响应式编程是一种解决关于事件传播的范式。换句话说,如果一个程序把所有数据的变化事件广播给对它感兴趣的部分(用户、其他程序、其他组件,以及下属部分),那么这个程序就可以称为一个响应式程序。

一个更简单的例子使用是Microsoft Excel。如果你在A1单元格输入一个数字,也在在B1输入一个数字,并且设置C1的内容为SUM(A1, B1),一旦A1或者是B1出现变化,那么C1将会立即更新他们的和。

我们把这种叫做响应式求和。

响应式跟传统的方式不同点有哪些呢?我们可以看看下面的例子:

int a = 4;
int b = 5;
int c = a + b;
System.out.println("" + c);//9
a = 6;
System.out.println("" + c);//还是9
// c虽然是a和b的和,但是我们改变a的值后,c的值并没有随之改变。

这个例子简单介绍了响应式编程的意义。当然我们有许多办法能够解决这个问题,但是也有一些问题需要解决。

为什么我们需要响应式编程?

回答这个问题的最简单方式是思考目前我们开发项目所面临的需求。

10-15年之前,网站出现故障或者是相应很慢是一种正常的现象,但是现在必须保证能够24*7在线可用,并且必须快速响应。一旦变慢或者不可用,使用者会倾向于转向其他的服务。在今天响应慢就是不可用或者出问题了。现在我们处理的是大量数据,并且需要提供快速的服务和处理。

过去网络请求的失败并不是什么大事,但是现在我们必须处理容错和给用户提供合理的原因信息。

过去,我们一般是开发简单的桌面程序,现在我们需要处理的是要求快速响应的网络应用。并且大多数情况,这些应用需要跟大量的远程服务进行通信。

如果我们想要我们的产品具有竞争力,就必须满足以上的所有需求。换句话说,我们必须做好以下内容:
- 模块化/动态化:这可以帮助我们的产品24*7可用。因为模块是可以在不当机的情况下上线和下线的。并且这种方式,也利于我们项目的架构能够解决随着规模增加更好代码管理的问题。
- 可扩展:这种方式使我们能够处理大数据和大量的请求。
- 容错机制:这可以是系统更稳定。
- 快速响应:意味着系统运行快速和高度可用。

让我们思考如何才能解决上面的需求呢:
- 如果我们的系统是事件驱动的那么就可以实现模块化。我们可以把整个系统划分为小的模块组件,并通过通知来进行组件间通信。这样我们可以响应处理通知表示的系统数据流。
- 可扩展意味着能够正常载入来响应持续增长的数据。
- 对失败和错误进行响应能够增加系统的稳定性。
- 快速响应就是能够迅速对于用户的活动进行反馈。

介绍RxJava

要进行响应式编程,我们需要一个库或者是一种特殊的编程语言,毕竟自己实现对于大多数人是一件非常困难的任务。并且Java并不是一门真正的相应式编程语言(虽然提供了java.util.Observable,但是功能太局限)。Java是一门静态,面向对象的语言,我们要完成一些功能需要写一些模式代码(简单的Java Bean等)。但是目前有一个支持Java的响应式编程库——RxJava。RxJava是一个由Netflix最初建立的一个开源项目,目前已经有很多人为这个项目贡献代码进行完善。

下载和配置RxJava

你可以从Github来下载源码,自己进行打包(https://github.com/ReactiveX/RxJava)。RxJava不依赖其他的库,支持Java8的lambda。有非常好的Javadoc和Github wiki,你可以从中查找资料和学习。你可以通过下面的方式下载和建立:

$ git clone git@github.com:ReactiveX/RxJava.git
$ cd RxJava/
$ ./gradlew build

当然我们更常用的是只下载已经生成好的jar文件,这里我们以1.0.14版本为例。
如果你使用Maven,你可以通过在pom.xml中添加RxJava作为一个依赖:

<dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxjava</artifactId>
    <version>1.0.14</version>
</dependency>

使用Gradle则需要配置你的build.gradle,添加下面的内容:

dependencies {
    ...
    compile 'io.reactivex:rxjava:1.0.14'
    ...
}

一个使用RxJava实现两个数相加的简单例子

public static void main(String[] args) {

  ConnectableObservable<String> cob = getConnect(System.in);
  Observable<Double> a = varInput("a", cob);
  Observable<Double> b = varInput("b", cob);

  reactSum(a, b);
  cob.connect();
}

static ConnectableObservable<String> getConnect(InputStream inputStream) {
  final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  return Observable.create(new Observable.OnSubscribe<String>() {
                             @Override public void call(Subscriber<? super String> subscriber) {
                               String line;
                               if (subscriber.isUnsubscribed()) return;
                               try {
                                 while (!subscriber.isUnsubscribed()
                                     && (line = reader.readLine()) != null) {
                                   if (line.equals("exit")) {
                                     break;
                                   }

                                   subscriber.onNext(line);
                                 }

                                 if (!subscriber.isUnsubscribed()) {
                                   subscriber.onCompleted();
                                 }
                               } catch (IOException e) {
                                 e.printStackTrace();
                                 subscriber.onError(e);
                               }
                             }
                           }

  ).publish();
}

static Observable<Double> varInput(String tag, Observable<String> observable) {
  final Pattern pattern = Pattern.compile("^\\s*" + tag + "\\s*[:|=]\\s*(-?\\d+\\.?\\d*)$");
  return observable.map(new Func1<String, Matcher>() {
    @Override public Matcher call(String s) {
      return pattern.matcher(s);
    }
  })
      .filter(new Func1<Matcher, Boolean>() {
        @Override public Boolean call(Matcher matcher) {
          return matcher.find();
        }
      })
      .map(new Func1<Matcher, String>() {
        @Override public String call(Matcher matcher) {
          return matcher.group(1);
        }
      })
      .map(new Func1<String, Double>() {
        @Override public Double call(String s) {
          return Double.parseDouble(s);
        }
      });
}

static void reactSum(Observable<Double> a, Observable<Double> b) {
  Observable.combineLatest(a, b, new Func2<Double, Double, Double>() {
    @Override public Double call(Double aDouble, Double aDouble2) {
      return aDouble + aDouble2;
    }
  })
      .subscribe(new Subscriber<Double>() {
        @Override public void onCompleted() {
          System.out.println("onComplete");
        }

        @Override public void onError(Throwable e) {
          System.out.println("onError:" + e.getMessage());
        }

        @Override public void onNext(Double aDouble) {
          System.out.println("a + b is:" + aDouble);
        }
      });
}

运行结果可能如下:

a:1
b:3
a + b is:4.0
a:5
a + b is:8.0
exit
onComplete

如果搭配Java8的lambda,程序会更加的简洁。这里我们暂时先不使用lambda。