Java Web 开发技术的演进:从 Servlet、Spring MVC 到 WebFlux 及其竞品分析

前言

随着互联网技术的快速发展,Web 应用程序在处理海量用户访问和大数据时面临着巨大的挑战。在这个过程中,Java Web 开发技术经历了从 Servlet 到 Spring MVC 再到 WebFlux 的演变。在这篇文章中,我们将探讨这三个技术的发展历程、痛点及解决方案,以及它们如何帮助开发者在不断变化的互联网世界中构建更高效、可扩展和响应式的 Web 应用程序。让我们从 Servlet 的诞生开始,追溯 Java Web 开发技术的发展脉络。

从 Servlet 到 Spring MVC 再到 WebFlux,这个技术的升级迭代过程主要反映了 Web 应用程序的发展和性能需求。下面我将详细介绍每个阶段的技术及其痛点解决方案。

Servlet:

Servlet 是 Java Web 应用程序的基础,它提供了一种用于处理 HTTP 请求的标准 Java 接口。Servlet 是一个面向对象的编程模型,每个 Servlet 类处理特定的请求。在早期的 Web 开发中,Servlet 是一种主流的解决方案。

痛点:

  1. 开发者需要手动处理请求和响应对象,编写大量样板代码。
  2. 不易于管理和组织大型项目的代码结构。

Spring MVC:

为了解决 Servlet 的痛点,Spring MVC 出现了。它是一个基于 Java 的 Web 应用程序框架,可以认为是servlet框架,提供了一套简化 Web 开发的工具和约定。通过使用 Spring MVC,开发者可以更加专注于业务逻辑,而不是底层细节。

改进:

  1. 提供了基于注解的编程模型,降低了开发复杂度。
  2. 提供了强大的依赖注入和控制反转功能,简化了组件之间的解耦。
  3. 支持灵活的 URL 映射和视图解析,方便开发者进行 URL 和视图的管理。

痛点:

Spring MVC是基于阻塞 I/O 的,这意味着在处理高并发请求时,可能会遇到性能瓶颈。 随着响应式编程模型的兴起,传统的 Spring MVC 不再满足现代 Web 应用程序的性能要求。

WebFlux:

为了解决 Spring MVC 中的性能瓶颈,Spring Framework 5.0 推出了 WebFlux。WebFlux 是基于响应式编程模型的,它提供了一种非阻塞、事件驱动的方式来构建 Web 应用程序。

改进:

  1. 支持异步和非阻塞 I/O,能够在高并发场景下更好地利用系统资源。
  2. 提供了两种编程模型:基于注解的编程模型和基于函数的编程模型。
  3. 提供了 WebClient,一个响应式的 HTTP 客户端,可以替代 RestTemplate。
  4. 支持与响应式数据存储(如 MongoDB、Cassandra、Redis 等)进行集成,实现端到端的响应式编程。

为什么说SpringMVC基于阻塞 I/O 就意味着在处理高并发请求时,可能会遇到性能瓶颈?

Spring MVC 基于阻塞 I/O 的原因在于其使用的 Servlet 技术,默认采用一个线程处理一个请求的模型。在这种模型中,当一个请求到达服务器时,服务器会为这个请求分配一个线程来处理。在处理过程中,线程会阻塞等待 I/O 操作(如数据库查询、文件读写等)完成,才能继续执行后续操作。在 I/O 操作期间,线程会处于等待状态,无法处理其他请求。

当面临高并发请求时,阻塞 I/O 模型可能会遇到性能瓶颈,原因如下:

  1. 线程资源有限:服务器的线程资源是有限的。当并发请求量很大时,服务器可能会耗尽可用线程,导致新请求无法得到及时处理。同时,由于每个线程都会消耗一定的内存和 CPU 资源,大量线程可能会导致系统资源紧张,进一步降低性能。

  2. 线程切换开销:当大量请求同时到达服务器时,操作系统需要在不同线程之间进行切换,以保证每个请求都能得到处理。线程切换会消耗 CPU 资源,降低系统的整体效率。

  3. I/O 操作效率低:阻塞 I/O 模型中,线程在等待 I/O 操作完成期间无法处理其他请求。这意味着线程在大部分时间里可能处于等待状态,无法充分利用 CPU 资源。

为了解决这些性能瓶颈,非阻塞 I/O 和响应式编程模型应运而生。在非阻塞 I/O 模型中,线程在发起 I/O 操作后不会等待其完成,而是立即返回,处理其他请求。当 I/O 操作完成时,线程会通过回调或事件的方式继续处理。这种模型充分利用了线程资源,提高了系统的吞吐量和响应能力。WebFlux 就是基于这种非阻塞 I/O 和响应式编程模型的 Web 框架。

