Java 响应式编程模式

Java 响应式编程模式

响应式编程

在 Spring Boot 中,响应式编程模式已经被引入,它可以让我们在 Spring Boot 的应用中使用响应式编程模式。
响应式编程带来的性能上和内存使用上的优化。

详见:

困难

但是不同于 async/await 模式,响应式编程也给编码带来了一些困难,主要如下:

  • null 处理不友好,甚至是灾难性的。
  • 对异常处理不友好。
    这个问题在一定程度上可以使用 RuntimeException 来规避。
  • 一个代码块只能最多调用一个响应式 API。

响应式编程的规则

  • 控制层,返回响应式对象,大多数情况下使用 Mono<T>
  • 服务层,使用@Transactional 的 API 必须返回响应式对象。
  • 数据访问层(R2DBC)返回响应式对象: Mono<T>, Flux<T>
  • 使用响应式方法的 API 尽量返回响应式对象。
  • 不要使用任何 block(), blockFirst(), share().block() 等 API,会引起严重的性能问题。
  • 避免使用 subscribe() API,可能会因此性能问题。
    在我的经验中,只有在重载传统接口的情况下才使用 subscribe() API。
  • 建议: 避免定义返回 Mono<Void>的方法。
    这种返回不能调用随后的映射方法 map()flatMap()transform()等方法。
    一个例外是,这个方法在控制层的最后被调用。
  • Mono.empty() 不能调用随后的映射方法 map()flatMap()transform()等。
  • Flux.empty() 可以调用随后的映射方法 colllectList()等方法。
  • 响应式 API 不能返回 null 或者 Mono.just(null) 或者其等价方式。
    会引起下面的错误:

    Caused by: java.lang.NullPointerException: The mapper returned a null value.

响应式编程模式

响应式编程是一种流编程,我把编程模式分为: 启动模式、映射模式、返回模式、异常模式。

异常模式

响应式编程对于异常处理,建议使用下面的方法:

  • 抛出 RuntimeException
  • 对于异常,返回 Mono.error(t) 方法。
  • doOnXXX() 方法。

启动模式

  • 常用的启动模式
Mono.just(data);
Mono.fromXXX(xxx);

  • 冷响应式
Mono.defer(() -> supplier);

冷响应式是指,在启动时,不会立即执行,而是在被订阅时才执行。
下面 IllegalArgumentException 会在 subscribe 后才会被调用。

// Sample code
	private Mono<Integer> monoAdd(Integer a, Integer b) {

		return Mono.defer(() -> {
			if (a == null) {
				throw new IllegalArgumentException("a is null");
			}

			if (b == null) {
				throw new IllegalArgumentException("b is null");
			}

			return Mono.just(a + b);
		});
	}

映射模式

这里讨论的映射模式,大都是关于多个响应式 API 之间的协作。

平行模式(flat pattern)

主要是用 flatMap() 方法。代码成 flatMap().flatMap().flatMap() 形状。
用于后面的 API 只使用前面 API 输出结果的情况。

	public static Mono<Integer> monoFlat(Integer a) {

		return Mono.defer(() -> {
			if (a == null) {
				throw new IllegalArgumentException("a is null");
			}
			return Mono.just(a);
		}).flatMap(data -> Mono.just(data * 2))
		.flatMap(data -> Mono.just(data + 100));
	}

	monoFlat(1).log().subscribe();
/*
00:15:17.005 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
00:15:17.164 [main] INFO reactor.Mono.FlatMap.1 - | onSubscribe([Fuseable] MonoFlatMap.FlatMapMain)
00:15:17.168 [main] INFO reactor.Mono.FlatMap.1 - | request(unbounded)
00:15:17.171 [main] INFO reactor.Mono.FlatMap.1 - | onNext(102)
00:15:17.173 [main] INFO reactor.Mono.FlatMap.1 - | onComplete()
*/

嵌套模式(nested pattern)

对于后面的 API 需要使用多个前面 API 输出结果的情况,可以使用嵌套模式。
在嵌套模式中,后面的 API 可以直接使用前面 API 的结果。

	public static Mono<Integer> monoNested(Integer a, Integer b) {
		// return a * 100 + b * 100
		return Mono.defer(() -> {
			if (a == null) {
				throw new IllegalArgumentException("a is null");
			}
			return Mono.just(a * 100).flatMap(o1 -> {
				if (b == null) {
					throw new IllegalArgumentException("a is null");
				}

				return Mono.just(b * 100).map(
						// 在这里可以同时使用 o1 和 o2
						o2 -> o1 + o2);
			});
		});
	}

monoNested(1, 2).log().subscribe();

/*
00:22:43.816 [main] INFO reactor.Mono.Defer.2 - onSubscribe([Fuseable] FluxMapFuseable.MapFuseableSubscriber)
00:22:43.817 [main] INFO reactor.Mono.Defer.2 - request(unbounded)
00:22:43.817 [main] INFO reactor.Mono.Defer.2 - onNext(300)
00:22:43.818 [main] INFO reactor.Mono.Defer.2 - onComplete()
*/

