Vert.x-Web与SpringBoot整合

Vert.x的各种优点本文就不再赘述了,感兴趣的朋友可以看一下博主的另一篇博文《Spring Boot同步架构与Vert.x异步架构高并发性能对比》。本文主要介绍博主整理开发的Vertx与SpringBoot整合的脚手架项目。项目旨在便于现有spring项目与Vert.x整合,既能体验Vert.x的高效又兼顾spring的泛用性,降低Vert.x的学习和使用成本,可以做到spring到Vert.x的平滑过渡。项目web层使用了vert.x-web框架,在web层与传统spring的服务层之间加入了异步服务层,异步服务层使用Service Proxies服务代理框架实现,简化了EventBus的使用。数据连接层使用了MybatisPlus框架。

项目结构如下图。

Vert.x-Web与SpringBoot整合_第1张图片

其中vs-core模块为本项目核心,vs-main模块主要用于演示项目。

vs-main中App.java为项目启动类,代码如下。

package com.xxx;

import com.xxx.handlerfactory.RouterHandlerFactory;
import com.xxx.vertx.DeployVertxServer;
import com.xxx.vertx.VertxUtil;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.eventbus.EventBusOptions;
import io.vertx.ext.web.Router;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.event.EventListener;

import java.io.IOException;

@SpringBootApplication
@ComponentScan("com.xxx")
public class App {
    /**
     * web api所在包路径
     */
    @Value("${web-api-packages}")
    private String webApiPackages;

    /**
     * 异步服务所在包路径
     */
    @Value("${async-service-impl-packages}")
    private String asyncServiceImplPackages;

    /**
     * http服务器端口号
     */
    @Value("${http-server-port}")
    private int httpServerPort;

    /**
     * 工作线程池大小(可根据实际情况调整)
     */
    @Value("${worker-pool-size}")
    private int workerPoolSize;

    /**
     * 异步服务实例数量(建议和CPU核数相同)
     */
    @Value("${async-service-instances}")
    private int asyncServiceInstances;

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

