深入浅出boot2.0 第14章 webFlux

响应式

spring 对响应式的支持

  • spring 5 新框架 Spring webFlux

  • 电商 金融 对 严谨要求高,对于数据的一致性十分重要

  • 并发:通过锁 保证一些重要数据的一致性

  • 游戏 视频 新闻 广告,不需要很高的数据一致性

  • 但 : 对于并发数 和 相应速度 十分在意

  • 传统的模式会引入 一致性的机制,会造成性能瓶颈,所以提出了:响应式编程。

  • Servlet 3.1 之后,就支持响应式编程

  • Spring boot 1.x.x 不支持

  • Spring boot 2.x.x 才能支持

  • 在讨论 WebFlux之前,需要了解 RxJava 和 Reactor

基础概念

  • 响应式编程
  • 是一种 面向数据流 和 变化传播 的 编程规范
  • 数据流 data streams
  • 异步 asynchronous
  • 消息

响应式编程的宣言

  • 基于相应式宣言 的理念所产生的编程方式。分为4大理念
    • 灵敏的
    • 可恢复的 :强大的容错机制 和 修复机制
    • 可伸缩的:根据自身压力,实现伸缩
    • 消息驱动:异步消息机制,事件之间的协作是通过消息进行的
  • 基于这样的理念,提出了各种模型,著名的有:Reactor 和 RxJava
  • Spring 5 基于 他们构建 Webflux,默认情况下 使用 Reactor

Reactor模型

  • 传统的模型

    • 请求1,请求2 ,到达请求队列 ——分发器
    • 分发成 线程1 ,线程2
    • 往往请求量,大于系统最大线程数
      • 新来的线程 要等到 旧线程 运行完成后 才能提供服务,要么被系统所抛弃
      • 如:视频,游戏,图片,复杂计算
  • Reactor 反应器模式

    • 客户端1,客户端2 ——每个客户端生成一个event——事件都到 selector中

    • selector 分发出 n个 Dispatcher——(每个dispatcher对应一个) Request Handler1

    • 上面是,看图的描述,下面是官方的解释:

      1. 客户端像服务器 注册 其感兴趣 的事件 ,(客户端订阅了对应的事件,不给服务器发送请求),
      2. 客户端发生一些已经注册的事件时,会触发服务器的相应。
        1. 此时 服务器存在一个 Select线程,负责轮询客户端发送过来的事件
        2. 找到对应的请求处理器 Request Handler,启用另一个线程运行。
        3. select线程只是轮询,相应非常快。
        4. 事件有多种,请求处理器有多个。(区分事件的类型)
        5. select 存在路由,
        6. 请求处理器处理业务,结果最终转换为 数据流 data Stream 发送到客户端
        7. 数据流的处理,还有背压 back pressure等。
  • 简单解释:

    • Reactor 是基于事件的模型
    • 对于服务器而言,它也是一种异步的
    • select线程轮询到事件,通过路由 找到处理器去 运行对应的逻辑
    • 处理器 最后返回的结果 会转换为数据流
  • 我的简单理解:

    1. 客户端 注册事件,并订阅 触发响应。
    2. 服务器存在 selector ,去轮询事件,找到 request handler
    3. request handler 启用另一个线程,结果最终转为 数据流。

Spring WebFlux的概述

  • Servlet 3.1 之前 web容器 都是基于 阻塞机制 开发的

  • 高并发 网站,使用函数式 的 编程 就 更为 直观 和 简易

  • 特别是 那些需要高速响应 而 对业务 逻辑 ,要求 并不十分严格的网站。

    • 如:游戏,视频,新闻浏览
  • java 8 之后,引入了 Lambda 和 Functional接口

  • Spring 5 推出了 Spring WebFlux 新一代的 Web响应式 编程 框架。

  • 分为:

    1. Router Functions ,路由分发层。在reactor中,它就是selector作用
    2. Spring WebFlux,控制层。处理逻辑前 封装 和 控制数据流返回格式
    3. HTTP/Reactive Streams 将结果转换为 数据流的过程
  • 需要能够支持 Servlet 3.1 + 的容器, tomcat jetty undertow

  • 异步编程,使用最多的是 netty , 在 webFlux 的starter中 默认依赖 netty

  • 两种开发模式:

    • 类似于 MVC的模式
    • 函数功能性编程
  • 数据流的封装,Reactor提供的 Flux 和 Mono

    • flux 存放 0—N 个数据流 序列,响应式框架会一个接一个将它们发送到客户端
    • mono 存放0-1 个数据流序列,
    • 可以相互装换
  • 背压 backpressure ,只对 Flux有意义

    • 如果在很短的时间内,服务端将 大量的数据流 传输给 客户端,那么客户端就可能被压垮
    • 为了解决这个问题,一般使用 响应式拉取,将服务端的数据流 划分为多个序列,当客户端处理完这个序列后,在给服务端发送消息,然后在拉取第二个序列

