浅析微服务框架 Helidon 的使用

零 前期准备

0 版本

JDK 版本 : OpenJDK 11.0.1

IDE : idea 2018.3

Helidon Webserver : helidon-webserver 1.0.0 (核心依赖包)

Helidon Json : helidon-media-jsonp-server 1.0.0 (json 支持包)

1 Helidon 简介

Helidon 是 Oracle 官方出品的 java 微服务框架,底层基于 Netty 驱动,大致的使用套路和 Vertx 相差不是很多(笔者只是浅浅的了解过 Vertx,可能存在理解不透的情况)。在 2019 年的年初,Helidon 迎来了 1.0.0 的版本更新,api 开始趋于稳定。

就目前的版本来看,Oracle 应该希望 Helidon 是一个小而美的轻巧型 java 框架,所以对其的封装非常之薄,仅仅是对 Netty 做了一层定制化的处理,同时也深度整合了 jdk 的相关接口(毕竟是自家的东西,驾驭能力不一般)。就连 json 的处理都引用了 javax 中的 joup 去做,而不是使用第三方工具包。

Helidon 目前的网络资料很少,所以本文主要通过其官方文档进行 Demo 的构筑。

一 配置

Helidon 中的配置类有两种,一种是用于读取配置文件的 Config,另一种是用于配置服务器对象的 ServerConfiguration。

先来看 Config:

//配置文件
Config conf = Config

                    //builder
                    .builder()

                    //载入配置文件,可以一次载入多张
                    .sources(
                            //根据绝对路径去载入配置文件
                            ConfigSources.file("D://application.yaml"),
                            //到 resource 文件夹下去寻找
                            ConfigSources.classpath("application2.yaml")
                    )

                    //创建完成
                    .build();

再来看 ServerConfiguration:

//创建一个配置对象,用于注入一些配置信息
ServerConfiguration config = ServerConfiguration

                                                //生成 builder
                                                .builder()

                                                //address

                                                //getLoopbackAddress() 会获取到 localhost
                                                .bindAddress(InetAddress.getLoopbackAddress())

                                                //getLocalHost() 会获取到本机的 ip
                                                //.bindAddress(InetAddress.getLocalHost())

                                                //端口
                                                .port(8080)

                                                //工作线程的线程数
                                                .workersCount(10)

                                                //增加一个 ServerConfiguration 对象
                                                //.addSocket("serverConfiguration",ServerConfiguration.builder().build())

                                                //载入配置文件
                                                //.config(conf)

                                                //创建完成
                                                .build();

二 路由

网络博文最简单的 Hello World 的路由对象:

//创建最简单的路由逻辑
Routing rt = Routing

                    //builder
                    .builder()

                    //设置路由的请求方法和业务逻辑
                    //设置多种请求方法
                    .anyOf(List.of(Http.Method.GET, Http.Method.POST,Http.Method.DELETE),"/hello",(req, res) -> res.send("hello world"))

                    //单一请求方法
                    //.post("/hello",(req, res) -> res.send("hello world"))
                    //.get("/hello",(req, res) -> res.send("hello world"))

                    //创建完成
                    .build();

结合业务逻辑的路由对象:

//创建路由对象
Routing routing = Routing

                        //builder
                        .builder()

                        //注册 json 解析器
                        .register(JsonSupport.create())

                        //添加 url 路由,一个路由对象可以添加多个

                        //可以添加业务逻辑的 service 类
                        .post("/hello", Handler.create(JsonObject.class,new HelloService()))

                        //hello world
                        .get("/hello1",(req, res) -> res.send("hello world"))

                        //创建完成
                        .build();

HelloService 中处理具体的业务逻辑:

//HelloService 必须实现 Handler.EntityHandler
class HelloService implements Handler.EntityHandler{

