Spring WebFlux基础教程--01

响应式堆栈Web框架Spring WebFlux已添加到Spring 5.0中。 它是完全无阻塞的,支持事件驱动,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。 在这个春季的webflux教程中,我们将通过一个hello world例子来学习反应式编程,webflux api等的基本概念。

1.反应式编程(Reactive Programming)

响应式编程是一种编程范例,可促进异步,非阻塞,事件驱动的数据处理方法。 响应式编程涉及将数据和事件建模为可观察的数据流,并实现数据处理例程以对这些流中的更改做出反应。

在深入研究响应式世界之前,首先要了解阻塞与非阻塞请求处理之间的区别。

1.1. 阻塞与非阻塞(异步)请求处理
1.1.1. 阻止请求处理

在传统的MVC应用程序中,当请求到达服务器时,将创建一个servlet线程。 它将请求委托给工作线程进行I / O操作(例如数据库访问等)。在工作线程忙时,servlet线程(请求线程)保持等待状态,因此被阻塞。 这也称为同步请求处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3NtWQEf-1571798491919)(https://howtodoinjava.com/wp-content/uploads/2019/02/Blocking-request-processing.png)]

由于服务器对请求线程有数量上的限制,因此限制了服务器在最大服务器负载下处理该数量请求的能力。 它可能会影响性能并限制服务器功能的完全利用。

1.1.2。 非阻塞请求处理

在非阻塞或异步请求处理中,没有线程处于等待状态。 通常只有一个请求线程接收该请求。

所有传入的请求都带有事件处理程序和回调信息。 请求线程将传入的请求委托给线程池(通常为少量线程),该线程池将请求委托给其处理函数,并立即开始处理来自请求线程的其他请求。

处理程序功能完成后,池中的线程之一将收集响应并将其传递给回调函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbQxTWsP-1571798491926)(https://howtodoinjava.com/wp-content/uploads/2019/02/Non-blocking-request-processing.png)]

线程的非阻塞性质有助于扩展应用程序的性能。 线程数量少意味着内存占用少,上下文切换也较少。

1.2.什么是反应式编程?

术语“反应性”是指围绕响应变化而构建的编程模型。 它是围绕发布者-订阅者模式(观察者模式)构建的。 在反应式编程中,我们请求资源并开始执行其他事情。 当数据可用时,我们会收到通知以及回调函数的数据通知。 在回调函数中,我们根据应用程序/用户需求处理响应。

要记住的重要一件事是背压。 在非阻塞代码中,控制事件的速率非常重要保护,可以保护消费者不被生产者的过度生产压垮。

响应式Web编程非常适合具有流数据的应用程序以及使用该数据并将其流传输给用户的客户端。 这对开发传统的CRUD应用程序不是很友好。 如果您想开发下一个具有海量数据的Facebook或Twitter,则响应式API可能正是您想要的。

2. 反应式流API(Reactive Streams API)

新的Reactive Streams API由Netflix,Pivotal,Lightbend,RedHat,Twitter和Oracle等工程师创建,现已成为Java 9的一部分。它定义了四个接口:

  • Publisher: 生产数据,供订阅者消费,只有一个方法 subscribe(Subscriber)

    It has a single method:

    public interface Publisher<T>
    {
        public void subscribe(Subscriber<? super T> s);
    }
    
  • Subscriber: 订阅生产者,接收数据(通过 onNext(T) 方法)、错误信息( onError(Throwable) 方法)、没有更多数据的信号( onComplete() ),在这些动作之前,publisher 会调用 onSubscription(Subscription)subscribe(Subscriber)

    It has four methods to handle various kind of responses received.

    public interface Subscriber<T>
    {
        public void onSubscribe(Subscription s);
        public void onNext(T t);
        public void onError(Throwable t);
        public void onComplete();
    }
    
  • Subscription: 是发布者和订阅者之间的连接,订阅者会通过它来请求更多的数据( request(long) ),或者中断连接( cancel() )。

    It has a single method:

    public interface Subscription<T>
    {
        public void request(long n);
        public void cancel();
    }
    
  • Processor: 表示一个处理的阶段,包括订阅者Subscriber和发布者Publisher和两者之间的协议

    public interface Processor<T, R> extends Subscriber<T>, Publisher<R>
    {
    }
    

    Two popular implementations of reactive streams are RxJava (https://github.com/ReactiveX/RxJava) and Project Reactor (https://projectreactor.io/).

3. What is Spring WebFlux ?

Spring WebFlux是[Spring MVC](https://howtodoinjava.com/spring-mvc-tutorial/)的并行版本,并支持完全无阻塞的反应流。 它支持背压概念,并使用“ Netty”作为内置服务器来运行反应性应用程序。 如果您熟悉Spring MVC编程风格,则也可以轻松地使用webflux。

Spring Webflux使用项目反应器作为反应库。 Reactor是Reactive Streams库,因此,它的所有运算符都支持无阻塞背压。 它是与Spring紧密合作开发的。

Spring WebFlux大量使用两个发布者:

  • Mono
    一个实体包装(0,1)
    Mono<String> mono = Mono.just(``"Alex"``);
    Mono<String> mono = Mono.empty();
    
  • Flux
    一种集合(0,n)
    Flux<String> flux = Flux.just("A", "B", "C");
    Flux<String> flux = Flux.fromArray(new String[]{"A", "B", "C"});
    Flux<String> flux = Flux.fromIterable(Arrays.asList("A", "B", "C"));
     
    //To subscribe call method
     
    flux.subscribe();
    

在Spring WebFlux中,我们称为反应式API /函数,它们返回monos和fluxes,而您的控制器将返回monos和助fluxes。 当您调用返回monos或fluxes的API时,它将立即返回。 当它们可用时,函数调用的结果将通过 mono或者 flux传递给您。

要构建真正的非阻塞应用程序,我们必须致力于将其所有组件创建/使用为非阻塞程序,即客户端,控制器,中间服务甚至数据库。 如果其中之一是阻塞的请求,那么我们构建的应用程序将会失败。

4. 程序

这个程序将采用Spring boot 2,为了使它完全非阻塞,将使用mongodb作为背压数据库。

4.1. Maven dependencies

Include spring-boot-starter-webflux, spring-boot-starter-data-mongodb-reactive, spring-boot-starter-test and reactor-test dependencies.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
 
    <groupId>com.howtodoinjava</groupId>
    <artifactId>spring-webflux-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>spring-webflux-demo</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
 
</project>
4.2. Configurations

Webflux Configuration

WebFluxConfig.java

import org.springframework.context.annotation.Configuration;
 
@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer
{  
}

MongoDb Configuration

MongoConfig.java

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
 
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
 
@Configuration
@EnableReactiveMongoRepositories(basePackages = "com.howtodoinjava.demo.dao")
public class MongoConfig extends AbstractReactiveMongoConfiguration
{  
    @Value("${port}")
    private String port;
     
    @Value("${dbname}")
    private String dbName;
 
    @Override
    public MongoClient reactiveMongoClient() {
        return MongoClients.create();
    }
 
    @Override
    protected String getDatabaseName() {
        return dbName;
    }
 
    @Bean
    public ReactiveMongoTemplate reactiveMongoTemplate() {
        return new ReactiveMongoTemplate(reactiveMongoClient(), getDatabaseName());
    }
}

Application Configuration

AppConfig.java

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
 
@Configuration
public class AppConfig
{
    @Bean
    public static PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer()
    {
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        ppc.setLocation(new ClassPathResource("application.properties"));
        ppc.setIgnoreUnresolvablePlaceholders(true);
        return ppc;
    }
}

Properties file

application.properties

port=27017
dbname=testdb

Logging configuration

logback.xml

<configuration>
 
    <appender name="STDOUT"
        class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
            </pattern>
        </encoder>
    </appender>
 
    <logger name="org.springframework" level="DEBUG"
        additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>
 
    <root level="ERROR">
        <appender-ref ref="STDOUT" />
    </root>
 
</configuration>

Spring boot application

WebfluxFunctionalApp.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class WebfluxFunctionalApp {
 
    public static void main(String[] args) {
        SpringApplication.run(WebfluxFunctionalApp.class, args);
    }
}
4.3. REST Controller

EmployeeController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
 
import com.howtodoinjava.demo.model.Employee;
import com.howtodoinjava.demo.service.EmployeeService;
 
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
 
@RestController
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;
 
    @RequestMapping(value = { "/create", "/" }, method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    public void create(@RequestBody Employee e) {
        employeeService.create(e);
    }
 
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<Mono<Employee>> findById(@PathVariable("id") Integer id) {
        Mono<Employee> e = employeeService.findById(id);
        HttpStatus status = e != null ? HttpStatus.OK : HttpStatus.NOT_FOUND;
        return new ResponseEntity<Mono<Employee>>(e, status);
    }
 
    @RequestMapping(value = "/name/{name}", method = RequestMethod.GET)
    @ResponseBody
    public Flux<Employee> findByName(@PathVariable("name") String name) {
        return employeeService.findByName(name);
    }
 
    @RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @ResponseBody
    public Flux<Employee> findAll() {
        Flux<Employee> emps = employeeService.findAll();
        return emps;
    }
 
    @RequestMapping(value = "/update", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public Mono<Employee> update(@RequestBody Employee e) {
        return employeeService.update(e);
    }
 
    @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    public void delete(@PathVariable("id") Integer id) {
        employeeService.delete(id).subscribe();
    }
 
}
4.4. Service classes

IEmployeeService.java

import com.howtodoinjava.demo.model.Employee;
 
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
 
public interface IEmployeeService
{
    void create(Employee e);
     
    Mono<Employee> findById(Integer id);
 
    Flux<Employee> findByName(String name);
 
    Flux<Employee> findAll();
 
    Mono<Employee> update(Employee e);
 
    Mono<Void> delete(Integer id);
}

EmployeeService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.howtodoinjava.demo.dao.EmployeeRepository;
import com.howtodoinjava.demo.model.Employee;
 
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
 
@Service
public class EmployeeService implements IEmployeeService {
     
    @Autowired
    EmployeeRepository employeeRepo;
 
    public void create(Employee e) {
        employeeRepo.save(e).subscribe();
    }
 
    public Mono<Employee> findById(Integer id) {
        return employeeRepo.findById(id);
    }
 
    public Flux<Employee> findByName(String name) {
        return employeeRepo.findByName(name);
    }
 
    public Flux<Employee> findAll() {
        return employeeRepo.findAll();
    }
 
    public Mono<Employee> update(Employee e) {
        return employeeRepo.save(e);
    }
 
    public Mono<Void> delete(Integer id) {
        return employeeRepo.deleteById(id);
    }
 
}
4.5. DAO repository

EmployeeRepository.java

import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
 
import com.howtodoinjava.demo.model.Employee;
 
import reactor.core.publisher.Flux;
 
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, Integer> {
    @Query("{ 'name': ?0 }")
    Flux<Employee> findByName(final String name);
}
4.6. Model

Employee.java

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
 
@Scope(scopeName = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Document
public class Employee {
 
    @Id
    int id;
    String name;
    long salary;
 
    //Getters and setters
 
    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
    }
}
5. Demo

Start the application and check request and responses.

  • HTTP POST http://localhost:8080/create

API Request 1

{
    "id":1,
    "name":"user_1",
    "salary":101
}
  • HTTP PUT http://localhost:8080/update

API Request 2

{
    "id":2,
    "name":"user_2",
    "salary":102
}
  • HTTP GET http://localhost:8080/

API Request

{
    "id":2,
    "name":"user_2",
    "salary":103
}
  • HTTP GET http://localhost:8080/

API Response

data:{"id":1,"name":"user_1","salary":101}
 
data:{"id":2,"name":"user_2","salary":102}

Spring WebFlux Demo

使用postman进行测试。

Sourcecode Download

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值