webHandler接口 和 运行流程

  • mvc 使用 DispatcherServlet

  • webFlux 使用 WebHandler

  • WebHandler接口

    • DispatcherHandler 分发处理器 核心
    • ResourceWebHandler 资源处理器
    • WebHandlerDecorator webHandler装饰器,装饰着模式
      • Exception Handling WebHandler
      • Filtering WebHandler
      • Http WebHandler Adapter

DispatcherHandler 源码

    public Mono<Void> handle(ServerWebExchange exchange) {
        if (logger.isDebugEnabled()) {
            ServerHttpRequest request = exchange.getRequest();
            logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
        }

        return this.handlerMappings == null ? Mono.error(HANDLER_NOT_FOUND_EXCEPTION) : 
        //框架封装数据流 flux
        Flux
            .fromIterable(this.handlerMappings)//循环 handlerMappings
            .concatMap(
            (mapping) -> { //找到合适的处理器
           			 return mapping.getHandler(exchange);
	      		  }
      		  )
            .next()//处理第一条记录
            .switchIfEmpty( //如果找不到处理器的情况
            Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
            	//通过反射运行处理器
            .flatMap((handler) -> {
            return this.invokeHandler(exchange, handler);
        }).flatMap((result) -> {
            //解析结果,将其转换 为 对应的数据流 序列
            return this.handleResult(exchange, result);
        });
    }
  • 与 Spring MVC 一样,都是从 HandlerMapping 找到对应 的处理器

  • getHandler方法找到对应的处理器

  • invokeHandler方法运行 处理器

  • 找到合适 handlerAdapter去运行处理

  • handleResult 将结果 转变为 对应的数据流

  • HandlerMapping 处理映射器 (@Controller @RequestMapping 获得)

  • 到达 DispatcherHandler 分发处理器

  • HandlerAdapter 处理适配器

  • Result 处理器结果

  • 分发处理器

  • handleResult 结果处理,转换为对应的数据流

引入pom

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency> mongodb-reactive
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
		</dependency>

		<dependency> webflux
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency> tomcat
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency> 这里书上没引用
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
  • 以 mongoDB 作为响应式 变成的数据源
  • 引用的tomcat 作为默认的服务器
  • Spring WebFlux 只能支持 Spring Data Reactive 非阻塞的数据响应方式
  • 数据库的开发往往是阻塞的,所以不适用。
  • Spring Data Reactive 支持 Redis , MongoDB ,NoSQL
  • redis偏向 作为缓存使用,所以最广泛的用 MongoDB
  • 不能引用 spring-boot-starter-web ,否则 只会加在 Spring MVC 了。

通过MVC方式 开发WebFlux服务端

pojo

public class User implements Serializable {
	private static final long serialVersionUID = 3923229573077975377L;
	@Id
	private Long id;
	// 性别
	private SexEnum sex;
	// 在MongoDB中使用user_name保存属性
	@Field("user_name")
	private String userName;
	private String note;
}

public enum SexEnum {
	MALE(1, "男"), 
	FEMALE(0, "女");
    
	private int code;
	private String name;
	
	SexEnum(int code, String name) {
		this.code = code;
		this.name = name;
	}
	
	public static SexEnum getSexEnum(int code) {
		SexEnum [] emuns = SexEnum.values();
		for (SexEnum item : emuns) {
			if (item.getCode() == code) {
				return item;
			}
		}
		return null;
	}
    
}

dao

@Repository
//请注意这里需要继承ReactiveMongoRepository
public interface UserRepository extends ReactiveMongoRepository<User, Long> {
	/**
     * 对用户名和备注进行模糊查询
     * @param userName —— 用户名称
     * @param note —— 备注
     * @return 符合条件的用户
     */
    public Flux<User> findByUserNameLikeAndNoteLike(
        String userName, String note);
}
  • webFlux为响应式提供了接口: ReactiveMongoRepository
  • find ByUserName Like And NoteLike

service

public interface UserService {

    Mono<User> getUser(Long id);

    Mono<User> insertUser(User user);

    Mono<User> updateUser(User user);

    Mono<Void> deleteUser(Long id);

    Flux<User> findUsers(String userName, String note);
}


@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public Mono<User> getUser(Long id) {
        return userRepository.findById(id);
    }
    
    @Override
    public Mono<User> insertUser( User user) {
        return userRepository.save(user);
    }

    @Override
    public Mono<User> updateUser(User user) {
        return userRepository.save(user);
    }

    @Override
    public Mono<Void> deleteUser(Long id) {
        Mono<Void> result = userRepository.deleteById(id);
        return result;
    }

    @Override
    public Flux<User> findUsers(String userName, String note) {
        return userRepository.findByUserNameLikeAndNoteLike(userName, note);
    }

}

action (flux 和 mono 说明)

  • 也可用 @Controller @RestController @GetMapping

  • 使用 REST 风格 更合适

  • 定义用户的视图
public class UserVo {
	private Long id;
	private String userName;
	private int sexCode;
	private String sexName;
	private String note;
}
  • Flux 0—N 个数据流 序列
  • Mono 0—1 个 数据流序列
  • @GetMapping ,这样的请求 就会被解析到 HandlerMapping 的机制中
  • 就能够 根据 URI 进行路由 到对应的方法中去
