SpringBoot系列:基于SpringBoot2.0的WebFlux应用入门

Spring WebFlux是在Spring框架5中引入的一种新的反应式Web框架。与Spring MVC不同,它不需要servlet API,完全异步和非阻塞,并通过Reactive Project实现Reactive Streams规范。

官网文档地址:

https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/web-reactive.html#webflux

1、创建一个SpringBoot2.0项目

Eclipse中创建Maven工程,也可以使用Spring Initializer创建,在Pom.xml中添加依赖:

注意:SpringBoot2.0仅支持JDK8.0以上,不支持JDK6、JDK7


	4.0.0
	com.sample
	sun-sample-springboot-webflux
	0.0.1-SNAPSHOT
	
	
		
			
				org.springframework.boot
				spring-boot-dependencies
				2.0.1.RELEASE
				pom
				import
			
		
	

	
		
			org.springframework.boot
			spring-boot-starter
		

		
			org.springframework.boot
			spring-boot-starter-webflux
		
		
		
            org.springframework.boot
            spring-boot-starter-data-jpa
        

        
            com.h2database
            h2
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	

Spring-boot-starter-webflux 将 spring-webflux、netty 以及其他必须的依赖包引入到类路径中。

h2database 是h2内存数据库,用于快速测试

jpa 是ORM操作,快速编写CRUD

工程主函数代码如下:

package com.suncht.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebfluxApplication {
	public static void main(String[] args) {
		SpringApplication.run(WebfluxApplication.class, args);
	}
}

application.properties属性文件配置如下:

server.port=8080

spring.datasource.url=jdbc:h2:file:D\:/h2/h2test;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true

spring.h2.console.enabled=true
spring.h2.console.path=/console
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false

#logging.level.root=debug

2、创建 HttpServer 

package com.suncht.sample.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;

import reactor.ipc.netty.http.server.HttpServer;

/**
 * 配置HttpServer
 * 可以配置基于Netty、基于Tomcat、基于Jetty
 * @author suncht
 *
 */
@Configuration
public class HttpServerConfig {
	@Autowired
	private Environment environment;

	@Bean
	public HttpServer httpServer(RouterFunction routerFunction) {
		HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
		ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
		HttpServer server = HttpServer.create("localhost", Integer.valueOf(environment.getProperty("server.port")));
		server.newHandler(adapter);
		return server;
	}
}

这个根据应用配置中指定的端口创建一个 Netty HttpServer 服务器。Spring 支持例如 Tomcat 或者 Undertow 等其他服务器。因为 Netty 本身是异步的和事件驱动的,因此它更适合用来运行 Reactive 应用。而 Tomcat 使用 Java NIO 来实现 Servlet 规范。Netty 的 NIO 实现专门针对异步、事件驱动和非堵塞应用进行了优化。

3、编写Http请求的业务逻辑

WebFlux 支持两种编程:

(1)使用@Controller这种基于注解的姿势, 与Sring MVC的姿势相同,最简单最简洁

(2)基于Java 8 Lambda的函数式编程风格,需要编写Handler和Router

第一方式:基于注解

package com.suncht.sample.controller;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.suncht.sample.model.User;
import com.suncht.sample.service.UserRepository;

/**
 * WebFlux的第一种方式: annotation-based(注解)
 * @author suncht
 *
 */
@RestController
@RequestMapping("/users")
public class UserRestController {
	@Autowired
	private UserRepository userRepository;

	@GetMapping("/")
	public List getAllUser() {
		return userRepository.findAll();
	}
	
	@GetMapping("/{id}")
	public User getUser(@PathVariable Long id) {
		Optional user = userRepository.findById(id);
		return user.get();
	}

	@GetMapping("/add/{userName}/{age}")
	public Long getUser(@PathVariable("userName") String userName, @PathVariable("age") Integer age) {
		User user = new User();
		user.setUserName(userName);
		user.setAge(age);
		User result = userRepository.save(user);

		return result.getId();
	}

}

