spring5.0 函数式web框架 webflux

spring5.0 函数式web框架 webflux


2个demo的源码在GitHub上 链接点这里


  • spring5.0 函数式web框架 webflux
    • 函数式web框架 webFlux
        • 什么是函数式
        • 什么是响应式
        • 什么是webFlux
        • webFlux 简单实例
        • 更简单的使用webFlux (springboot)
    • spring5.0 的其他新特性介绍
        • JDK 8+和Java EE7+以上版本
        • 核心特性
        • 测试方面的改进
        • spring对kotlin的支持
        • spring Data

函数式web框架 webFlux

什么是函数式

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。

特性
- 惰性计算
- 函数是“第一等公民”
- 只使用表达式而不使用语句
- 没有副作用

什么是响应式

Spring官方文档: In plain terms reactive programming is about non-blocking applications that are asynchronous and event-driven and require a small number of threads to scale vertically (i.e. within the JVM) rather than horizontally (i.e. through clustering).

简单来说响应式编程是关于异步的事件驱动的需要少量线程的垂直扩展而非水平扩展的无阻塞应用

A key aspect of reactive applications is the concept of backpressure which is a mechanism to ensure producers don’t overwhelm consumers. For example in a pipeline of reactive components extending from the database to the HTTP response when the HTTP connection is too slow the data repository can also slow down or stop completely until network capacity frees up.

Reactive programming also leads to a major shift from imperative to declarative async composition of logic. It is comparable to writing blocking code vs using the CompletableFuture from Java 8 to compose follow-up actions via lambda expressions.

For a longer introduction check the blog series “Notes on Reactive Programming” by Dave Syer.

百度百科: 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

4个关键词:
- Responsive: 可响应的。要求系统尽可能做到在任何时候都能及时响应。
- Resilient: 可恢复的。要求系统即使出错了,也能保持可响应性。
- Elastic: 可伸缩的。要求系统在各种负载下都能保持可响应性。
- Message Driven:消息驱动的。要求系统通过异步消息连接各个组件。

Excel表格就是一个响应式的(演示示例)

backpressure: backpressure(背压)概念的理解

什么是webFlux


左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router FunctionsWebFluxReactive Streams三个新组件。

  • Router Functions: 对标@Controller,@RequestMapping等标准的Spring MVC注解,提供一套函数式风格的API,用于创建Router,Handler和Filter。
  • WebFlux: 核心组件,协调上下游各个组件提供响应式编程支持。
  • Reactive Streams: 一种支持背压(Backpressure)的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor

在Web容器的选择上,Spring WebFlux既支持像Tomcat,Jetty这样的的传统容器(前提是支持Servlet 3.1 Non-Blocking IO API),又支持像Netty,Undertow那样的异步容器。不管是何种容器,Spring WebFlux都会将其输入输出流适配成Flux格式,以便进行统一处理。

值得一提的是,除了新的Router Functions接口,Spring WebFlux同时支持使用老的Spring MVC注解声明Reactive Controller。和传统的MVC Controller不同,Reactive Controller操作的是非阻塞的ServerHttpRequestServerHttpResponse,而不再是Spring MVC里的HttpServletRequestHttpServletResponse

 @GetMapping("/reactive/restaurants")
    public Flux findAll() {
        return restaurantRepository.findAll();
    }

可以看到主要变化就是在 返回的类型上Flux

Flux和Mono 是 Reactor 中的流数据类型,其中Flux会发送多次,Mono会发送0次或一次

使用webflux需要具备的基础是Reactive programming 的理解。
Reactor 的基础 和 熟练的java8 lambda使用

webFlux 简单实例

Server类

public class Server {

    public static final String HOST = "localhost";

    public static final int PORT = 8080;

    private Routes routes = new Routes(); // ③

    public static void main(String[] args) throws Exception {
        Server server = new Server();
        server.startReactorServer(); // ①
//      server.startTomcatServer(); // ②

        System.out.println("Press ENTER to exit.");
        System.in.read();
    }
}