// REST风格控制器,
@RestController //返回的内容将转为json对象
public class UserController {

	@Autowired
	private UserService userService;

	// 获取用户
	@GetMapping("/user/{id}")
	public Mono<UserVo> getUser(@PathVariable Long id) {
		return userService.getUser(id)
				// 从User对象转换为UserVo对象
				.map(u -> translate(u));
	}

	// 新增用户
	@PostMapping("/user")
	public Mono<UserVo> insertUser(@RequestBody User user) {
		return userService.insertUser(user)
				// 从User对象转换为UserVo对象
				.map(u -> translate(u));
	}

	// 更新用户
	@PutMapping("/user")
	public Mono<UserVo> updateUser(@RequestBody User user) {
		return userService.updateUser(user)
				// 从User对象转换为UserVo对象
				.map(u -> translate(u));
	}

	// 删除用户
	@DeleteMapping("/user/{id}")
	public Mono<Void> deleteUser(@PathVariable Long id) {
		return userService.deleteUser(id);
	}

	// 查询用户
	@GetMapping("/user/{userName}/{note}")
	public Flux<UserVo> findUsers(@PathVariable String userName, @PathVariable String note) {
		return userService.findUsers(userName, note)
				// 从User对象转换为UserVo对象
				.map(u -> translate(u));
	}



	// 加入局部验证器
	@InitBinder
	public void initBinder(DataBinder binder) {
		binder.setValidator(new UserValidator());
	}



	/***
	 * 完成PO到VO的转换
	 * 
	 * @param user
	 *            ——PO 持久对象
	 * @return UserVo ——VO 视图对象
	 */
	private UserVo translate(User user) {
		UserVo userVo = new UserVo();
		userVo.setUserName(user.getUserName());
		userVo.setSexCode(user.getSex().getCode());
		userVo.setSexName(user.getSex().getName());
		userVo.setNote(user.getNote());
		userVo.setId(user.getId());
		return userVo;
	}

}

配置文件

# MongoDB服务器
spring.data.mongodb.host=192.168.10.128
# MongoDB用户名。docker默认启动 没有用户名和密码
spring.data.mongodb.username=spring 
# MongoDB密码
spring.data.mongodb.password=123456
# MongoDB端口
spring.data.mongodb.port=27017
# MongoDB库名称
spring.data.mongodb.database=springboot

