web编程(2) spring-webflux 鸟瞰

1. 概述

Spring WebFlux 是Spring 5 引入的全新技术框架,采用响应式编程,最大的特点,就是从请求到响应整个过程都是异步非阻塞,底层支持netty,undertow,以及servlet 3.1 +等web容器。跟上一篇对mvc介绍一样,先抛开具体的技术,从外部视角,最简单的流程开始。

1.1 简单流程

它跟spring-mvc处理过程相似,同样包含以下步骤:

步骤1:解析http协议请求数据包,与mvc封装为Request类对象不同,它封装为ServerHttpRequest类对象。

步骤2:通过url匹配、映射到一个具体的Controller类method,或HandlerFunction实例。

步骤3:解析Request对象数据,转换为控制类method的参数数据。

步骤4:校验method的参数数据。

步骤5:invoke控制类method方法。

步骤6:按视图格式(json,xml,html等),序列化返回值。

步骤7:将视图数据,按http协议格式刷入TCP响应流。

1.2 spring-webflux流程

1.3 核心类

以下是spring-webflux处理过程中,最常见的几个类。

(1) DispatcherHandler类

所有spring-webflux处理请求,都由该类的handle(ServerWebExchange exchange)方法统一处理,从匹配handler,到invokeHandler, 以及最后view的处理逻辑,贯穿整个过程。

(2) RequestMappingHandlerMapping类

该类负责匹配controller类型的handler,通过url查找到唯一的controller.method,然后封装为Mono<HandlerMethod>实例,它不像mvc体系,还需要匹配spring拦截器。

为了提高匹配效率,在web项目启动时,它的内部类MappingRegistry,会扫描、加载所有RequestMapping方法,预先构建url查找字典。

(3) RouterFunctionMapping类

该类负责匹配HandlerFunction类型的handler,该handler通过RouterFunction方式路由配置。

(4) RequestMappingHandlerAdapter、HandlerFunctionAdapter类

该类负责controller类型的handler的invoke处理,以及调用前的参数解析、校验等准备逻辑。

(5) ResponseBodyResultHandler类

该类负责处理响应结果HandlerResult。

其中,响应式编程基础Mono,Flux类,计划单独整理,这里就不赘述了。

2. 实施

首先,需要添加依赖

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

2.1 注解模式

这种编程模型与传统 Spring MVC 注解的使用上一致,样例如下:

@Controller
@RequestMapping(value="/demo")
public class DemoController {
	
	private final Logger logger = LoggerFactory.getLogger(DemoController2.class);	
	private final AtomicInteger atomicInt = new AtomicInteger();
	
	@Autowired
	private MyService myService;

	@RequestMapping(value="/info1")
	@ResponseBody
	public Mono<String> info1(ServerHttpRequest request, ServerHttpResponse response) {
		response.getHeaders().add("myinfo", "xxx");
		return Mono.just("hello spring flux");				
	}		
	
	@GetMapping(value="/info2")
	@ResponseBody
	public Mono<String> info2(
			@RequestParam("id") Integer id) {
		
		logger.info("enter function...." + Thread.currentThread().getName());	
		
		return Mono.create(x -> {
			x.success("hello: " +  atomicInt.getAndIncrement());
		});				
	}
	
	@GetMapping(value="/info3")
	@ResponseBody
	public Mono<Apple> info3(
			@RequestParam("id") Integer id, 
			@RequestParam("name") String name) {
		
		Mono<Apple> result = Mono.just(id)
		.flatMap(x -> this.myService.getNameFromMvc(x))
		.map(x -> {	
			Apple apple = new Apple();
			apple.setId(id);
			apple.setName(x);
			return apple;
		});

		return result;				
	}

	@GetMapping(value="/info4/{id}")
	@ResponseBody
	public Mono<String> info4(@PathVariable("id") String id) {
		return Mono.just("hello: " + id);				
	}	

	@PostMapping(value="/info5")
	@ResponseBody
	public Mono<String> info5(
			@RequestParam("id") String id,
			@Validated
			@RequestBody Apple bb) throws JsonProcessingException {
		
		ObjectMapper mapper = new ObjectMapper();
		logger.info("request info :{}, {}", id, mapper.writeValueAsString(bb));

		return Mono.just("hello info5");				
	}
}

2.2 HandlerFunction模式

通过HandlerFunction接口继承类,来处理handle逻辑,然后由RouterFunction配置该handler的匹配谓词RequestPredicate,样例如下:

HandlerFunction继承类

@Service
public class HelloHandler implements HandlerFunction<ServerResponse> {

	@Override
	public Mono<ServerResponse> handle(ServerRequest request) {
		 return ServerResponse.ok().body(BodyInserters.fromValue("Hello World"));		
	}
}

RouterFunction配置

@Configuration
public class RoutingConfig {
	
	@Autowired
	private HelloHandler helloHandler;

	@Bean
    public RouterFunction<ServerResponse> router1() {

		RouterFunction<ServerResponse> router = 
				RouterFunctions.route(RequestPredicates.path("/hello-world"), helloHandler::handle);
		return router;
	}
}

