Skip to content

Latest commit

 

History

History
267 lines (226 loc) · 13.1 KB

README.md

File metadata and controls

267 lines (226 loc) · 13.1 KB

vertx-start

简单地,快速地启动vert.x的手脚架

vertx-start保留了vert.x原汁原味的开发方式,并没有修改运行时的任何东西。

vertx-start非常的轻量级,代码也就那么几行。而且只依赖了vertx-core,vertx-web。 是开发vert.x居家旅行、早日脱单的必备良药。

实例代码:https://github.com/wang007/vertx-start-example

maven坐标

<dependency>
    <groupId>com.github.wang007</groupId>
    <artifactId>vertx-start</artifactId>
    <version>1.0.1</version>
<dependency>

有什么功能?

  • @Route
  • @Deploy
  • 免copy Json, JsonArray
  • profiles.active

快速启动vertx-start

public class Main {
    public static void main(String[] args) {
	    Vertx vertx = Vertx.vertx();
	    VertxBoot boot = VertxBoot.create(vertx);
	    boot.start();
   }
}

就是这么简单把vertx-start启动起来。 这种启动方式确保你的配置文件中有base.paths属性。默认从启动类开始扫描。 base.paths属性用于指定vertx-start扫描component(组件)的路径。 可以指定多个,用逗号","间隔。 vertx-start是不管你的vertx怎么获取的,即是说,用Main方式启动也行,也用Launcher也可以。

当然你也可以在hook方法中把VertxBoot保存出来。

public class Main {

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        VertxBoot.create(vertx)
                .afterDeployedHook(VertxBootHolder::setVertxBoot)
                .start();
    }
    public static final class VertxBootHolder {
        public volatile static VertxBoot vertxBoot;
        public synchronized static void  setVertxBoot(VertxBoot boot) {
            vertxBoot = vertxBoot;
        }
        public static VertxBoot getVertxBoot() {
            return vertxBoot;
        }
    }
}

痛点1

大部分情况下,你的Router代码是在Verticle中组织的,当Route的数量少时还好,如果Route数量大的话,所有的Router代码组织到一个Verticle中,这是会让开发者很头痛的事情。

如果Router通过hack方式传到其他的Verticle中,其实这是没用的。Router最终运行所在的eventLoop是httpServer listen的那个Verticle。 这样会有很明显的“意识”问题,以为Router会在其他Verticle的上下文执行,其实不然。

vertx-start是如何解决这个痛点的呢?

把Router组织到LoadRouter上,按功能划分不同的功能的Router到LoadRouter中。

@Route( mountPath = "/demo")
public class DemoRouter implements LoadRouter {
    @Override
    public void start(Router router, Vertx vertx) {
        router.route("/wang").handler(rc -> {
            rc.response().end("hello world");
        });
    }
}
@Deploy(instances = 16)
public class HttpServer extends HttpServerVerticle {}

就通过这样简单的几行代码,就可以把httpServer启动起来。 而instances是Verticle的实例数,HttpServer对应的Verticle实例数 = eventLoop count,充分发挥Vert.x的性能。

public interface LoadRouter {

    /**
     * {@link LoadRouter}生命周期方法。
     *
     * 当且仅当{@link #start(Future)}成功完成, 调用入参中的{@link Future#complete()}
     *
     */
    void start(Future<Void> future);

    /**
     *
     * @return 用于 {@link LoadRouter} 排序, 升序。 默认: 0.
     */
    default int order() {
        return 0 ;
    }

    /**
     * {@link LoadRouter}创建好后调用。
     *
     * 例如:权限相关的route实现,可以放到该方法中。
     *
     * @param router 当使用{@link Route#mountPath()} 挂载路径, router为subRouter, (子路由)
     * @param server {@link HttpServerVerticle}定义client组件,然后在这里获取,达到所有LoadRouter共享。
     */
    default  void init(Router router, Vertx vertx, HttpServerVerticle server) {}

}
  • init方法在start方法之前执行。 例如一些前置Route(像权限校验的Route)可以在init方法创建。HttpServerVerticle子类中声明一些client,然后通过server.self()转成对应的子类,获取Client。
  • order方法用于LoadRouter实现类排序,order越小,越前面。意味着越先把LoadRouter中调用Route加到MainRouter容器中。
  • 绝大多数情况下,推荐使用AbstractLoadRouter,需要协程的kt请使用CoroutineRouter。