spring.webflux.static-path-pattern=/static/**
// 定义扫描包
@SpringBootApplication(scanBasePackages="com.springboot.chapter14")
// 由于引入JPA,默认的情况下,需要配置数据源,
// 通过@EnableAutoConfiguration排除原有自动配置的数据源
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
// 在WebFlux下,驱动MongoDB的JPA接口
@EnableReactiveMongoRepositories(
    // 定义扫描的包
    basePackages="com.springboot.chapter14.repository")
public class Chapter14Application  {

    public static void main(String[] args) {
        SpringApplication.run(Chapter14Application.class, args);
    }
    
}
  • 默认会 尝试 装配 关系数据库 数据源 ,所以排除数据源初始化
  • EnableReactiveMongoRepositories 开启响应式 MongoDB JPA接口
  • http://localhost:8080/user/1

客户端开发,WebClient

  • 用户请求

  • 网关

    • 产品微服务:发布管理产品
    • 用户微服务:管理会员
    • 账户微服务:管理财务
    • 交易微服务:
  • 一次交易:用户微服务 判断用户等级,财务微服务管理用户的消费款项,产品微服务管理产品的发放,交易微服务记录交易的发生情况。

  • 为了方便微服务之间的调用,webFlux 提供了 WebClient类,比RestTemplate更强大



public class Chapter14WebClient {
	public static void main(String[] args) {
        // 创建WebClient对象,并且设置请求基础路径
        WebClient client = WebClient.create("http://localhost:8080");
        getSecurityUser(client, 1L);
        updateUserName(client, 1L, "update_user_name");
        getUserPojo(client, 1L);
        // 一个新的用户
        User newUser = new User();
        newUser.setId(6L);
        newUser.setNote("note_6");
        newUser.setUserName("user_name_6");
        newUser.setSex(SexEnum.MALE);
        insertUser3(client, newUser);
        insertUser2(client);
        // 一个新的用户
        User newUser = new User();
        newUser.setId(1L);
        newUser.setNote("note_1");
        newUser.setUserName("user_name_1");
        newUser.setSex(SexEnum.MALE);
        // 新增用户
        insertUser(client, newUser);
        // 获取用户
        getUser(client, 1L);
        User updUser = new User();
        updUser.setId(1L);
        updUser.setNote("note_update");
        updUser.setUserName("user_name_update");
        updUser.setSex(SexEnum.FEMALE);
        // 更新用户
        updateUser(client, updUser);
        // 查询用户
        findUsers(client, "user", "note");
        // 删除用户
        deleteUser(client, 3L);

	}
    
    
    private static void getUser(WebClient client, Long id) {
		Mono<UserVo> userMono =
				// 定义GET请求
				client.get()
						// 定义请求URI和参数
						.uri("/user/{id}", id)
						// 接收请求结果类型
						.accept(MediaType.APPLICATION_STREAM_JSON)
						// 设置请求结果检索规则
						.retrieve()
						// 将结果体转换为一个Mono封装的数据流
						.bodyToMono(UserVo.class);
		// 获取服务器发布的数据流,此时才会发送请求
		UserVo user = userMono.block();
		System.out.println("【用户名称】" + user.getUserName());
	}
    //get() .uri   .accept(MediaType.APPLICATION_STREAM_JSON)
    //.retrieve()  .bodyToMono(UserVo.class);
    

	private static void insertUser(WebClient client, User newUser) {
		// 注意这只是定义一个时间,并不会发送请求
		Mono<UserVo> userMono =
				// 定义POST请求
				client.post()
						// 设置请求URI
						.uri("/user")
						// 请求体为JSON数据流
						.contentType(MediaType.APPLICATION_STREAM_JSON)
						// 请求体内容
						.body(Mono.just(newUser), User.class)
						// 接收请求结果类型
						.accept(MediaType.APPLICATION_STREAM_JSON)
						// 设置请求结果检索规则
						.retrieve()
						// 将结果体转换为一个Mono封装的数据流
						.bodyToMono(UserVo.class);
		// 获取服务器发布的数据流,此时才会发送请求
		UserVo user = userMono.block();
		System.out.println("【用户名称】" + user.getUserName());
	}
    // post() .uri("/user") .contentType .body(Mono.just(newUser), User.class)
    // .accept .retrieve()  .bodyToMono
    //userMono.block(); 才会发送请求
	
	
	private static void insertUser3(WebClient client, User newUser) {
		// 注意这只是定义一个时间,并不会发送请求
		Mono<UserVo> userMono =
				// 定义POST请求
				client.post()
						// 设置请求URI
						.uri("/user3")
						// 请求体为JSON数据流
						.contentType(MediaType.APPLICATION_STREAM_JSON)
						// 请求体内容
						.body(Mono.just(newUser), User.class)
						// 接收请求结果类型
						.accept(MediaType.APPLICATION_STREAM_JSON)
						// 设置请求结果检索规则
						.retrieve()
						// 将结果体转换为一个Mono封装的数据流
						.bodyToMono(UserVo.class);
		// 获取服务器发布的数据流,此时才会发送请求
		UserVo user = userMono.block();
		System.out.println("【用户名称】" + user.getUserName());
	}

	

	private static void updateUser(WebClient client, User updUser) {
		Mono<UserVo> userMono =
				// 定义PUT请求
				client.put().uri("/user")
						// 请求体为JSON数据流
						.contentType(MediaType.APPLICATION_STREAM_JSON)
						// 请求体内容
						.body(Mono.just(updUser), User.class)
						// 接收请求结果类型
						.accept(MediaType.APPLICATION_STREAM_JSON)
						// 设置请求结果检索规则
						.retrieve()
						// 将结果体转换为一个Mono封装的数据流
						.bodyToMono(UserVo.class);
		// 获取服务器发布的数据流,此时才会发送请求
		UserVo user = userMono.block();
		System.out.println("【用户名称】" + user.getUserName());
	}

	private static void findUsers(WebClient client, String userName, String note) {
		// 定义参数map
		Map<String, Object> paramMap = new HashMap<>();
		paramMap.put("userName", userName);
		paramMap.put("note", note);
		Flux<UserVo> userFlux =
				// 定义PUT请求,使用Map传递多个参数
				client.get().uri("/user/{userName}/{note}", paramMap)
						// 接收请求结果类型
						.accept(MediaType.APPLICATION_STREAM_JSON)
						// 设置请求结果检索规则
						.retrieve()
						// 将结果体转换为一个Mono封装的数据流
						.bodyToFlux(UserVo.class);

		// 通过Iterator遍历结果数据流,执行后服务器才会响应
		Iterator<UserVo> iterator = userFlux.toIterable().iterator();
		// 遍历
		while (iterator.hasNext()) {
			UserVo item = iterator.next();
			System.out.println("【用户名称】" + item.getUserName());
		}
	}
    //.bodyToFlux 
    // userFlux.toIterable().iterator() 才会发送请求
    //下来式处理,只在每一次 执行循环 时,才会向服务器 要一个 数据流序列 到 客户端处理。
    //当处理完一个数据流序列后,才会执行第二次,获取下一个数据流序列。直到获取所有的数据流序列。

	private static void deleteUser(WebClient client, Long id) {
		Mono<Void> result = client.delete()
				// 设置请求URI
				.uri("/user/{id}", id)
				// 接收请求结果类型
				.accept(MediaType.APPLICATION_STREAM_JSON)
				// 设置请求结果检索规则
				.retrieve()
				// 将结果体转换为一个Mono封装的数据流
				.bodyToMono(Void.class);
		// 获取服务器发布的数据流,此时才会发送请求
		Void voidResult = result.block();
		System.out.println(voidResult);
	}
    //.bodyToMono(Void.class);

	private static void insertUser2(WebClient client) {
	    // 注意这只是定义一个时间,并不会发送请求
	    Mono<UserVo> userMono = 
	        // 定义POST请求
	        client.post()
	        // 设置请求URI,和约定格式的用户信息
	        .uri("/user2/{user}", "2-convert4-0-note4")
	        // 接收请求结果类型
	        .accept(MediaType.APPLICATION_STREAM_JSON)
	        // 设置请求结果检索规则
	        .retrieve()
	        // 将结果体转换为一个Mono封装的数据流
	        .bodyToMono(UserVo.class);
	    // 获取服务器发布的数据流,此时才会发送请求
	    UserVo user = userMono.block();
	    System.out.println("【用户名称】" + user.getUserName());
	}
	

	
	// 转换方法
	private static UserPojo translate(UserVo vo) {
	    if (vo == null) {
	        return null;
	    }
	    UserPojo pojo = new UserPojo();
	    pojo.setId(vo.getId());
	    pojo.setUserName(vo.getUserName());
	    // 性别转换
	    pojo.setSex(vo.getSexCode() == 1 ? 1 : 2);
	    pojo.setNote(vo.getNote());
	    return pojo;
	}

	public static void getUserPojo(WebClient client, Long id) {
	    Mono<UserPojo> userMono =
	        // HTTP GET请求
	        client.get()
	             // 定义请求URI和参数
	            .uri("/user/{id}", id)
	             // 接收结果为JSON数据流
	            .accept(MediaType.APPLICATION_STREAM_JSON)
	             // 启用交换
	            .exchange()
	             // 出现错误则返回空
	            .doOnError(ex -> Mono.empty())
	             // 获取服务器发送过来的UserVo对象
	            .flatMap(response -> response.bodyToMono(UserVo.class))
	             // 通过自定义方法转换为客户端的UserPojo
	            .map(user -> translate(user));
	    // 获取客户端的UserPojo
	    UserPojo pojo = userMono.block();
	    // 不为空打印信息
	    if (pojo != null) {
	        System.out.println("获取的用户名称为" + pojo.getUserName());
	    } else {
	        System.out.println("获取的用户编号为" + id + "失败");
	    }
	}
	
	
	
	
}


深入了解 WebFlux服务端开发

  • 读取请求的http首部所设置内容

  • 新增参数转换器 和 验证规则

  • 错误的处理

  • 实现 WebFluxConfigurer

类型转换器

配置

  • 约定用户 以字符串格式: {id}-{useerName}-{sex}-{note} 传递
// 实现Java 8的接口WebFluxConfigurer,该接口都是default方法
@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
    // 注册Converter
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(stringToUserConverter());
    }

    // 定义String --> User类型转换器
    // @Bean// 如果定义为Spring Bean,Spring Boot会自动识别为类型转换器
    // 这种方法 更加简单
    public Converter<String, User> stringToUserConverter() {
        Converter<String, User> converter = new Converter<String, User>() {
            @Override
            public User convert(String src) {
                String strArr[] = src.split("-");
                User user = new User();
                Long id = Long.valueOf(strArr[0]);
                user.setId(id);
                user.setUserName(strArr[1]);
                int sexCode = Integer.valueOf(strArr[2]);
                SexEnum sex = SexEnum.getSexEnum(sexCode);
                user.setSex(sex);
                user.setNote(strArr[3]);
                return user;
            }
        };
        return converter;
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
            // 注册资源,可以通过URI访问
            .addResourceHandler("/resources/static/**")
            // 注册Spring资源,可以在Spring机制中访问
            .addResourceLocations("/public/**", "classpath:/static/")
            // 缓存一年(365天)
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
    }
    
//    // 设置全局性验证器
//    @Override
//    public Validator getValidator() {
//        return new UserValidator();
//    }
}

使用

  • 使用转换器

	@PostMapping("/user2/{user}")
	public Mono<UserVo> insertUser2(@PathVariable("user") User user) {
		return userService.insertUser(user)
				// 进行PO和VO之间的转换
				.map(u -> translate(u));
	}

webClient测试

 private static void insertUser2(WebClient client) {
        // 注意这只是定义一个时间,并不会发送请求
        Mono<UserVo> userMono =
                // 定义POST请求
                client.post()
                        // 设置请求URI,和约定格式的用户信息
                        .uri("/user2/{user}", "2-convert4-0-note4")
                        // 接收请求结果类型
                        .accept(MediaType.APPLICATION_STREAM_JSON)
                        // 设置请求结果检索规则
                        .retrieve()
                        // 将结果体转换为一个Mono封装的数据流
                        .bodyToMono(UserVo.class);
        // 获取服务器发布的数据流,此时才会发送请求
        UserVo user = userMono.block();
        System.out.println("【用户名称】" + user.getUserName());
    }
  • 日期的格式化:spring.webflux.date-format=yyyy-MM-dd

验证器

定义

public class UserValidator implements Validator {

	// 确定支持的验证类型
    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(User.class);
    }
    
    // 验证逻辑
    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;
        // 监测用户名是否为空
        if (StringUtils.isEmpty(user.getUserName())) {
            errors.rejectValue("userName", null, "用户名不能为空");
        }
    }

}

配置

@Configuration
public class WebFluxConfig implements WebFluxConfigurer {

    // 设置全局性验证器
    @Override
    public Validator getValidator() {
        return new UserValidator();
    }
}
  • 创建一个验证器,不能是多个,为各个控制器所共享

使用

	@PostMapping("/user3")
	public Mono<UserVo> insertUser3(@Valid @RequestBody User user) {
		return userService.insertUser(user)
				// 进行PO和VO之间的转换
				.map(u -> translate(u));
	}
	
  • 这里加入的是 全局验证器,有时候希望 局部验证器
  • 可以仿照 MVC的 @InitBinder,将类和 验证器进行绑定
//只会在这一个controller生效
	// 加入局部验证器
	@InitBinder
	public void initBinder(DataBinder binder) {
		binder.setValidator(new UserValidator());
	}

访问静态资源

@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
            // 注册资源,可以通过URI访问
            .addResourceHandler("/resources/static/**")
            // 注册Spring资源,可以在Spring机制中访问
            .addResourceLocations("/public/**", "classpath:/static/")
            // 缓存一年(365天)
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
    }
    
}
  • 直接通过URI访问 static下资源

深入客户端开发

  • 设置请求头

  • 服务端发生了异常处理

  • 之前是通过 retrieve 方法 将 服务端的数据流转换

错误处理

	public static void getUser2(WebClient client, Long id) {
	    Mono<UserVo> userMono =
	        // HTTP GET请求
	        client.get()
	            // 定义请求URI和参数
	            .uri("/user/{id}", id)
	            // 接收结果为JSON数据流
	            .accept(MediaType.APPLICATION_STREAM_JSON)
	            // 设置检索
	            .retrieve().onStatus(
	                // 发生4开头或者5开头的状态码,4开头是客户端错误,5开头是服务器错误
	                // 第一个Lambda表达式,返回如果为true,则执行第二个Lambda表达式
	                status -> status.is4xxClientError() || status.is5xxServerError(),
	                // 如果发生异常,则用第二个表达式返回作为结果
	                // 第二个Lambda表达式
	                response -> Mono.empty())
	                // 将请求结果转换为Mono数据流
	            .bodyToMono(UserVo.class);
	    UserVo user = userMono.block();
	    // 如果用户正常返回
	    if (user != null) {
	        System.out.println("【用户名称】" + user.getUserName());
	    } else {// 不能正常返回或者用户为空
	        System.out.println("服务器没有返回编号为:" + id + "的用户");
	    }
	}
  • response -> Mono.empty()) 让结果转变成空

自定义转换规则

//客户端的pojo
public class UserPojo {
	private Long id;
	private String userName;
	// 1-男 2-女
	private int sex = 1;
	private String note = null;
}

//服务器的pojo
public class UserVo {
	private Long id;
	private String userName;
	private int sexCode;
	private String sexName;
	private String note;
}

    // 转换方法
    private static UserPojo translate(UserVo vo) {
        if (vo == null) {
            return null;
        }
        UserPojo pojo = new UserPojo();
        pojo.setId(vo.getId());
        pojo.setUserName(vo.getUserName());
        // 性别转换
        pojo.setSex(vo.getSexCode() == 1 ? 1 : 2);
        pojo.setNote(vo.getNote());
        return pojo;
    }


 public static void getUserPojo(WebClient client, Long id) {
        Mono<UserPojo> userMono =
                // HTTP GET请求
                client.get()
                        // 定义请求URI和参数
                        .uri("/user/{id}", id)
                        // 接收结果为JSON数据流
                        .accept(MediaType.APPLICATION_STREAM_JSON)
                        // 启用交换
                        .exchange()
                        // 出现错误则返回空
                        .doOnError(ex -> Mono.empty())
                        // 获取服务器发送过来的UserVo对象
                        .flatMap(response -> response.bodyToMono(UserVo.class))
                        // 通过自定义方法转换为客户端的UserPojo
                        .map(user -> translate(user));
        // 获取客户端的UserPojo
        UserPojo pojo = userMono.block();
        // 不为空打印信息
        if (pojo != null) {
            System.out.println("获取的用户名称为" + pojo.getUserName());
        } else {
            System.out.println("获取的用户编号为" + id + "失败");
        }
    }


  • translate(user) 将 服务端对象 转换为 客户端对象

  • 不用 retrieve而是:.exchange() 方法,允许自定义转换

  • .flatMap(response -> response.bodyToMono(UserVo.class)) 对服务器的方法请求 转换为 Mono对象

  • .map(user -> translate(user)); 转成 Mono

设置请求头


	@PutMapping("/user/name")
	public Mono<UserVo> updateUserName(@RequestHeader("id") Long id,  
	        @RequestHeader("userName") String userName) {
	    Mono<User> userMono = userService.getUser(id);
	    User user = userMono.block();
	    if (user == null) { // 查找不到用户信息,抛出运行异常消息......
	        throw new RuntimeException("找不到用户信息");
	    }
	    user.setUserName(userName);
	    return this.updateUser(user);
	}


//这里没用
public static void updateUserName(WebClient client, Long id, String userName) {
	    Mono<UserVo> monoUserVo = client
	         // HTTP PUT请求 
	        .put()
	         // 请求URI
	        .uri("/user/name", userName)
	         // 第一个请求头
	        .header("id", id +"")
	         // 第二个请求头
	        .header("userName", userName)
	         // 设置接收JSON数据流
	        .accept(MediaType.APPLICATION_STREAM_JSON)
	         // 检索
	        .retrieve()
	         // 根据服务端响应码处理逻辑
	        .onStatus(
	            status -> status.is4xxClientError() || status.is5xxServerError(), 
	            response -> Mono.empty())
	         // 转换为UserVo对象
	        .bodyToMono(UserVo.class);
	    UserVo userVo = monoUserVo.block();
	    // 不为空打印信息
	    if (userVo != null) {
	        System.out.println("获取的用户名称为" + userVo.getUserName());
	    } else {
	        System.out.println("获取的用户编号为" + id + "失败");
	    }
	}

public static void getSecurityUser(WebClient client, Long id) {
	    Mono<UserVo> monoUserVo = client
	         // HTTP PUT请求 
	        .get()
	         // 请求URI
	        .uri("/security/user/{id}", id)
	         // 第一个请求头
	        .header("header_password", "pwd")
	         // 第二个请求头
	        .header("header_user", "user")
	         // 设置接收JSON数据流
	        .accept(MediaType.APPLICATION_STREAM_JSON)
	         // 检索
	        .retrieve()
	         // 根据服务端响应码处理逻辑
	        .onStatus(
	            status -> status.is4xxClientError() || status.is5xxServerError(), 
	            response -> Mono.empty())
	         // 转换为UserVo对象
	        .bodyToMono(UserVo.class);
	    UserVo userVo = monoUserVo.block();
	    // 不为空打印信息
	    if (userVo != null) {
	        System.out.println("获取的用户名称为" + userVo.getUserName());
	    } else {
	        System.out.println("获取的用户编号为" + id + "失败");
	    }
	}

使用路由函数方式 开发 WebFlux

  • 路由函数 router functions
  • 体验了高并发的特性,函数式编程 的潮流
  • 可读性,可维护性变差

处理器

  • 需要开发一个处理器,用来处理各种场景
  • 在这个基础上,可开发用户处理器

@Service
public class UserHandler {

	@Autowired
	private UserRepository userRepository = null;

	public Mono<ServerResponse> getUser(ServerRequest request) {
		// 获取请求URI参数
		String idStr = request.pathVariable("id");
		Long id = Long.valueOf(idStr);
		Mono<UserVo> userVoMono = userRepository.findById(id)
				// 转换为UserVo
				.map(u -> translate(u));
		return ServerResponse
				// 响应成功
				.ok()
				// 响应体类型
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				// 响应体
				.body(userVoMono, UserVo.class);
	}
	
 // .cache() 不使用:程序就会在等待数据的接收
    
    
	public Mono<ServerResponse> insertUser(ServerRequest request) {
	    Mono<User> userMonoParam = request.bodyToMono(User.class);
	    Mono<UserVo> userVoMono = userMonoParam
	            // 缓存请求体
	            .cache()
	            // 处理业务逻辑,转变数据流
	            .flatMap(user  ->userRepository.save(user)
	            // 转换为UserVo对象
	            .map(u->translate(u)));
	    return ServerResponse
	            // 响应成功
	            .ok()
	            // 响应体类型
	            .contentType(MediaType.APPLICATION_JSON_UTF8)
	            // 响应体
	            .body(userVoMono, UserVo.class);
	}
	
	public Mono<ServerResponse> updateUser(ServerRequest request) {
	    Mono<User> userMonoParam = request.bodyToMono(User.class);
	    Mono<UserVo> userVoMono = userMonoParam.cache()
	            .flatMap(user ->userRepository.save(user)
	                    .map(u->translate(u)));
	    return ServerResponse
	            // 响应成功
	            .ok()
	            // 响应体类型
	            .contentType(MediaType.APPLICATION_JSON_UTF8)
	            // 响应体
	            .body(userVoMono, UserVo.class);
	}
	
	public Mono<ServerResponse> deleteUser(ServerRequest request) {
	    // 获取请求URI参数
	    String idStr = request.pathVariable("id");
	    Long id = Long.valueOf(idStr);
	    Mono<Void> monoVoid = userRepository.deleteById(id);
	    return ServerResponse
	            // 响应成功
	            .ok()
	            // 响应体类型
	            .contentType(MediaType.APPLICATION_JSON_UTF8)
	            // 响应体
	            .body(monoVoid, Void.class);
	}
	
	public Mono<ServerResponse> findUsers(ServerRequest request) {
	    String userName = request.pathVariable("userName");
	    String note = request.pathVariable("note");
	    Flux<UserVo> userVoFlux = 
	            userRepository.findByUserNameLikeAndNoteLike(userName, note)
	            .map(u -> translate(u));
	    // 请参考getUser方法的注释
	    return ServerResponse
	            .ok()
	            .contentType(MediaType.APPLICATION_JSON_UTF8)
	            .body(userVoFlux, UserVo.class);
	}

	public Mono<ServerResponse> updateUserName(ServerRequest request) {
	    // 获取请求头数据
	    String idStr = request.headers().header("id").get(0);
	    Long id = Long.valueOf(idStr);
	    String userName = request.headers().header("userName").get(0);
	    // 获取原有用户信息
	    Mono<User> userMono = userRepository.findById(id);
	    User user = userMono.block();
	    // 修改用户名
	    user.setUserName(userName);
	    Mono<UserVo> result = userRepository.save(user).map(u -> translate(u));
	    // 响应结果
	    return ServerResponse
	            .ok()
	            .contentType(MediaType.APPLICATION_JSON_UTF8)
	            .body(result, UserVo.class);
	}
	
	/***
	 * 完成PO到VO的转换
	 * 
	 * @param user
	 *            PO 持久对象
	 * @return UserVo ——VO 视图对象
	 */
	private UserVo translate(User user) {
		UserVo userVo = new UserVo();
		userVo.setUserName(user.getUserName());
		userVo.setSexCode(user.getSex().getCode());
		userVo.setSexName(user.getSex().getName());
		userVo.setNote(user.getNote());
		userVo.setId(user.getId());
		return userVo;
	}
}

  • Mono updateUserName(ServerRequest request)