①:Netty

    public void startReactorServer() throws InterruptedException {
        RouterFunction route = routes.rout(); // ④
        HttpHandler httpHandler = toHttpHandler(route);

        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        HttpServer server = HttpServer.create(HOST, PORT);
        server.newHandler(adapter).block();
    }

②:Tomcat

public void startTomcatServer() throws LifecycleException {
        RouterFunction route = routes.rout();
        HttpHandler httpHandler = toHttpHandler(route);

        Tomcat tomcatServer = new Tomcat();
        tomcatServer.setHostname(HOST);
        tomcatServer.setPort(PORT);
        Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
        ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
        Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
        rootContext.addServletMapping("/", "httpHandlerServlet");
        tomcatServer.start();
    }

③:Route

public class Routes {

    public RouterFunction rout() {
        CustomerRepository repository = new CustomerRepositoryImpl();
        CustomerHandler handler = new CustomerHandler(repository);

        return nest(
                path("/person"),
                nest(accept(APPLICATION_JSON),
                        route(GET("/{id}"), handler::getPerson)
                                .andRoute(method(HttpMethod.GET), handler::listPeople)
                ).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson)
        ).andNest(
                path("/product"),
                route(path("/"), serverRequest ->
                        ServerResponse.ok().contentType(APPLICATION_JSON)
                                .body(fromPublisher(Flux.just(new Product(1, "PC", 1000.00)), Product.class))
                )
        );
    }
}

route中设置uri地址对应的资源,可以创建多个route

Route创建的方法

  • 返回类型为:RouterFunction
  • 通过RouterFunctions来创建Route

    1. RouterFunctions.route(RequestPredicate predicate, HandlerFunction handlerFunction)
      其中 RequestPredicate 是一个函数式接口,接受一个’T’类型返回一个布尔类型。
      大多数情况,我们都可以通过RequestPredicates类的静态方法来创建这个对象,比如:

      public static RequestPredicate GET(String pattern) {
              return method(HttpMethod.GET).and(path(pattern));
          }

      HandlerFunction 也是一个函数式接口,接受一个ServerResponse的子类返回Mono,可以把这个对象当作实际处理逻辑的部分。

    2. public static RouterFunction nest(RequestPredicate predicate, RouterFunction routerFunction)

      nest 方法同样有一个 RequestPredicate 但是第二个参数变成了 RouterFunction routerFunction,这个方法的作用是为了分支uri。

      在RESTFUL风格的接口中,会有目录层级的概念,这里的nest方法就可以理解为层级的关系,比如上述的”/person”后面的”/{id}” 就是相当于 “/person/{id}”

  • public static RequestPredicate accept(MediaType... mediaTypes) accept可以用来设定接收或者返回的请求类型
    这里的MediaType 和 Jersey里我们用的大致相同
  • 连接方法:and(RouterFunction),andOther(RouterFunction),nest(),andNest(),andRoute()等,可以进行组合
  • Filter

    通过RouterFunction的 filter方法可以添加过滤器,next.handle(request);的前后可以添加一些需要做的事

.filter((request, next) -> {
    System.out.println("Before handler invocation: " + request.path());
    Response response = next.handle(request);
    Object body = response.body();
    System.out.println("After handler invocation: " + body);
    return response;
  })