WebFlux 实现非阻塞和异步的原理是什么?

WebFlux 是一个基于响应式编程模型的框架,它是 Spring Framework 5.0 中引入的一个新特性。WebFlux 提供了一种用于构建响应式 Web 应用程序的非阻塞、事件驱动的方式。它支持异步的处理流程,能够更好地利用系统资源,从而在高并发场景下提高应用程序的性能和响应能力。

WebFlux 实现非阻塞和异步的原理主要依赖于以下几个关键技术和概念:

  1. 响应式编程模型:响应式编程模型允许开发者以声明式的方式处理数据流和事件驱动的操作。在响应式编程中,开发者可以组合、转换和订阅数据流,而无需关心底层的线程和并发问题。WebFlux 使用 Reactor 库实现响应式编程,Reactor 是一个基于 Java 的响应式编程库,实现了 Reactive Streams 规范。

  2. Reactive Streams:Reactive Streams 是一套处理异步数据流的标准接口,包括 Publisher(发布者)、Subscriber(订阅者)、Subscription(订阅)和 Processor(处理器)。WebFlux 使用 Reactive Streams 提供了一套非阻塞的流式数据处理机制,可以在高并发场景下提高系统的吞吐量和响应能力。

  3. 非阻塞 I/O:WebFlux 使用 Netty 作为底层的网络通信框架,Netty 支持非阻塞 I/O 操作。在非阻塞 I/O 模型中,当发起 I/O 操作时,线程不会阻塞等待操作完成,而是立即返回并继续处理其他任务。当 I/O 操作完成时,线程会通过事件或回调的方式得到通知,从而继续处理后续操作。这种模式充分利用了线程资源,提高了系统的吞吐量和响应能力。

  4. 异步编程:WebFlux 的异步编程主要体现在其对异步 I/O 操作和事件驱动的支持。通过使用响应式编程模型和非阻塞 I/O,WebFlux 能够实现高效的异步请求处理。在这种模型中,线程可以在等待 I/O 操作期间处理其他请求,从而充分利用系统资源。

综上所述,WebFlux 实现非阻塞和异步的原理主要依赖于响应式编程模型、Reactive Streams 规范、非阻塞 I/O 技术以及异步编程。这些技术和概念相互协作,使得 WebFlux 能够在高并发场景下提供高性能和响应能力。

一个使用webflux开发的Demo

以下是一个使用 Spring WebFlux 开发的简单示例,演示了创建一个基本的 RESTful API,用于管理一个简单的书籍清单。

准备工作:

在 Spring Boot 项目中集成 Spring WebFlux 非常简单。以下是集成 WebFlux 的步骤:

  1. 添加依赖: 首先,您需要在项目的 pom.xml(如果您使用的是 Maven)或 build.gradle(如果您使用的是 Gradle)中添加 Spring WebFlux 依赖。以下是 Maven 和 Gradle 的依赖配置示例:

Maven:

<dependencies>
    <!-- Spring Boot WebFlux -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>
复制代码

Gradle:

dependencies {
    // Spring Boot WebFlux
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
复制代码

添加 spring-boot-starter-webflux 依赖后,Spring Boot 会自动配置 WebFlux 相关的组件,并启用响应式编程和非阻塞 I/O 功能。

  1. 配置: 通常情况下,Spring Boot 会自动配置 WebFlux 以及与之相关的组件,因此您无需进行额外的配置。但是,如果需要,您可以在项目的 application.properties 或 application.yml 文件中进行自定义配置。以下是一些常见的 WebFlux 配置项:
  • server.port:设置应用程序的 HTTP 端口,默认值为 8080。
  • spring.codec.max-in-memory-size:设置解码器的最大内存大小,默认值为 2MB。
  • spring.webflux.base-path:设置 WebFlux 应用程序的基本路径。 例如,在 application.properties 文件中,您可以设置 HTTP 端口:
server.port=8081
复制代码

开始编码:

1、首先,创建一个表示书籍的 Book 类:

public class Book {
    private String id;
    private String title;
    private String author;

    // 构造函数、getters 和 setters 省略
}

复制代码

2、接下来,创建一个 BookRepository 接口,用于存储和检索书籍对象:

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

public interface BookRepository {
    Flux<Book> findAll();
    Mono<Book> findById(String id);
    Mono<Book> save(Book book);
    Mono<Void> deleteById(String id);
}

复制代码

3、现在,创建一个实现 BookRepository 接口的 InMemoryBookRepository 类:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class InMemoryBookRepository implements BookRepository {
    private final Map<String, Book> books = new ConcurrentHashMap<>();

    @Override
    public Flux<Book> findAll() {
        return Flux.fromIterable(books.values());
    }

    @Override
    public Mono<Book> findById(String id) {
        return Mono.justOrEmpty(books.get(id));
    }

    @Override
    public Mono<Book> save(Book book) {
        books.put(book.getId(), book);
        return Mono.just(book);
    }

    @Override
    public Mono<Void> deleteById(String id) {
        books.remove(id);
        return Mono.empty();
    }
}

复制代码

4、接下来,创建一个 BookHandler 类,用于处理书籍相关的 HTTP 请求:

import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

public class BookHandler {
    private final BookRepository bookRepository;

    public BookHandler(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Mono<ServerResponse> getAllBooks(ServerRequest request) {
        return ServerResponse.ok().body(bookRepository.findAll(), Book.class);
    }

    public Mono<ServerResponse> getBookById(ServerRequest request) {
        return bookRepository.findById(request.pathVariable("id"))
                .flatMap(book -> ServerResponse.ok().body(BodyInserters.fromValue(book)))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> saveBook(ServerRequest request) {
        Mono<Book> bookMono = request.bodyToMono(Book.class);
        return bookMono.flatMap(book ->
                ServerResponse.status(HttpStatus.CREATED).body(bookRepository.save(book), Book.class));
    }

    public Mono<ServerResponse> deleteBookById(ServerRequest request) {
        return bookRepository.deleteById(request.pathVariable("id"))
                .then(ServerResponse.noContent().build());
    }
}

复制代码

5、最后,创建一个 BookRouter 类,用于定义书籍相关的 HTTP 路由规则:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class BookRouter {

    @Bean
    public RouterFunction<ServerResponse> route(BookHandler bookHandler) {
        return RouterFunctions
                .route(GET("/books").and(accept(MediaType.APPLICATION_JSON)), bookHandler::getAllBooks)
                .andRoute(GET("/books/{id}")
                    .and(accept(MediaType.APPLICATION_JSON)), bookHandler::getBookById)
                .andRoute(POST("/books").and(accept(MediaType.APPLICATION_JSON)), bookHandler::saveBook)
                .andRoute(DELETE("/books/{id}").and(accept(MediaType.APPLICATION_JSON)), bookHandler::deleteBookById);
    }
}

复制代码

6、现在,您需要配置一个主应用程序类来启动 Spring Boot 应用程序:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class WebFluxDemoApplication {

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

    @Bean
    public BookRepository bookRepository() {
        return new InMemoryBookRepository();
    }

    @Bean
    public BookHandler bookHandler(BookRepository bookRepository) {
        return new BookHandler(bookRepository);
    }
}
复制代码

这个示例演示了如何使用 Spring WebFlux 创建一个简单的 RESTful API。BookHandler 类负责处理 HTTP 请求,BookRouter 类负责定义路由规则,BookRepository 接口负责存储和检索书籍对象。请注意,这个示例使用了响应式编程和非阻塞 I/O,使得应用程序在高并发场景下能够更好地利用系统资源。

拓展与思考

WebFlux 更适合什么类型的项目:

  1. 高并发场景:WebFlux 使用非阻塞 I/O 和响应式编程,使得在高并发场景下能够更好地利用系统资源。对于访问量较大、需要支持大量并发请求的 Web 应用程序,WebFlux 是一个很好的选择。

  2. 微服务架构:在微服务架构中,服务之间的调用和通信非常重要。WebFlux 支持异步和非阻塞的请求处理,可以有效地减小服务间通信的延迟,提高整个系统的响应能力。

  3. 数据流处理:对于需要实时处理数据流的应用程序,WebFlux 提供了响应式编程模型,使得开发者能够更方便地组合、转换和订阅数据流,提高数据处理效率。

WebFlux 存在的一些缺点:

  1. 学习曲线:对于习惯于传统阻塞式编程的开发者来说,响应式编程和非阻塞 I/O 模型可能需要一定时间去学习和适应。尤其是在错误处理、调试和性能优化方面,WebFlux 可能比传统的 Spring MVC 更具挑战性。

  2. 第三方库支持:虽然 WebFlux 已经相对成熟,但与传统的 Spring MVC 相比,一些第三方库可能还没有完全适配响应式编程和非阻塞 I/O 模型。在使用这些库时,可能需要额外的工作来确保与 WebFlux 的兼容性。

  3. 适用性:对于一些低并发、简单的 CRUD 类型的应用程序,使用 WebFlux 可能并不会带来显著的性能提升。在这种情况下,采用更熟悉的 Spring MVC 可能是一个更合适的选择。

总之,WebFlux 更适合处理高并发、数据流处理和微服务架构的场景,但对于一些简单的应用程序,传统的 Spring MVC 可能仍然是一个不错的选择。在决定使用 WebFlux 时,开发者需要权衡项目的需求、团队的技能和第三方库的支持情况。

webFlux 的同类竞品技术对比

在 Java 生态系统中,有一些与 WebFlux 类似的技术,它们都支持响应式编程和非阻塞 I/O。以下是一些 WebFlux 的同类竞品技术:

  1. Vert.x:Vert.x 是一个用于构建响应式应用程序的工具包。它提供了一套简单、可扩展的 API,支持异步和非阻塞 I/O。Vert.x 可以与多种语言(如 Java、JavaScript、Groovy、Ruby、Kotlin 等)一起使用,具有良好的性能和可扩展性。

  2. Akka HTTP:Akka HTTP 是基于 Akka Actors 和 Akka Streams 构建的一个高性能、非阻塞 HTTP 服务器和客户端库。它支持响应式流处理,可以用于构建高并发、低延迟的分布式系统。Akka HTTP 主要使用 Scala 语言编写,但也支持 Java。

  3. Play Framework:Play 是一个支持 Java 和 Scala 的高性能 Web 开发框架。它提供了一套简洁、优雅的 API,支持非阻塞 I/O 和响应式编程。Play 使用 Akka 作为底层基础设施,具有良好的性能和可扩展性。

  4. Micronaut:Micronaut 是一个用于构建微服务和 Serverless 应用程序的现代框架。它提供了响应式编程支持,并与多种响应式库(如 RxJava、Reactor 等)集成。Micronaut 旨在提供高性能、低内存占用的应用程序。

  5. Quarkus:Quarkus 是一个用于构建云原生、微服务和 Serverless 应用程序的全栈框架。它提供了响应式编程支持,并与 Vert.x 集成。Quarkus 优化了启动时间和内存占用,适用于构建高性能、资源高效的应用程序。

技术语言支持特点
Spring WebFluxJava响应式编程,基于 Reactor 库,与 Spring 生态系统紧密集成
Vert.xJava, JavaScript, Groovy, Ruby, Kotlin等异步、非阻塞 I/O,高性能,多语言支持,简洁易用的 API
Akka HTTPScala, Java高性能,基于 Akka Actors 和 Akka Streams,响应式流处理
Play FrameworkJava, Scala高性能,支持非阻塞 I/O 和响应式编程,基于 Akka,优雅的 API
MicronautJava, Groovy, Kotlin响应式编程,高性能,低内存占用,适用于微服务和 Serverless 应用
QuarkusJava, Kotlin响应式编程,高性能,优化启动时间和内存占用,适用于云原生应用

这些技术都有各自的优势和特点。在选择适合项目的技术时,需要考虑性能、可扩展性、易用性、生态系统以及团队的技能和经验等因素。

小结

综上所述,从 Servlet 到 Spring MVC 再到 WebFlux,这个技术升级迭代过程主要体现了对 Web 应用程序开发的简化、性能优化和异步非阻塞编程模型的需求。每个新技术的出现都旨在解决上一个技术的痛点,并为开发者提供更好的工具和实践。

Servlet 的痛点在于开发者需要手动处理请求和响应对象,编写大量样板代码,以及难以管理和组织大型项目的代码结构。Spring MVC 解决了这些问题,为开发者提供了一套简化 Web 开发的工具和约定,同时引入了依赖注入和控制反转功能,降低了开发复杂度。

然而,随着 Web 应用程序的规模和性能需求不断提高,Spring MVC 基于阻塞 I/O 的架构逐渐暴露出性能瓶颈。为了解决这个问题,WebFlux 应运而生。WebFlux 基于响应式编程模型,提供了非阻塞、事件驱动的方式来构建 Web 应用程序。它支持异步和非阻塞 I/O,能够在高并发场景下更好地利用系统资源。同时,WebFlux 提供了与响应式数据存储的集成,实现端到端的响应式编程。

总之,从 Servlet 到 Spring MVC 再到 WebFlux,技术的升级迭代过程反映了 Web 应用程序的发展和性能需求。每个新技术的出现都为开发者提供了更好的工具和实践,以满足不断变化的应用场景。

作者:G探险者
链接:https://juejin.cn/post/7220793382020939831

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值