开发请求路由

  • 与 请求URI 对应起来。

  • http请求 映射到方法上

//静态导入
import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration
public class RouterConfig {
	// 注入用户处理器
	@Autowired
	private UserHandler userHandler = null;





	// 用户路由
	@Bean
	public RouterFunction<ServerResponse> userRouter() {
		RouterFunction<ServerResponse> router =
				// 对应请求URI的对应关系
				route(
						// GET请求和其路径
						GET("/router/user/{id}")
								// 响应结果为JSON数据流
								.and(accept(APPLICATION_STREAM_JSON)),
						// 定义处理方法
						userHandler::getUser)
								// 增加一个路由
								.andRoute(
										// GET请求和其路径
										GET("/router/user/{userName}/{note}").and(accept(APPLICATION_STREAM_JSON)),
										// 定义处理方法
										userHandler::findUsers)
								// 增加一个路由
								.andRoute(
										// POST请求和其路径
										POST("/router/user")
												// 请求体为JSON数据流
												.and(contentType(APPLICATION_STREAM_JSON)
														// 响应结果为JSON数据流
														.and(accept(APPLICATION_STREAM_JSON))),
										// 定义处理方法
										userHandler::insertUser)
								// 增加一个路由
								.andRoute(
										// PUT请求和其路径
										PUT("/router/user")
												// 请求体为JSON数据流
												.and(contentType(APPLICATION_STREAM_JSON))
												// 响应结果为JSON数据流
												.and(accept(APPLICATION_STREAM_JSON)),
										// 定义处理方法
										userHandler::updateUser)
								.andRoute(
										// DELETE请求和其路径
										DELETE("/router/user/{id}")
												// 响应结果为JSON数据流
												.and(accept(APPLICATION_STREAM_JSON)),
										// 定义处理方法
										userHandler::deleteUser)
								.andRoute(
										// PUT请求和其路径
										PUT("/router/user/name")
												// 响应结果为JSON数据流
												.and(accept(APPLICATION_STREAM_JSON)),
										// 定义处理方法
										userHandler::updateUserName);
		return router;
	}


}