第二种方式:基于函数

package com.suncht.sample.handler;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import com.suncht.sample.model.User;
import com.suncht.sample.service.UserRepository;

import reactor.core.publisher.Mono;

/**
 * UserHandler
 * 用户操作的业务逻辑
 * @author suncht
 *
 */
@Service
public class UserHandler {
	@Autowired
	private UserRepository userRepository;
	

	public Mono handleGetUserById(ServerRequest request) {
		Long userId = Long.valueOf(request.pathVariable("id"));
		Optional user = userRepository.findById(userId);
		return ServerResponse.ok().body(Mono.just(user.get()), User.class)
				.switchIfEmpty(ServerResponse.notFound().build());
	}	
}

这是针对用户的handler,其实就是用户的业务逻辑操作而已。跟我们平时写的业务逻辑不同是:返回对象Mono和Flux。

关于Mono和Flux,请参考官网:https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/web-reactive.html#webflux-fn-handler-functions

package com.suncht.sample.route;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;

import com.suncht.sample.handler.UserHandler;

/**
 * WebFlux的第二种方式: functional(java方法)
 * Router路由,其实就是用代码实现http请求路径,类似于Python Web框架django,手动编写路由代码
 * @author suncht
 *
 */
@Configuration
public class UserRouter {
	@Bean
	public RouterFunction routerFunction(UserHandler userHandler) {
		return RouterFunctions
				.route(RequestPredicates.GET("/api/user/{id}")
				.and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUserById);
	}
}

这是用户http请求的路由规则,手动编写。较第一方法而言,比较麻烦。

4、测试

可以在浏览器中直接输入HTTP地址:http://127.0.0.1:8080/api/user/2 进行测试

也可以使用WebFlux中的WebClient测试,代码如下:

package com.suncht.sample.test;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import com.suncht.sample.model.User;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class WebTest {
	private WebClient client = null;
	@Before
	public void init() {
		client = WebClient.create("http://127.0.0.1:8080/");
	}
	
	@Test
	public void testUserById() {
		Mono result = client.get()// 请求方法,get,post...
				.uri("users/{id}", "1")// 请求相对地址以及参数
				.accept(MediaType.APPLICATION_JSON).retrieve()// 请求类型
				.bodyToMono(User.class);// 返回类型
		User user = result.block();
		System.out.println(user);
	}
	
	@Test
	public void testUserById2() {
		Mono result2 = client.get()// 请求方法,get,post...
				.uri("api/user/{id}", "1")// 请求相对地址以及参数
				.accept(MediaType.APPLICATION_JSON).retrieve()// 请求类型
				.bodyToMono(User.class);// 返回类型
		User user2 = result2.block();
		System.out.println(user2);
	}
	
	@Test
	public void testAllUsers() {
		Flux userFlux = client.get()
				.uri("users/")
				.accept(MediaType.APPLICATION_JSON).retrieve()// 请求类型
				.bodyToFlux(User.class);// 返回类型
		
		List users = userFlux.collectList().block();
		System.out.println(users);
	}
	
}

WebClient是远程调用Http请求的一种工具类、新思路

5、其他代码

User实体:

package com.suncht.sample.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "t_user")
public class User implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name="user_name", nullable=false, length=50)
	private String userName;
	
	@Column(name="age")
	private Integer age;

	public Long getId() {
		return id;
	}

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

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + ", age=" + age + "]";
	}
	
	

}

针对User的JPA CRUD操作:

package com.suncht.sample.service;

import org.springframework.data.jpa.repository.JpaRepository;

import com.suncht.sample.model.User;

public interface UserRepository extends JpaRepository {
}

6、整个工程项目结构预览

SpringBoot系列:基于SpringBoot2.0的WebFlux应用入门_第1张图片

GitHub代码: https://github.com/suncht/sun-test/tree/master/springboot2.webflux.test

你可能感兴趣的:(SpringBoot2.0,WebFlux,WebFlux,SpringBoot2.0)