    //json 的解析门面对象
    private static final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Collections.emptyMap());

    //核心业务类,里面可以写具体的业务逻辑,最后 send(...) 进 response 里就可以了
    @Override
    public void accept(ServerRequest req, ServerResponse res, Object entity) {

        //entity 本质上就是 JsonObject
        JsonObject reqJson = (JsonObject)entity;

        //获取键值对并打印,这个操作和 Fastjson 很类似
        String ret = reqJson.getString("hello");
        System.out.println(ret);

        //创建要返回的 json object
        JsonObject msg = jsonFactory

                                //builder
                                .createObjectBuilder()

                                //添加一组 json 的 key - value 键值对
                                .add("message", "Hello")

                                //创建 json 对象完成
                                .build();

        //json array 的创建方式
        //JsonArray array = jsonFactory.createArrayBuilder().build();

        //返回
        res.send(msg);
    }
}

三 服务器对象

服务器对象:

//启动服务器
WebServer

        //创建服务器对象
        //具体依赖 ServerConfiguration 和 Routing 对象
        .create(config,routing)

        //开启服务器
        .start()

        //获取 future
        .toCompletableFuture()

        //设置超时时间为 10 秒
        .get(10, TimeUnit.SECONDS);

四 服务实现

先来看 WebServer 的 create(...) 方法:

//step 1
//WebServer.class
static WebServer create(ServerConfiguration configuration, Routing routing) {
    //有效性验证
    Objects.requireNonNull(routing, "Parameter 'routing' is null!");

    //builder(...) 方法会创建一个 WebServer.Builder 对象,最终在 build() 方法里创建 WebServer
    return builder(routing).config(configuration)
                            .build();
}

//step 2
//WebServer.Builder.class
public WebServer build() {

    //routings 是一个 Map 对象,用来储存更多的路由对象
    String unpairedRoutings = routings
                                    .keySet()
                                    .stream()
                                    .filter(routingName -> configuration == null || configuration.socket(routingName) == null)
                                    .collect(Collectors.joining(", "));

    //有效性验证
    if (!unpairedRoutings.isEmpty()) {
        throw new IllegalStateException("No server socket configuration found for named routings: " + unpairedRoutings);
    }

    //NettyWebServer 是 WebServer 接口的实现类
    //defaultRouting 是上方 create(...) 方法存入的路由对象
    WebServer result = new NettyWebServer(configuration == null
                                                    ? ServerBasicConfig.DEFAULT_CONFIGURATION
                                                    : configuration,
                                            defaultRouting, routings);
    
    //默认的路由对象即为 RequestRouting 类型
    //此处给路由对象添加回调方法
    if (defaultRouting instanceof RequestRouting) {
        ((RequestRouting) defaultRouting).fireNewWebServer(result);
    }

    //返回
    return result;
}

//step 3
//NettyWebServer.class
NettyWebServer(ServerConfiguration config,
                   Routing routing,
                   Map namedRoutings) {
    
    //获取所有的 SocketConfiguration 对象
    //即为之前 addSocket(...) 方法存入的对象,也包括主配置对象自身
    Set> sockets = config.sockets().entrySet();

    //创建两个线程
    this.bossGroup = new NioEventLoopGroup(sockets.size());
    this.workerGroup = config.workersCount() <= 0 ? new NioEventLoopGroup() : new NioEventLoopGroup(config.workersCount());

    this.configuration = config;

    //循环所有的配置对象,然后分别创建 ServerBootstrap 对象并保存
    for (Map.Entry entry : sockets) {
        String name = entry.getKey();
        SocketConfiguration soConfig = entry.getValue();
        ServerBootstrap bootstrap = new ServerBootstrap();
        JdkSslContext sslContext = null;
        if (soConfig.ssl() != null) {
            sslContext = new JdkSslContext(soConfig.ssl(), false, ClientAuth.NONE);
        }

        if (soConfig.backlog() > 0) {
            bootstrap.option(ChannelOption.SO_BACKLOG, soConfig.backlog());
        }
        if (soConfig.timeoutMillis() > 0) {
            bootstrap.option(ChannelOption.SO_TIMEOUT, soConfig.timeoutMillis());
        }
        if (soConfig.receiveBufferSize() > 0) {
            bootstrap.option(ChannelOption.SO_RCVBUF, soConfig.receiveBufferSize());
        }

        //childHandler 是核心的业务 handler,使用者写的业务逻辑也都在里面
        HttpInitializer childHandler = new HttpInitializer(sslContext, namedRoutings.getOrDefault(name, routing), this);

        //储存 handler
        initializers.add(childHandler);

        //配置 Netty 的启动器对象
        bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(childHandler);

        //储存启动器
        bootstraps.put(name, bootstrap);
    }
}