3. 环节

3.1 解析http请求包

默认web容器netty,首先netty将请求数据包,解析为HttpRequest实例,然后再由spring-web框架,统一封装为ServerHttpRequest实例。

3.2 web过滤器

过滤器 WebFilter,区别于spring-mvc中过滤器,它完全是spring-web框架下概念,它同样也可对http请求进行统一拦截、重定向、修改等处理,样例如下:

@Component
@Order(value = 1)
public class MyWebFilter3 implements WebFilter {
	
	private final Logger logger = LoggerFactory.getLogger(MyWebFilter1.class);

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

		if (this.isPermit(exchange.getRequest().getPath().value())) {
			logger.info("拦截请求:" + exchange.getRequest().getURI());
					
			ServerHttpResponse response = exchange.getResponse();						
			byte[] msg = "no permit request.".getBytes(java.nio.charset.StandardCharsets.UTF_8);
			DataBuffer buffer = response.bufferFactory().wrap(msg);			
			return response.writeWith(Mono.just(buffer));
		}
		
		if (this.isRedirect(exchange.getRequest().getPath().value())) {
			logger.info("重定向请求:" + exchange.getRequest().getURI());
			
			ServerHttpResponse response = exchange.getResponse();			
			response.setStatusCode(HttpStatus.FOUND);
	        response.getHeaders().setLocation(URI.create("https://www.baidu.com"));			
	        return response.setComplete();
		}
		
		if (this.isInnerRedirect(exchange.getRequest().getPath().value())) {	
			logger.info("内部重定向:" + exchange.getRequest().getURI());
			ServerHttpRequest authErrorReq = exchange.getRequest().mutate().path("/demo2/getInfo3/103").build();			
            ServerWebExchange authErrorExchange = exchange.mutate().request(authErrorReq).build();
            return chain.filter(authErrorExchange);			
		}

		return chain.filter(exchange);
	}
	
	private boolean isPermit(String url) {
		return url.equals("/xxx/xxx");
	}
	
	private boolean isRedirect(String url) {
		return url.equals("/xxx/xxx");
	}
	
	private boolean isInnerRedirect(String url) {
		return url.equals("/xxx/xxx");
	}	

}

3.3 请求参数转换、解析

spring提供了多种处理方式,主要有Converter、HttpMessageConverter,以及注解等,这部分逻辑跟spring-mvc是一样的,就不在赘述。细节可参考上一篇介绍spring-mvc时,对该部分内容的介绍:web编程(1) spring-webmvc

4. 配置

一般通过实例化spring提供的WebFluxConfigurer接口,进行统一配置,样例如下:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
	
	/*
	 * 静态资源
	 */		
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
	}	
	
	/*
	 * 跨域资源共享
	 */		
	@Override
    public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
        .allowedOrigins("http://localhost:8080", "https://www.example.com")
        .allowedMethods("GET", "POST")
        .allowCredentials(true);
    }
	
	/*
	 * http message编解码
	 */
	@Override
	public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
		configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder());
		configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder());
	}

	/*
	 * Converter转换类
	 */	
    @Override
    public void addFormatters(FormatterRegistry registry) {
    	registry.addConverter(new StringToDateConverter());
    }
}

5. 异常

通过@ControllerAdvice注解,统一处理异常,这部分跟spring-webmvc没有区别。

@ControllerAdvice
public class GlobalExceptionHandler {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public String methodArgumentNotValidException(MethodArgumentNotValidException e){
    	logger.error("GlobalExceptionHandler error", e);
    	
    	FieldError f = e.getBindingResult().getFieldError();    	
    	logger.info("methodArgumentNotValidException: {}, {}, {}", f.getField(), f.getDefaultMessage(), f.getObjectName());
    	
    	return "GlobalExceptionHandler: " + e.getMessage();
    }	

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String handleException(Exception e){
    	logger.error("GlobalExceptionHandler error", e);
        return "GlobalExceptionHandler: " + e.getMessage();
    }
}

6. 总结

(1)由于http请求IO处理,采用异步非阻塞方式,它的线程很少,Netty容器默认IO线程数是内核数量的2倍,而webflux调整为1倍,非常适用于解决IO密集处理场景,例如api网关,它的CPU利用率很高,不存在多线程开发中,线程的上下文切换开销问题。 

(2)整个处理过程,必须是非阻塞的,否则性能就会很差,包括依赖的第三方服务,都需要采用非阻塞方式处理,目前redis, mongodb 都已支持响应式数据访问,因JDBC本身同步阻塞,需要改由R2DBC方式处理。

(3)对于spring cloud 项目,web 客户端解决方案Feign,现在还不支持响应式处理,如果需要,官方建议使用 feign-reactive 来代替处理。

(4)对于HTTP请求客户端的处理,需要通过 WebClient,它本身完全非阻塞,跟controller层共享IO处理线程。

(5)以上都是该框架的优点,不足之处就是,传统的同步阻塞技术体系,积累的历史资源比较丰富,相对而言,响应式技术体系还在不断发展、丰富中,可利用的资源要少一些。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值