smallrye-mutiny兵变! 用于 Java 的直观事件驱动反应式编程库 weir 2022-02-06 11:12:24.0 java,mutiny 992 # smallrye-mutiny兵变! 用于 Java 的直观事件驱动反应式编程库 ### Mutiny 是一个新颖的反应式编程库。 它提供了一个简单但功能强大的异步开发模型,可让您构建反应式应用程序。 Mutiny 可以在任何表现出异步性的 Java 应用程序中使用。从反应式微服务、数据流、事件处理到 API 网关和网络实用程序,Mutiny 非常适合。 官网:https://smallrye.io/smallrye-mutiny/index.html #### 事件驱动 Mutiny 将事件置于其设计的核心。使用 Mutiny,您可以观察事件、对其做出反应,并创建优雅且可读的处理管道。不需要函数式编程的博士学位。 #### 可导航 即使使用智能代码完成,具有数百种方法的类也会令人困惑。Mutiny 提供了可导航且明确的 API,可将您推向所需的运营商。 #### 非阻塞 I/O Mutiny 是驯服具有非阻塞 I/O 的应用程序的异步特性的完美伴侣。以声明方式组合操作、转换数据、执行进度、从故障中恢复等等。 #### Quarkus 和 Vert.x 原生 Mutiny 集成在Quarkus中,其中每个反应式 API 都使用 Mutiny,并且Eclipse Vert.x客户端可以使用 Mutiny 绑定。然而 Mutiny 是一个独立的库,最终可以在任何 Java 应用程序中使用。 #### 为异步世界而生 Mutiny 可用于任何异步应用程序,例如事件驱动的微服务、基于消息的应用程序、网络实用程序、数据流处理,当然还有……反应式应用程序! #### 内置无功转换器 Mutiny 基于Reactive Streams规范,因此它可以与任何其他响应式编程库集成。此外,它建议转换器与其他流行的库进行交互。  ### 个人理解 后端服务的反应式编程思想理念由来已久,异步是当今互联网追求更快响应不可或缺的技术力量,netty这么多年的技术积累和卓越的性能体验给java生态带来了前所未有的高速发展。 从rxjava到spring的Project Reactor再到vert.x,到今天给大家介绍的Mutiny都是在倡导一种异步编程思想,谁的好坏先不说但是这是未来,这是我们对信息世界的追求,我想从技术成熟度来说,从准备的时间上来说,都告诉我们是时候刷新一下技术体系了。 ###个人代码实例演示 ```java package com.weirblog.resource; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; import javax.transaction.Transactional; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import com.weirblog.entity.Video; import com.weirblog.vo.DataGrid; import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.PanacheQuery; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import io.quarkus.panache.common.Sort.Direction; import io.quarkus.qute.Location; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; import io.smallrye.mutiny.Uni; import io.vertx.core.json.JsonObject; @Path("video") @ApplicationScoped public class VideoController { @Location("admin/videoAdd.html") Template videoAdd; @GET @Path("editUI/{id}") @Produces(MediaType.TEXT_HTML) public TemplateInstance videoEdit(@PathParam("id") Integer id) { // TemplateInstance templateInstance = videoAdd.data("video", Video.findById(id)); // return Uni.createFrom().completionStage(() -> templateInstance.renderAsync()); Uni video = Video.findById(id); return videoAdd.data("video", video); } @GET @Path("addUI") @Produces(MediaType.TEXT_HTML) public TemplateInstance videoAdd() { return videoAdd.data("video", null); } @GET @Path("list") public Uni list(@QueryParam("page") Integer page, @QueryParam("rows") Integer rows) { return Panache.withTransaction(() -> { PanacheQuery findAll = Video.findAll(Sort.by("createDate", Direction.Descending)); Uni count = findAll.count(); Uni> list = findAll.page(Page.of(page - 1, rows)).list(); return Uni.combine().all().unis(count, list).combinedWith((c, l) -> new DataGrid(c, l)); }); } @POST @Path("add") @Transactional public Uni add(Video video) { if (video.id == null) { return Panache.withTransaction(video::persist).replaceWith(Response .ok(new JsonObject().put("msg", "新增成功").put("success", true)).status(Status.CREATED)::build); } else { return Panache.withTransaction(() -> Video.findById(video.id).onItem().ifNotNull().invoke(entity -> { entity.updateDate = new Date(); entity.vdesc = video.vdesc; entity.vname = video.vname; entity.vurl = video.vurl; })).onItem().ifNotNull() .transform(entity -> Response.ok(new JsonObject().put("msg", "修改成功").put("success", true)).build()) .onItem().ifNull().continueWith(Response.ok().status(Status.NOT_FOUND)::build); } } @DELETE @Path("delete") public Uni delete(@FormParam("videoIds") String videoIds) { List collect = Arrays.asList(videoIds.split(",")).stream().map(s -> Integer.valueOf(s)) .collect(Collectors.toList()); return Panache.withTransaction(() -> Video.delete("id in (?1)", collect)) .map(deleted -> deleted != null ? Response.ok(new JsonObject().put("msg", "操作成功").put("success", true)).status(Status.OK) .build() : Response.ok().status(Status.NOT_FOUND).build()); } } ``` 大家先看,认真的看,我先不讲什么理论,看大家能否看懂代码。 ```java package com.weirblog.resource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import com.weirblog.entity.Tmenu; import com.weirblog.service.TmenuService; import com.weirblog.vo.MenuVo; import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.qute.Location; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; import io.smallrye.mutiny.Uni; import io.vertx.core.json.JsonObject; @Path("menu") @ApplicationScoped public class TmenuController { @Inject TmenuService tmenuService; @GET @Path("tree") public Uni> tree(@QueryParam("id") Integer id) { return Panache.withTransaction(() -> { Uni> menuList = null; if (id == null) { menuList = Tmenu.list("PID is null"); } else { menuList = Tmenu.list("PID = ?1", id); } // Uni> menuList = Tmenu.list("PID is null"); Uni> mlist = menuList.chain(list -> { List menus = new ArrayList(); for (Tmenu tmenu : list) { MenuVo m = new MenuVo(); m.setId(tmenu.id); m.setText(tmenu.text); m.setName(tmenu.text); m.setIconcls(tmenu.iconcls); m.setUrl(tmenu.url); m.setPid(tmenu.PID != null ? tmenu.PID : null); Map attributes = new HashMap(); attributes.put("url", tmenu.url); m.setAttributes(attributes); menus.add(m); } return Uni.createFrom().item(menus); }); // pid有没有子节点 Uni> listAll = Tmenu.listAll(); Uni> pidMap = listAll.chain(list -> { Map map = list.parallelStream().filter(m -> m.getPID() != null) .collect(Collectors.groupingBy(Tmenu::getPID, Collectors.counting())); return Uni.createFrom().item(map); }); return Uni.combine().all().unis(mlist, pidMap).combinedWith((list, map) -> { for (MenuVo tmenu : list) { Long count = map.get(tmenu.getId()); if (count != null && count > 0) { // System.out.println("--------tmenu---" + tmenu.getId()+"----count-------------" + count); tmenu.setState("closed"); } else { tmenu.setState("open"); } } return list; }); }); } @GET @Path("allTree") public Uni> allTree() { return Panache.withTransaction(() -> { Uni> tmenus = Tmenu.findAll().list(); Uni> mvos = tmenus.chain(list -> { List menus = new ArrayList(); for (Tmenu tmenu : list) { MenuVo m = new MenuVo(); m.setId(tmenu.id); m.setIconcls(tmenu.iconcls); m.setName(tmenu.text); m.setUrl(tmenu.url); m.setPid(tmenu.PID); Map attributes = new HashMap(); attributes.put("url", tmenu.url); m.setAttributes(attributes); menus.add(m); } return Uni.createFrom().item(menus); }); Uni> pidMap = tmenus.chain(list -> { Map map = list.parallelStream().filter(m -> m.getPID() != null) .collect(Collectors.toMap(Tmenu::getPID, a -> a, (k1, k2) -> k1)); return Uni.createFrom().item(map); }); return mvos.chain(list -> pidMap.chain(map -> { for (MenuVo tmenu : list) { if (tmenu.getPid() != null) { Tmenu t = map.get(tmenu.getPid()); if (t != null) { tmenu.setPname(t.text); } } } return Uni.createFrom().item(list); })); }); } @POST @Path("add") public Uni add(Tmenu tmenu) { if (tmenu.id == null) { return Panache.withTransaction(tmenu::persist).replaceWith(Response .ok(new JsonObject().put("msg", "新增成功").put("success", true)).status(Status.CREATED)::build); } else { return Panache.withTransaction(() -> Tmenu.findById(tmenu.id) .onItem() .ifNotNull().invoke(entity -> { entity.text = tmenu.text; entity.url = tmenu.url; entity.PID = tmenu.PID; })) .onItem().ifNotNull() .transform(entity -> Response.ok(new JsonObject().put("msg", "修改成功").put("success", true)).build()) .onItem().ifNull().continueWith(Response.ok().status(Status.NOT_FOUND)::build); } } @Location("admin/menuAdd.html") Template menuAdd; @GET @Path("addUI") @Produces(MediaType.TEXT_HTML) public TemplateInstance menuAdd() { return menuAdd.data("tmenu", new Tmenu()); } @Location("admin/menuAdd.html") Template menuEdit; @GET @Path("editUI/{id}") @Produces(MediaType.TEXT_HTML) public TemplateInstance menuEdit(@PathParam("id") Integer id) { return menuEdit.data("tmenu", Tmenu.findById(id)); } @DELETE @Path("delete/{id}") public Uni delete(@PathParam("id") Integer id) { return Panache.withTransaction(() -> Tmenu.deleteById(id)) .map(deleted -> deleted ? Response.ok(new JsonObject().put("msg", "操作成功").put("success", true)).status(Status.OK) .build() : Response.ok().status(Status.NOT_FOUND).build()); } } ``` 这两段代码代码看下来你觉得反应式编程难么? 更多的源码请到这里慢慢看:https://gitee.com/weir_admin/weirblog-quarkus/tree/master/weirblog-quarkus-reactive 这是我的博客正在做异步编程重构,目前还有一些问题待解决,我也会通过视频方式给大家介绍问题和我的编码心得,视频会在B站更新:https://space.bilibili.com/36507008 我的整体体会是异步编程确实难,但我发现难道是api入门也就是入门难,那么一旦你看到了真实项目的代码说实话能看懂,仔细看看发现它不难,它的套路其实很简单,难道是对这种模式的理解和变通,我想如果你真的感兴趣自己能力上面还达不到理解吸收的程度,那就好好学习多看看多练练源码也有多尝试,也可以加入我们quarkus群共同研究学习,我们会非常欢迎。