NettyWebServer 中的构造器代码其实本质上就是半段 Netty 的启动模板代码。

主要是为了兼容多配置文件带来的多启动器需求,所以做了一个 for 循环。在多数时候其实就只有一个配置对象,也就只有一个启动器。

而另外半段 Netty 模板代码在 start(...) 方法中:

//NettyWebServer.class
public synchronized CompletionStage start() {

    //started 是一个 boolean 类型的变量,用来判断该服务器对象是否启动
    if (!started) {

        channelsUpFuture.thenAccept(startFuture::complete)
                        .exceptionally(throwable -> {
                            if (channels.isEmpty()) {
                                startFailureHandler(throwable);
                            }
                            for (Channel channel : channels.values()) {
                                channel.close();
                            }
                            return null;
                        });

        channelsCloseFuture.whenComplete((webServer, throwable) -> shutdown(throwable));

        //在之前 NettyWebServer 的构造器中储存下来的所有 Netty 启动器
        Set> bootstrapEntries = bootstraps.entrySet();
        int bootstrapsSize = bootstrapEntries.size();
        for (Map.Entry entry : bootstrapEntries) {
            ServerBootstrap bootstrap = entry.getValue();
            String name = entry.getKey();
            SocketConfiguration socketConfig = configuration.socket(name);
            if (socketConfig == null) {
                throw new IllegalStateException(
                        "no socket configuration found for name: " + name);
            }
            int port = socketConfig.port() <= 0 ? 0 : socketConfig.port();
            if (channelsUpFuture.isCompletedExceptionally()) {
                break;
            }

            try {
                //bootstrap 的设置
                //Helidon 中用监听器方式去异步启动 Netty
                bootstrap.bind(configuration.bindAddress(), port).addListener(channelFuture -> {
                    if (!channelFuture.isSuccess()) {
                        LOGGER.info(() -> "Channel '" + name + "' startup failed with message '"
                                + channelFuture.cause().getMessage() + "'.");
                        channelsUpFuture.completeExceptionally(new IllegalStateException("Channel startup failed: " + name,
                                                                                            channelFuture.cause()));
                        return;
                    }

                    Channel channel = ((ChannelFuture) channelFuture).channel();
                    LOGGER.info(() -> "Channel '" + name + "' started: " + channel);
                    channels.put(name, channel);

                    channel.closeFuture().addListener(future -> {
                        LOGGER.info(() -> "Channel '" + name + "' closed: " + channel);
                        channels.remove(name);
                        if (channelsUpFuture.isCompletedExceptionally()) {
                            if (channels.isEmpty()) {
                                channelsUpFuture.exceptionally(this::startFailureHandler);
                            } else if (future.cause() != null) {
                                LOGGER.log(Level.WARNING,
                                            "Startup failure channel close failure",
                                            new IllegalStateException(future.cause()));
                            }
                        } else {
                            if (!future.isSuccess()) {
                                channelsCloseFuture.completeExceptionally(new IllegalStateException("Channel stop failure.",
                                                                                                    future.cause()));
                            } else if (channels.isEmpty()) {
                                channelsCloseFuture.complete(this);
                            }
                        }
                    });

                    if (channelsUpFuture.isCompletedExceptionally()) {
                        channel.close();
                    }

                    if (channels.size() >= bootstrapsSize) {
                        LOGGER.finer(() -> "All channels started: " + channels.size());
                        channelsUpFuture.complete(this);
                    }
                });
            } catch (RejectedExecutionException e) {
                if (shutdownThreadGroupsInitiated.get()) {
                    break;
                } else {
                    throw e;
                }
            }
        }

        started = true;
        LOGGER.fine(() -> "All channels startup routine initiated: " + bootstrapsSize);
    }

    //返回一个 CompletableFuture 对象
    return startFuture;
}

五 一点唠叨

· Helidon 框架有很多其它组件,比如 Security、Data Source 等,有待继续研究