Handler

  • handler差不多可以理解为SpringMvc 中的service+controller,当然也可以在拆分出service,handler只作为controller
  • uri中的参数可以通过 int personId = Integer.valueOf(request.pathVariable("id")); 来获取
  • 返回的类型 Mono

    可以通过Spring 提供的 ServerResponse 来创建

    1. 状态码方法,可以使用现成的,比如成功的状态码:

      static ServerResponse.BodyBuilder ok() {
              return status(HttpStatus.OK);
          }

      也可以自己定义:

      static ServerResponse.BodyBuilder ok() {
              return status(HttpStatus.OK);
          }
    2. contentType(MediaType var1) 返回的内容类型 同样是 MediaType类型
    3. 最后就是返回的内容:

         <T, P extends Publisher<T>> Mono<ServerResponse> body(P var1, Class<T> var2);
      
         <T, P extends Publisher<T>> Mono<ServerResponse> body(P var1, ParameterizedTypeReference<T> var2);
      
         Mono<ServerResponse> syncBody(Object var1);
      
         Mono<ServerResponse> body(BodyInsertersuper ServerHttpResponse> var1);
      
         Mono<ServerResponse> render(String var1, Object... var2);
      
         Mono<ServerResponse> render(String var1, Map<String, ?> var2);

      一般常用的通过 body方法来放入返回的内容

      1. > Mono body(P var1, Class var2);

        Flux people = this.repository.all();
                return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Customer.class);
      2. 使用BodyInserters的构建方法 fromObject(); fromPublisher();等

      3. 使用 build 方法

        Mono<ServerResponse> build(Publisher<Void> var1);
    4. 控制页面跳转 permanentRedirect() 或 temporaryRedirect() 这2个方法与ok()都返回的是 BodyBuilder,返回页面的内容可以通过body方法来输出

[演示]

更简单的使用webFlux (springboot)

通过springboot来使用webFlux 会更简单:

[springboot的版本必须是2.0以上]

<parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.0.M4version>
        <relativePath/> 
    parent>

webFlux 的依赖包

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webfluxartifactId>
    dependency>

入口类和普通的springboot项目一样

@SpringBootApplication
public class WebfluxApplication {

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

创建Route:

@Configuration
public class WebRoutes {

    @Bean
    public RouterFunction route() {
        return RouterFunctions.route(
                GET("/"), request -> {
                    Mono user = Mono.just(new User("1", "a"));
                    return ok().body(fromPublisher(user, User.class));
                });
    }
}

此时我们的项目已经可以运行了,启动时可以看到 已经创建了地址映射 ‘/’

Mapped (GET && /) -> com.lin.webflux.web.WebRoutes$$Lambda$190/874765360@44a44d5d

创建多个Route 只需要上述的相同操作,比如每个模块一个route bean

[例子]

==RESTFUL API 和 eventStram 和 websocket编程==

spring5.0 的其他新特性介绍

JDK 8+和Java EE7+以上版本

  • 整个框架的代码基于java8
  • 通过使用泛型等特性提高可读性
  • 对java8提高直接的代码支撑
  • 运行时兼容JDK9
  • Java EE 7API需要Spring相关的模块支持
  • 运行时兼容Java EE8 API
  • 取消部分的包,类和方法
  • 包 beans.factory.access
  • 包 dbc.support.nativejdbc
  • 从spring-aspects 模块移除了包mock.staicmock,不在提AnnotationDrivenStaticEntityMockingControl支持
  • 许多不建议使用的类和方法在代码库中删除

核心特性

  • 支持候选组件索引(也可以支持环境变量扫描)
  • 支持@Nullable注解
  • 函数式风格GenericApplicationContext/AnnotationConfigApplicationContext
  • 基本支持bean API注册
  • 在接口层面使用CGLIB动态代理的时候,提供事物,缓存,异步注解检测
  • XML配置作用域流式
  • Spring WebMVC
  • 全部的Servlet 3.1 签名支持在Spring-provied Filter实现
  • 在Spring MVC Controller方法里支持Servlet4.0 PushBuilder参数
  • 多个不可变对象的数据绑定(Kotlin/Lombok/@ConstructorPorties)
  • 支持jackson2.9
  • 支持JSON绑定API
  • 支持protobuf3
  • 支持Reactor3.1 Flux和Mono

测试方面的改进