拉链模式(zip pattern)

对于后面的 API 需要使用多个前面 API 输出结果的情况,可以使用拉链模式。
在拉链模式中,后面的 API 可以通过参数获取前面 API 的结果。

	public static Mono<Integer> monoZip(Integer a, Integer b) {
		// return a * 100 + b * 100
		return Mono.zip(
				Mono.defer(() -> {
					if (a == null) {
						throw new IllegalArgumentException("a is null");
					}
					return Mono.just(a * 100);
				}),
				Mono.defer(() -> {
					if (b == null) {
						throw new IllegalArgumentException("b is null");
					}
					return Mono.just(b * 100);
				}), (o1, o2) -> o1 + o2);
	}

	monoZip(1, 2).log().subscribe();

/*
00:32:22.326 [main] INFO reactor.Mono.Zip.3 - onSubscribe([Fuseable] MonoZip.ZipCoordinator)
00:32:22.326 [main] INFO reactor.Mono.Zip.3 - request(unbounded)
00:32:22.327 [main] INFO reactor.Mono.Zip.3 - onNext(300)
00:32:22.328 [main] INFO reactor.Mono.Zip.3 - onComplete()
*/

原子模式(atomic pattern)

拉链模式和嵌套模式都不能处理 null 值,原子模式可以。
注意下面示例中的 return Mono.just(0) 可以确保不会忽略 null 值的情况。

	public static Mono<Integer> monoAtomic(Integer a, Integer b) {
		AtomicReference<Integer> a100Ref = new AtomicReference<>(0);
		AtomicReference<Integer> b100Ref = new AtomicReference<>(0);

		// return a * 100 + b * 100
		return Mono.defer(() -> {
			if (a == null) {
				a100Ref.set(null);
			} else {
				a100Ref.set(a * 100);
			}

			return Mono.just(0);
		}).flatMap(o -> {
			if (b == null) {
				b100Ref.set(null);
			} else {
				b100Ref.set(b * 100);
			}
			return Mono.just(0);
		}).map(o -> {
			if (a100Ref.get() == null || b100Ref.get() == null) {
				return 0;
			}
			return a100Ref.get() + b100Ref.get();
		});
	}

monoAtomic(1, 2).log().subscribe();
/*
11:03:46.162 [main] INFO reactor.Mono.Map.4 - onSubscribe(FluxMap.MapSubscriber)
11:03:46.163 [main] INFO reactor.Mono.Map.4 - request(unbounded)
11:03:46.163 [main] INFO reactor.Mono.Map.4 - onNext(0)
11:03:46.164 [main] INFO reactor.Mono.Map.4 - onComplete()
*/

null 对象模式(null pattern)

我们还可以使用默认值来处理 null 值的情况。
在处理 null 值时,一个常见的需求是:

在一个 lambda 闭包中:

  • 可以知道这个值是 null 还是非 null。
  • 可以获取这个值。
  • 可以调用并返回一个新的响应式对象(发布者)

一个技巧是使用 .defaultIfEmpty() 方法来处理 null 值。
这个技巧对于数值或者 String 类型的值可能有效的,但是对于类实例就不好用了。
在这种情况下,可以考虑定义一个接口。

public interface Nullable {
	boolean isNone();
}

package demo.reactive;

public class Employee implements Nullable {

    private static final Employee none = new Employee(true);

    public static Employee none() {
        return none;
    }

    private Employee(boolean isNone) {
        this.isNone = isNone;
    }

    private Employee() {
    }

    private int id;
    private String name;
    private boolean isNone;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean isNone() {
        return isNone;
    }

    public void setNull(boolean isNone) {
        this.isNone = isNone;
    }

}

	public static Mono<Employee> monoNullable() {

		// return nullable object
		return Mono.defer(() -> {
			return Mono.<Employee>empty();
		}).defaultIfEmpty(Employee.none());

	}

monoNullable().map(o -> {
			logger.info(MessageFormat.format("map.isNone: {0}", o.isNone()));
			return o;
		}).doOnSuccess(o -> {
			logger.info(MessageFormat.format("doOnSuccess: {0}", o));
		}).log().subscribe();

/*
18:28:06.789 [main] INFO reactor.Mono.PeekTerminal.1 - | onSubscribe([Fuseable] MonoPeekTerminal.MonoTerminalPeekSubscriber)
18:28:06.794 [main] INFO reactor.Mono.PeekTerminal.1 - | request(unbounded)
18:28:06.796 [main] INFO demo.ReactiveDemo - map.isNone: true
18:28:06.796 [main] INFO demo.ReactiveDemo - doOnSuccess: demo.reactive.Employee@120d6fe6
18:28:06.797 [main] INFO reactor.Mono.PeekTerminal.1 - | onNext(demo.reactive.Employee@120d6fe6)
18:28:06.799 [main] INFO reactor.Mono.PeekTerminal.1 - | onComplete()
*/

返回模式

下面是常见的返回模式。

Mono.empty();
Mono.then();
Mono.then(mono);
Mono.thenReturn(data);

参照

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章