· 很时髦很轻巧,其实就是简单封装了一下 Netty,大多数的组件也是直接用 jdk 自带的

· json 部分其实就是把 javax.json 包里的工具利用了一下,但是笔者个人觉得没比第三方好用

· 目前来看仅仅是 Oracle 在自 high,社区热度还不够,潜力值不好估计

六 服务端全部代码

import io.helidon.common.http.Http;
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
import io.helidon.media.jsonp.server.JsonSupport;
import io.helidon.webserver.*;
import javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class HelidonService {

    public void run() throws InterruptedException, ExecutionException, TimeoutException {

        //配置文件
//        Config conf = Config
//
//                            //builder
//                            .builder()
//
//                            //载入配置文件,可以一次载入多张
//                            .sources(
//                                    //根据绝对路径去载入配置文件
//                                    ConfigSources.file("D://application.yaml"),
//                                    //到 resource 文件夹下去寻找
//                                    ConfigSources.classpath("application2.yaml")
//                            )
//
//                            //创建完成
//                            .build();

        ServerConfiguration c = ServerConfiguration.builder().build();

        //创建一个配置对象,用于注入一些配置信息
        ServerConfiguration config = ServerConfiguration

                                                        //生成 builder
                                                        .builder()

                                                        //address

                                                        //getLoopbackAddress() 会获取到 localhost
                                                        .bindAddress(InetAddress.getLoopbackAddress())

                                                        //getLocalHost() 会获取到本机的 ip
                                                        //.bindAddress(InetAddress.getLocalHost())

                                                        //端口
                                                        .port(8080)

                                                        //工作线程的线程数
                                                        .workersCount(10)

                                                        //增加一个 ServerConfiguration 对象
                                                        //.addSocket("serverConfiguration",ServerConfiguration.builder().build())

                                                        //载入配置文件
                                                        //.config(conf)

                                                        //创建完成
                                                        .build();

          //创建最简单的路由逻辑
//        Routing rt = Routing
//
//                                //builder
//                                .builder()
//
//                                //设置路由的请求方法和业务逻辑
//                                //设置多种请求方法
//                                .anyOf(List.of(Http.Method.GET, Http.Method.POST),"/hello",(req, res) -> res.send("hello world"))
//
//                                //单一请求方法
//                                //.post("/hello",(req, res) -> res.send("hello world"))
//                                //.get("/hello",(req, res) -> res.send("hello world"))
//
//                                //创建完成
//                                .build();


        //创建路由对象
        Routing routing = Routing

                                //builder
                                .builder()

                                //注册 json 解析器
                                .register(JsonSupport.create())

                                //添加 url 路由,一个路由对象可以添加多个

                                //可以添加业务逻辑的 service 类
                                .post("/hello", Handler.create(JsonObject.class,new HelloService()))

                                //hello world
                                .get("/hello1",(req, res) -> res.send("hello world"))

                                //创建完成
                                .build();

        //启动服务器
        WebServer

                //创建服务器对象
                .create(config,routing)

                //开启服务器
                .start()

                //获取 future
                .toCompletableFuture()

                //设置超时时间
                .get(10, TimeUnit.SECONDS);
    }

    //main 方法
    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        new HelidonService().run();
    }
}

class HelloService implements Handler.EntityHandler{

    //json 的解析门面对象
    private static final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Collections.emptyMap());

    @Override
    public void accept(ServerRequest req, ServerResponse res, Object entity) {

        //entity 本质上就是 JsonObject
        JsonObject reqJson = (JsonObject)entity;

        //获取键值对并打印,这个操作和 Fastjson 很类似
        String ret = reqJson.getString("hello");
        System.out.println(ret);

        //创建要返回的 json object
        JsonObject msg = jsonFactory

                                //builder
                                .createObjectBuilder()

                                //添加一组 json 的 key - value 键值对
                                .add("message", "Hello")

                                //创建 json 对象完成
                                .build();

        //json array 的创建方式
        //JsonArray array = jsonFactory.createArrayBuilder().build();

        //返回
        res.send(msg);
    }
}

你可能感兴趣的:(java)