@Route怎么使用?

首先声明一点,@Route只能加到LoadRouter实现类上,否则报错。

@Route中的3个属性。
  • value -> 路径前缀。默认是"",即没有路径前缀。像上面的实例代码, 最终的访问路径是 /demo/wang。即会把value的值拼接到Router定义route的路径中。
  • mountPath -> 挂载路径。默认是"",即不挂载subRouter,直接挂载到MainRouter上。 先声明一点, mountPath跟value不冲突,如果两者同时存在,最终的访问路径是 /mountPath/value/path。
  • sharedMount -> 是否共享挂载subRouter,默认是true。即共享。 绝大多数情况下,都是true。

    如果不熟悉vertx-web的话,会对这个挂载路径有疑问。     Router是Route的容器,里面有skipList保存了所有的Route。如果访问后面的Route的话,需要跟前面的Route逐一匹配。Route数量大的话,对性能有所损失。     而mountPath就是Route进行了分类。把一些route加到subRouter上,最后把subRouter加到MainRouter。 更详细的解读,请参考vertx-web官方文档关于mount的解释。     mountPath是强烈推荐使用一个属性。

痛点2

当写完一个Verticle的时候,需要手动调用vertx#deployVerticle方法来部署Verticle。尽管你可能很不情愿,但是你必须这么做。

@Deploy
public class DemoVerticle extends AbstractVerticle {
    @Override
    public void start() throws Exception {
        vertx.eventBus().<JsonSend>consumer("jsonSend", msg -> {
            System.out.println(msg.body());
            msg.reply(new JsonSend().put("name", "wang007"));
        });
    }
}

对,就是这么简单,通过@Deploy注解,就把Verticle部署了。

@Deploy的属性
  • instances -> 默认是1。Vertilce的实例数,这个没啥好说的了吧。
  • worker -> 默认是false。是否为workVerticle
  • order -> 默认是0。 部署Verticle时的顺序,值越小,越先部署。假如verticle之间有依赖的话,可以使用该属性。

也许你会说,这么属性还不够啊,vertx部署Verticle的时候,有很多属性可选呢, 甚至包括部署完成时的操作。 别急,都有, 听我娓娓道来。

实现VerticleConfig接口
 public interface VerticleConfig {
    /**
     * 部署verticle的参数
     * {@link Deploy}中值 != 默认值 将会设置到options中
     * @return 部署参数
     */
    default DeploymentOptions options() {
        return new DeploymentOptions();
    }
    /**
     * 确保该verticle是单实例的 即{@link DeploymentOptions#instances} = 1
     * @return true: verticle必须单利,如果{@link Deploy#instances()} != 1 或 {@link #options()}中的instances != null 报错
     *         false: 允许多利的
     * @throws IllegalStateException
     */
    default boolean requireSingle() {
        return false ;
    }
    /**
     * 部署verticle完成之后的回调
     * {@link io.vertx.core.Vertx#deployVerticle(String, Handler)} 中的Handler
     * @return handler
     */
    default  Handler<AsyncResult<String>> deployedHandler() {
        return null;
    }
}

让verticle实现类实现VerticleConfig接口

  • options方法,设置部署参数。如果@Deploy有设置数,且不等于默认参数。那么会设置到options中。
  • requireSingle方法,确保Verticle单例。默认是false。如果设置返回true。且设置多实例数的话,报错。
  • deployedHandler方法,设置部署完成后操作。默认是null。

HttpServerVerticle

是的,你没看错,要启动一个httpServer,必须继承HttpServerVerticle。并用@Deploy注解。骨灰级推荐httpServer对应Verticle的实例数等于eventLoop的实例数。才能充分发挥vert.x的性能。 HttpServerVerticle有多个拓展方法。

  1. addressAndPort方法。默认启动端口:8080,如果8080不合你的胃口。你只需要覆盖该方法,提供你的端口即可。
  2. **可以在init方法初始化一些client,并且initFuture#complete方法通知初始化完成,且public对应的client。然后可以LoadRouter#init方法中获取。
  3. before方法(敲黑板)。传入的参数是MainRouter。在执行所有的LoadRouter方法之前执行,可以覆盖该方法,做一些全局的Route操作。 例如BodyHandler等。
  4. doStop方法。传入的参数是httpServer(Vert.x中的)实例,做Verticle stop时的操作。
  5. beforeAccept方法。传入的参数是request。在请求来临时,进入MainRouter之前执行。这一步可以做请求之前拦截操作。