使用过滤器

  • 通过验证身份后,才能处理 业务逻辑

  • 请求头上存放用户名和密码,才能访问

  • 在上个类上加入:

    	// 请求头用户名属性名称
    	private static final String HEADER_NAME = "header_user";
    	// 请求头密码属性名称
    	private static final String HEADER_VALUE = "header_password";
    
    	@Bean
    	public RouterFunction<ServerResponse> securityRouter() {
    		RouterFunction<ServerResponse> router =
    				// 对应请求URI的对应关系
    				route(
    						// GET请求和其路径
    						GET("/security/user/{id}")
    								// 响应结果为JSON数据流
    								.and(accept(APPLICATION_STREAM_JSON)),
    						// 定义处理方法
    						userHandler::getUser)
    								// 使用过滤器
    								.filter((request, next) -> filterLogic(request, next));
    		return router;
    	}
    
    
    	// 请求过滤器逻辑
    	private Mono<ServerResponse> filterLogic(ServerRequest request, HandlerFunction<ServerResponse> next) {
    		// 取出请求头
    		String userName = request.headers().header(HEADER_NAME).get(0);
    		String password = request.headers().header(HEADER_VALUE).get(0);
    		// 验证通过的条件
    		if (!StringUtils.isEmpty(userName) && !StringUtils.isEmpty(password) && !userName.equals(password)) {
    			// 接受请求
    			return next.handle(request);
    		}
    		// 请求头不匹配,则不允许请求,返回为未签名错误 401,
    		return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
    	}
    
    
  • .filter((request, next) -> filterLogic(request, next));
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值