WebFlux和Spring Data Reactive的完整demo


本实例演示了:在处理一个REST请求的过程中,web服务器依次发起读redis、查mongodb、写redis这些网络IO请求,这些IO请求的执行过程全部遵从NIO模式,即发出IO请求的线程不进行阻塞而是直接释放,等到数据源返回结果后,新分配一个reactive线程来处理结果数据。
依赖的spring版本为:spring-webflux(5.1.2)、spring-data-redis(2.1.2)、spring-data-mongodb(2.1.2);使用的是非springboot项目也支持的配置方式。

配置ReactiveRedisTemplate

1、先定义连接池配置

package wzp.redis.factory;

import java.time.Duration;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;

public class LettuceClientConfigurationFactory implements FactoryBean<LettuceClientConfiguration> {

	@Override
	public LettuceClientConfiguration getObject() throws Exception {
		GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
		poolConfig.setMaxTotal(200);
		poolConfig.setMaxWaitMillis(2000);
		poolConfig.setTestOnBorrow(true);
		LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
				.poolConfig(poolConfig).commandTimeout(Duration.ofMillis(3000)).shutdownTimeout(Duration.ZERO).build();

		return lettuceClientConfiguration;
	}

	@Override
	public Class<?> getObjectType() {
		return LettuceClientConfiguration.class;
	}

}

2、再配置ReactiveRedisTemplate

	<bean id="redisStandaloneConfiguration"
		class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
		<property name="hostName" value="${redis.host}" />
		<property name="port" value="${redis.port}" />
		<property name="password"
			value='#{T(org.springframework.data.redis.connection.RedisPassword).of("${redis.password}")}' />
	</bean>
	<bean id="lettuceClientConfiguration"
		class="wzp.redis.factory.LettuceClientConfigurationFactory" />
	<bean id="lettuceConnectionFactory"
		class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
		<constructor-arg name="standaloneConfig"
			ref="redisStandaloneConfiguration" />
		<constructor-arg name="clientConfig"
			ref="lettuceClientConfiguration" />
	</bean>
	<bean id="redisReactiveTemplate"
		class="org.springframework.data.redis.core.ReactiveStringRedisTemplate">
		<constructor-arg name="connectionFactory"
			ref="lettuceConnectionFactory" />
	</bean>

配置ReactiveMongoTemplate、ReactiveMongoRepository

1、定义Repository接口,由spring-data-mongodb负责扫描定义并注入CURD逻辑

package wzp.mongo.repository.reactive;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;

import wzp.mybatis.bo.User;

public interface UserReactiveRepository extends ReactiveMongoRepository<User, String> {
}

2、定义响应式MongoClient的工厂

package wzp.mongo.factory;

import java.util.concurrent.TimeUnit;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ReadConcern;
import com.mongodb.WriteConcern;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
//初始化bean的时候,顺便使注解生效,扫描所有Reactive Repository
@EnableReactiveMongoRepositories(basePackages = "wzp.mongo.repository.reactive", reactiveMongoTemplateRef = "mongoReactiveTemplate")
public class MongoReactiveClientFactory implements FactoryBean<MongoClient>, DisposableBean {
	// 标准连接地址mongodb://test:[email protected]:27017/?authSource=test
	@Value("${mongo.uri}")
	private String mongoUri;

	private MongoClient client;

	@Override
	public MongoClient getObject() throws Exception {
		if (this.client != null) {
			return this.client;
		}
		// 对POJO进行BASE格式的序列化和反序列化
		CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
				com.mongodb.MongoClient.getDefaultCodecRegistry(),
				CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build()));
		// 连接池最小连接数为1,最大连接数为100,3秒内未获取空闲连接则直接失败;
		// 建立连接时,3秒超时;发起查询时,10秒超时
		MongoClientSettings settings = MongoClientSettings.builder()
				.applyConnectionString(new ConnectionString(mongoUri))
				.applyToConnectionPoolSettings(
						builder -> builder.minSize(1).maxSize(100).maxWaitTime(3000, TimeUnit.MILLISECONDS).build())
				.applyToSocketSettings(builder -> builder.connectTimeout(3000, TimeUnit.MILLISECONDS)
						.readTimeout(10000, TimeUnit.MILLISECONDS).build())
				.codecRegistry(pojoCodecRegistry).readConcern(ReadConcern.MAJORITY).writeConcern(WriteConcern.MAJORITY)
				.build();
		this.client = MongoClients.create(settings);
		return this.client;
	}

	@Override
	public Class<?> getObjectType() {
		return MongoClient.class;
	}

	@Override
	public void destroy() throws Exception {
		if (this.client != null) {
			this.client.close();
		}
	}
}

3、配置ReactiveMongoClient,ReactiveMongoTemplate

	<bean id="mongoReactiveClient"
		class="wzp.mongo.factory.MongoReactiveClientFactory" />
	<bean id="mongoReactiveTemplate"
		class="org.springframework.data.mongodb.core.ReactiveMongoTemplate">
		<constructor-arg name="mongoClient"
			ref="mongoReactiveClient" />
		<constructor-arg name="databaseName"
			value="${mongo.database}" />
	</bean>

WebFlux处理流实例

主要展示如何使用Mono、Flux流的switchIfEmpty、flatMap方法来串接各个NIO请求

	@Autowired
	private ReactiveStringRedisTemplate redisReactiveTemplate;
	@Autowired
	private UserReactiveRepository userReactiveRepository;
	
	@GetMapping(value = "/flux.do")
	@ResponseBody
	public Mono<User> flux(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// return webClientTest();
		int id = 43;
		String redisKey = "wzpTestUser" + id;
		User query = new User();
		query.setUser_id(id);
		// 用于判断是否从redis获取到了缓存的对象
		List<User> cached = Lists.newArrayList();

		LOGGER.info("redis get start");

		Mono<User> monoRedisGet = redisReactiveTemplate.opsForValue().get(redisKey).map(strUser -> {
			// 此段逻辑的处理线程,应与redis get start的线程不同
			LOGGER.info("redis get map");
			User user = JSON.parseObject(strUser, User.class);
			cached.add(user);
			return user;
		}).log();

		// 当redis里不存在缓存时,才到mongo查询
		Mono<User> monoMongoQuery = monoRedisGet
				.switchIfEmpty(userReactiveRepository.findOne(org.springframework.data.domain.Example.of(query))).log();

		Mono<User> monoRedisSet = monoMongoQuery.flatMap(user -> {
			if (cached.size() == 0) {
				// 当redis里不存在缓存时,才写入缓存
				LOGGER.info("mongo query flatmap");
				return redisReactiveTemplate.opsForValue()
						.set(redisKey, JSON.toJSONString(user), Duration.ofSeconds(10)).map(success -> {
							// 此段逻辑的处理线程,应与mongo query flatmap的线程不同
							LOGGER.info("redis set map");
							return user;
						});
			}
			return Mono.just(user);
		}).log();

		// 对Flux流的真正订阅,交由WebFlux框架负责
		return monoRedisSet;
	}

猜你喜欢

转载自blog.csdn.net/wzp1986/article/details/84317749