您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

Spring 5 新功能:函数式 Web 框架

2024/12/29 13:36:22发布18次查看
示例
我们选用示例程序作为开始。下面是一个响应资源库用于暴露person对象。这个响应资源库与传统的无响应资源库类似,除了flux对应传统的 list,mono对应传统的 person对象。mono作为完成标识:用于指示保存工作完成.更多reactor 类型信息请查阅 dave发布的博客
public interface personrepository { mono getperson(int id); flux allpeople(); mono saveperson(mono person);}
这里我们介绍如何使用新的函数式web框架暴露资源库:
routerfunction route = route(get(/person/{id}), request -> { mono person = mono.justorempty(request.pathvariable(id)) .map(integer::valueof) .then(repository::getperson); return response.ok().body(frompublisher(person, person.class)); }) .and(route(get(/person), request -> { flux people = repository.allpeople(); return response.ok().body(frompublisher(people, person.class)); })) .and(route(post(/person), request -> { mono person = request.body(tomono(person.class)); return response.ok().build(repository.saveperson(person)); }));
这里我们介绍如何运行它,下面是reactor netty的示例:
httphandler httphandler = routerfunctions.tohttphandler(route); reactorhttphandleradapter adapter = new reactorhttphandleradapter(httphandler); httpserver server = httpserver.create(localhost, 8080); server.startandawait(adapter);
最后要做的是,进行一次尝试请求:
$ curl ' {name:john doe,age:42}
上面的介绍覆盖了很多内容,下面让我们深入挖掘下!
核心组件
我将通过依次介绍handlerfunction,routerfunction以及filterfunction 等核心组件来介绍整个框架。这三个接口以及本文中其他类型都可以在org.springframework.web.reactive.function包中找到。
处理功能
新框架的起点是handlerfunction,其实质是function,其中的request 和 response都是新定义的不可变接口,提供了基础的对jdk8优化的http消息描述dsl。有一个便捷的构造response实例的构造器,与responseentity中的十分相似。注解方式中与handlerfunction相对应的是@requestmapping所注解的方法。
如下是“hello world”的处理方法,它返回了状态为200,body为字符串的消息。
handlerfunction helloworld = request -> response.ok().body(fromobject(hello world));
如上,构建于reactor之上的处理方法是完全的响应式的(reactive),它们可以接受flux、mono或者其他相应流(reactive streams)的发布者作为返回类型的参数。
需要注意的是处理方法本身是没有副作用的,因为它将response作为返回值,而不是作为参数(对比servlet.service(servletrequest,servletresponse),其实质是biconsumer)。无副作用的方法有很多好处:更有利于测试、构建和优化。
路由功能
入站请求是由routerfunction,(即function { if (request.path().equals(/hello-world)) { return optional.of(r -> response.ok().body(fromobject(hello world))); } else { return optional.empty(); } };
一般不用写完整的路由方法,而是静态引入routerfunctions.route(),这样就可以用请求判断式(requestpredicate) (即 predicate)和处理方法(handlerfunction)创建路由方法了。如果判断式判断成功则返回处理方法,否则返回空结果。如下是用route方法方式重写上面的例子:
routerfunction helloworldroute = routerfunctions.route(request -> request.path().equals(/hello-world), request -> response.ok().body(fromobject(hello world)));
静态引入requestpredicates.*后就可以使用那些常用的判断式了,如匹配路径、http方法、content-type等。这样上面的例子将会变得更精简:
routerfunction helloworldroute = routerfunctions.route(requestpredicates.path(/hello-world), request -> response.ok().body(fromobject(hello world)));
组合功能
两个路由方法可以被组合成一个新的路由方法,可以路由任意处理方法:如果第一个路由不匹配则执行第二个。可以通过调用routerfunction.and()方法实现,如下:
routerfunction route = route(path(/hello-world), request -> response.ok().body(fromobject(hello world))) .and(route(path(/the-answer), request -> response.ok().body(fromobject(42))));
上面的例子如果路径匹配/hello-world会返回“hello world”,如果匹配/the-answer则返回“42”。如果都不匹配则返回一个空的optional对象。注意,组合的路由是按顺序执行的,所以应该将更通用的方法放到更明确的方法的前面。
请求判断式也是可以组合的,通过调研and或者or方法。正如预期的一样:and表示给定的两个判断式同时满足则组合判断式满足,or则表示任意判断式满足。如下:
routerfunction route = route(method(httpmethod.get).and(path(/hello-world)), request -> response.ok().body(fromobject(hello world))) .and(route(method(httpmethod.get).and(path(/the-answer)), request -> response.ok().body(fromobject(42))));
实际上,requestpredicates中的大部分判断式都是组合的!比如requestpredicates.get(string)是requestpredicates.method(httpmethod)和requestpredicates.path(string)的组合。所以上面的例子可以重写为:
routerfunction route = route(get(/hello-world), request -> response.ok().body(fromobject(hello world))) .and(route(get(/the-answer), request -> response.ok().body(fromobject(42))));
方法引用
此外,目前为止我们的处理方法都是行内的lambda表达式。尽管这样很适合于实例和简短的例子,但是当结合请求路由和请求处理两个关注点时,可能就有变“混乱”的趋势了。所以我们将尝试将他们简化。首先,创建一个包含处理逻辑的类:
class demohandler { public response helloworld(request request) { return response.ok().body(fromobject(hello world)); } public response theanswer(request request) { return response.ok().body(fromobject(42)); }}
注意,这两个方法的签名都是和处理方法兼容的。这样就可以方法引用了:
demohandler handler = new demohandler(); // or obtain via di routerfunction route = route(get(/hello-world), handler::helloworld) .and(route(get(/the-answer), handler::theanswer));
过滤功能
由路由器函数进行映射的路由可以通过调用 routerfunction.filter(filterfunction) 来进行过滤, 这里的 filterfunction 其实就是一个 bifunction。函数的处理器(handler)参数代表的就是整个链条中的下一项: 这是一个典型的 handlerfunction, 但如果附加了多个过滤器的话,它也能够是另外的一个 filterfunction。让我们向路由添加一个日志过滤器:
routerfunction route = route(get(/hello-world), handler::helloworld) .and(route(get(/the-answer), handler::theanswer)) .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; });
注意这里对下一个处理器的调用时可选的。这个在安全或者缓存的场景中是很有用的 (例如只在用户拥有足够的权限时才调用 next)。
因为 route 是一个没有被绑定的路由器函数,我们就得知道接下来的处理会返回什么类型的响应消息。这就是为什么我们在过滤器中要以一个 response 结束, 那样它就会可能有一个 string 类型的响应消息体。我们可以通过使用 routerfunction.andsame() 而不是 and() 来完成这件事情。这个组合方法要求路由器函数参数是同一个类型。例如,我们可以让所有的响应消息变成小写的文本形式:
routerfunction route = route(get(/hello-world), handler::helloworld) .andsame(route(get(/the-answer), handler::theanswer)) .filter((request, next) -> { response response = next.handle(request); string newbody = response.body().touppercase(); return response.from(response).body(fromobject(newbody)); });
使用注解的话,类似的功能可以使用 @controlleradvice 或者是一个 servletfilter 来实现。
运行一个服务端
所有这些都很不错,不过仍然有一块欠缺:我们如何实际地将这些函数在一个 http 服务器中跑起来呢? 答案毋庸置疑,那就是通过调用另外的一个函数。 你可以通过使用 routerfunctions.tohttphandler() 来将一个路由器函数转换成 httphandler。httphandler 是 spring 5.0 m1 中引入的一个响应式抽象: 它能让你运行许多的响应式运行时: reactor netty, rxnetty, servlet 3.1+, 以及 undertow。在本示例中,我们已经展示了在 reactor netty 中运行一个路由会是什么样子的。对于 tomcat 来说则是像下面这个样子:
httphandler httphandler = routerfunctions.tohttphandler(route); httpservlet servlet = new servlethttphandleradapter(httphandler); tomcat server = new tomcat(); context rootcontext = server.addcontext(, system.getproperty(java.io.tmpdir)); tomcat.addservlet(rootcontext, servlet, servlet); rootcontext.addservletmapping(/, servlet); tomcatserver.start();
需要注意的意见事情就是上面的东西并不依赖于一个 spring 应用程序上下文。就跟 jdbctemplate 以及其它 spring 的工具类那样, 要不要使用应用程序上下文是可以选的: 你可以将你的处理器和路由器函数在一个上下文中进行绑定,但并不是必须的。
还要注意的就是你也可以将一个路由器函数转换到一个 handlermapping中去,那样就它可以在一个 dispatcherhandler (可能是跟响应式的 @controllers 并行)中运行了。
结论
至此便结束了对 spring 新函数式 web 框架的介绍。让我简单小结一下:
处理功能通过作出回应来处理请求,
路由器功能可连接到处理功能,并可与其他路由器功能共用,
路由器功能可由过滤器的功能进行过滤,
路由器功能可在响应式网络运行机制中运行。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product