    @EventListener
    public void deployVerticles(ApplicationReadyEvent event) {
        EventBusOptions eventBusOptions = new EventBusOptions();
        //便于调试 设定超时等时间较长 生产环境建议适当调整
        eventBusOptions.setConnectTimeout(1000 * 60 * 30);
        Vertx vertx = Vertx.vertx(
                new VertxOptions().setWorkerPoolSize(workerPoolSize)
                        .setEventBusOptions(eventBusOptions)
                        .setBlockedThreadCheckInterval(999999999L)
                        .setMaxEventLoopExecuteTime(Long.MAX_VALUE)
        );
        VertxUtil.init(vertx);
        try {
            Router router = new RouterHandlerFactory(webApiPackages).createRouter();
            DeployVertxServer.startDeploy(router, asyncServiceImplPackages, httpServerPort, asyncServiceInstances);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

该类在SpringBoot传统启动类的基础之上加入了deployVerticles方法,加上@EventListener注解表示该方法在Spring容器初始化完成后执行。该方法的主要工作是完成Vertx对象的实例化,Router构建,verticle发布等。App.java类中有几个参数需要提前在application.yml中配置,具体见代码注释。

RestApi类类似于springMVC的Controller类,代码如下。

package com.xxx.web;

import com.xxx.anno.RouteHandler;
import com.xxx.anno.RouteMapping;
import com.xxx.anno.RouteMethod;
import com.xxx.entity.User;
import com.xxx.model.ReplyObj;
import com.xxx.service.UserAsyncService;
import com.xxx.service2.UserTwoAsyncService;
import com.xxx.utils.AsyncServiceUtil;
import com.xxx.utils.HttpUtil;
import com.xxx.utils.ParamUtil;
import com.xxx.vertx.VertxUtil;
import io.vertx.core.Handler;
import io.vertx.core.file.FileSystem;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.FileUpload;
import io.vertx.ext.web.RoutingContext;

import java.util.List;
import java.util.Set;

import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_OK;


@RouteHandler("restapp")
public class RestApi {

    private UserAsyncService userAsyncService = AsyncServiceUtil.getAsyncServiceInstance(UserAsyncService.class);

    private UserTwoAsyncService userTwoAsyncService = AsyncServiceUtil.getAsyncServiceInstance(UserTwoAsyncService.class);

    /**
     * 演示过滤器
     *
     * @return
     */
    @RouteMapping(value = "/*", method = RouteMethod.ROUTE, order = 2)
    public Handler appFilter() {
        return ctx -> {
            System.err.println("我是appFilter过滤器!");
            ctx.next();
        };
    }

    /**
     * 演示路径参数
     *
     * @return
     */
    @RouteMapping(value = "/test/:id", method = RouteMethod.GET)
    public Handler myTest() {
        return ctx -> {
            JsonObject param = ParamUtil.getRequestParams(ctx);
            HttpUtil.fireJsonResponse(ctx.response(), HTTP_OK, ReplyObj.build().setMsg("Hello,欢迎使用测试地址.....").setData(param.encode()));
        };
    }

    /**
     * 演示服务调用
     *
     * @return
     */
    @RouteMapping(value = "/listUsers", method = RouteMethod.GET)
    public Handler listUsers() {
        return ctx -> {
            JsonObject param = ParamUtil.getRequestParams(ctx);
            if (param.containsKey("age")) {
                param.put("age", Integer.valueOf(param.getString("age")));
            }
            User user = new User(param);
            userAsyncService.listUsers(user, ar -> {
                if (ar.succeeded()) {
                    List userList = ar.result();
                    HttpUtil.fireJsonResponse(ctx.response(), HTTP_OK, ReplyObj.build().setData(userList));
                } else {
                    HttpUtil.fireJsonResponse(ctx.response(), HTTP_INTERNAL_ERROR,
                            ReplyObj.build().setData(ar.cause().getMessage()).setCode(HTTP_INTERNAL_ERROR));
                }
            });
        };
    }

    @RouteMapping(value = "/findUserById", method = RouteMethod.GET)
    public Handler findUserById() {
        return ctx -> {
            JsonObject param = ParamUtil.getRequestParams(ctx);
            if (!param.containsKey("id")) {
                HttpUtil.fireJsonResponse(ctx.response(), HTTP_INTERNAL_ERROR,
                        ReplyObj.build().setMsg("缺少id参数").setCode(HTTP_INTERNAL_ERROR));
                return;
            }
            userTwoAsyncService.findUser(Long.valueOf(param.getString("id")), ar -> {
                if (ar.succeeded()) {
                    User user = ar.result();
                    HttpUtil.fireJsonResponse(ctx.response(), HTTP_OK, ReplyObj.build().setData(user));
                } else {
                    HttpUtil.fireJsonResponse(ctx.response(), HTTP_INTERNAL_ERROR,
                            ReplyObj.build().setData(ar.cause().getMessage()).setCode(HTTP_INTERNAL_ERROR));
                }
            });
        };
    }

    /**
     * 演示文件上传
     *
     * @return
     */
    @RouteMapping(value = "/upload", method = RouteMethod.POST)
    public Handler upload() {
        return ctx -> {
            Set uploads = ctx.fileUploads();
            FileSystem fs = VertxUtil.getVertxInstance().fileSystem();
            uploads.forEach(fileUpload -> {
                String path = "D:/vertxupload/" + fileUpload.fileName();
                fs.copyBlocking(fileUpload.uploadedFileName(), path);
            });
            HttpUtil.fireJsonResponse(ctx.response(), HTTP_OK, ReplyObj.build().setData("OK"));
        };
    }

}

该类使用注解的方式定义了Router的路径,方法,并编写对应的handler处理http请求。在Vert.x-Web中,如果请求同时匹配多个路由,则会优先执行先注册的路由,如果该路由handler中执行了next()方法,则会继续执行下一个匹配的路由handler,我们可以利用此特性实现与servlet中过滤器相似的效果,例如权限控制等。请注意路由的注册顺序将影响handler的执行顺序,通过配置@RouteMapping注解中的order属性可以实现注册顺序控制,order默认为0,注册顺序为从大到小。

为了避免event-loop被阻塞,我们使用了异步服务封装service层的同步阻塞代码,并且服务代理的handler在worker thread中执行。异步服务实际上是官方对EventBus调用的封装,简化了代码编写,因此服务代理的handler实际上就是EventBus的消费handler。要注意的是服务代理接口的参数类型与异步返回类型都有限制,具体可以参考官方文档中的Vert.x Service Proxy一节。

服务代理接口代码如下。

package com.xxx.service;

import com.xxx.entity.User;
import io.vertx.codegen.annotations.ProxyGen;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;

import java.util.List;


@ProxyGen
public interface UserAsyncService {

    void listUsers(User user, Handler>> resultHandler);
}

加上@ProxyGen可以自动生成服务代理实现类,但是并不代表我们不需要编写代码,我们仍需要自己编写服务代理接口实现类来自定义handler的逻辑。注意@ProxyGen需要配合package-info.java才能实现自动生成。

服务代理接口实现类代码如下。

package com.xxx.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xxx.anno.AsyncServiceHandler;
import com.xxx.entity.User;
import com.xxx.model.BaseAsyncService;
import com.xxx.service.UserAsyncService;
import com.xxx.service.UserService;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;


@Component
@AsyncServiceHandler
public class UserAsyncServiceImpl implements UserAsyncService, BaseAsyncService {

    @Autowired
    private UserService userService;

    @Override
    public void listUsers(User user, Handler>> resultHandler) {
        try {
            List userList = userService.list(new QueryWrapper<>(user));
            Future.succeededFuture(userList).setHandler(resultHandler);
        } catch (Exception e) {
            resultHandler.handle(Future.failedFuture(e));
        }
    }
}

注意该类需要加上@AsyncServiceHandler注解,同时还需要实现BaseAsyncService接口,这些是用于服务代理注册及实例化的,具体实现在vs-core模块中。加上@Component注解后可以注入spring服务,服务层以及数据连接层代码与传统Spring项目相同,因此本文不再赘述。

对项目封装感兴趣的朋友可以下载源码看看。

项目源码

参考项目

vertx-framework开源项目

你可能感兴趣的:(JAVA,SpringBoot,Vert.x)