不知道算不算痛点的痛点3

    熟悉的vert.x的朋友,都知道。eventBus send json,jsonArray的时候,会发生一次copy操作。尽管你的代码中是能确保线程安全的。     实现JsonSend,JsonArraySend, 大费周折,最后发现还是不够理想。 这里的不够理想是指send的时候必须要设置codecName。因为我的实现中走不到最后defaultCodecMap中。这个在实现之前没发现。瞎眼程序员。

JsonSend,JsonArraySend

@Deploy
public class DemoVerticle extends AbstractVerticle {

    @Override
    public void start() throws Exception {
    
        vertx.eventBus().<JsonSend>consumer("jsonSend", msg -> {
            System.out.println(msg.body());
            msg.reply(new JsonSend().put("name", "wang007"));
        });
        
        vertx.eventBus().<JsonArraySend>consumer("jsonArraySend", msg -> {
            System.out.println(msg.body());
            msg.reply(new JsonArraySend().add("wang007"));
        });

        JsonSend json = new JsonSend().put("name", "wang007").put("hobby", "girl");
        
        vertx.eventBus().<JsonSend>send("jsonSend", json, JsonSend.options(), msg -> {
            System.out.println(msg.result().body());
        });
        
        JsonArraySend array = new JsonArraySend().add("xiaowang");
        
        vertx.eventBus().<JsonArraySend>send("jsonArraySend", array, JsonArraySend.options(), msg -> {
            System.out.println(msg.result().body());
        });
    }
}

其中JsonSend.options(), JsonArraySend.options()是必须的。 否则还是会发生copy。 就是说,你可以把JsonSend当成JsonObject来用,JsonArraySend当成JsonArray来用是没问题的。

JsonSend,JsonArraySend是如何实现的。

  1.   其实关于JsonSend,JsonArraySend是一种妥协。
  2.   JsonSend,JsonArraySend维护着一个属性,会自动判断是否调用了eventBus send。如果调用了JsonSend,JsonArraySend就变成不可变。同时这个不可变的json也传到Consumer中, consumer使用这个json也不可变。只能从里面读取数据。
  3.   同时JsonSend,JsonArraySend的使用有一定的限制。例如不能存Map,List,Map可以用JsonObject代替,List可以用JsonArray。还有存进send中的JsonObject,JsonArray将的不可变。切记。尝试存的话,会报错。
  4.   即是说JsonSend,JsonArraySend免copy的实现方式是通过send之后不可变实现的。 jsonSend,JsonArraySend没有100%不可变。但是正常使用是没问题的。还是那句话:你要做傻逼,没人拦得住你。

属性文件

  1.  vertx-start默认加载classpath下的application.properties文件。
  2.  可以调用VertxBoot #setConfigFilePath方法设置classpath下的其他路径
  3.  如果以上都不满足或者想要添加一些额外的属性, 可以在vertxBoot #start方法之前调用vertxboot #getContainer方法,然后强制成InternalContainer。 再调用appendProperties方法,添加属性。同样,也可以调用appendComponent方法来添加Component到容器中。

关于base.paths

  •      可以添加多个path。
  •      base.paths就是组件的路径。确保注解的Component都包含在base.paths中

关于profiles.active

  •      关于profiles.active,相信使用过spring的朋友都知道,用于指定不同环境的配置文件。

  •      啰嗦一下,profiles.active文件的前缀、后缀必须跟主配置文件一样。      例如:主配置文件:application.properties, profiles.active文件:application-dev.properties

  •     profiles.active加载方法且有先后顺序。先去System属性文件中找(通过启动jvm的时候添加-Dbase.paths参数添加)。找不到再去主属性文件中找。找不到就是没有。即不加载profiles.active文件。

  • 调用vertxBoot.loadFor方法把属性加到到pojo中。同时pojo使用@Properties注解上。