  • 完成了对JUnit 5’s Juptier编程和拓展模块在Spring TestContext框架
  • SpringExtension:是JUnit多个可拓展API的一个实现,提供了对现存Spring TestContext Framework的支持,使用@ExtendWith(SpringExtension.class)注解引用。
  • @SpringJunitConfig:一个复合注解
  • @ExtendWith(SpringExtension.class) 来源于Junit Jupit
  • @ContextConfiguration 来源于Srping TestContext框架
  • @DisabledIf 如果提供的该属性值为true的表达或占位符,信号:注解的测试类或测试方法被禁用
  • 在Spring TestContext框架中支持并行测试
  • 通过SpringRunner在Sring TestContext框架中支持TestNG, Junit5,新的执行之前和之后测试回调。在testexecutionlistener API和testcontextmanager新beforetestexecution()和aftertestexecution()回调。
  • 在testexecutionlistener API和testcontextmanager新beforetestexecution()和aftertestexecution()回调。MockHttpServletRequest新增了getContentAsByteArray()和getContentAsString()方法来访问请求体
  • 如果字符编码被设置为mock请求,在print()和log()方法中可以打印Spring MVC Test的redirectedUrl()和forwardedUrl()方法支持带变量表达式URL模板。
  • XMLUnit 升级到了2.3版本。

spring对kotlin的支持

spring在5.0版本提供了对kotlin的支持,
- 空安全api
- 支持kotlin不可变类通过optional参数和默认值
- 函数式bean定义 DSL
- 函数式routing定义 DSL
- 利用Kotlin具体的类型参数指定显式类避免使用序列化/反序列化的各种API比如RestTemplate或WebFlux的API。
- kotlin 空安全支持 @Autowired或@Inject 和 @RequestParam、@RequestHeader等
- kotlin脚本同时支持 springMVC 和 Spring WebFlux
- 支持kotlin构造器自动注入
- kotlin反射用来定义接口方法参数

主要介绍2点
1. Bean 和 route 的 DSL

spring注入bean的方式我们知道的有2中,xml配置文件和注解。如果使用kotlin的话使用dsl的方式会更方便。

```
fun beans() = beans {
    bean()
    bean {
        Routes(ref(), ref())
    }
    bean("webHandler") {
        RouterFunctions.toWebHandler(ref().router(), HandlerStrategies.builder().viewResolver(ref()).build())
    }
    bean("messageSource") {
        ReloadableResourceBundleMessageSource().apply {
            setBasename("messages")
            setDefaultEncoding("UTF-8")
        }
    }
    bean {
        val prefix = "classpath:/templates/"
        val suffix = ".mustache"
        val loader = MustacheResourceTemplateLoader(prefix, suffix)
        MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
            setPrefix(prefix)
            setSuffix(suffix)
        }
    }
    profile("foo") {
        bean()
    }
}

class Foo

```

bean() 等价于 在UserHandler的类上面加上 @Component 或者在xml 里配置一个bean
bean() 可以接受配置参数:

    ```
    name: String? = null,
    scope: Scope? = null,
    isLazyInit: Boolean? = null,
    isPrimary: Boolean? = null,
    autowireMode: Autowire = Autowire.NO,
    isAutowireCandidate: Boolean? = null,
    crossinline function: BeanDefinitionContext.() -> T
    ```

bean {
    Routes(ref(), ref())
}
ref() 是让spring自动初始化Routes构造器注入的bean
  1. Router 的 DSL

    fun router() = router {
            accept(TEXT_HTML).nest {
                GET("/") { ok().render("index") }
                GET("/sse") { ok().render("sse") }
                GET("/users", userHandler::findAllView)
            }
            "/api".nest {
                accept(APPLICATION_JSON).nest {
                    GET("/users", userHandler::findAll)
                }
                accept(TEXT_EVENT_STREAM).nest {
                    GET("/users", userHandler::stream)
                }
    
            }
            resources("/**", ClassPathResource("static/"))
        }.filter { request, next ->
            next.handle(request).flatMap {
                if (it is RenderingResponse) RenderingResponse.from(it).modelAttributes(attributes(request.locale(), messageSource)).build() else it.toMono()
            }
        }

    可以更方便的创建route

spring Data

  • 部分jpa 支持直接返回 Flux 的数据类型

你可能感兴趣的:(spring)