学习Java中的零依赖性Web-App

249 阅读6分钟

在Go、NodeJS和许多其他现代编程语言中,创建Web应用程序的任务可以只使用标准库和一些实用的微型库来实现。这是这些语言的一个很好的特性,因为现代应用程序的规模、复杂性和模块化程度都在不断增加。

我们生活在一个成百上千的容器化微服务不断被部署、生成并在很短的时间内再次停止的世界里。在这样的环境中,一个小的足迹是关键。减少足迹的另一个好处是降低了这些系统的内在复杂性。每一个额外的外部依赖都意味着更高的心理负荷,以及以后可能出现的安全或性能问题,还有保持最新的不可避免的维护负担。

传统上,Java的成功普遍存在于企业部门,其业务关键性强,但往往是单片式的应用。在这篇博文中,我们将了解Java标准库是否跟上了现代潮流,以及是否有可能只用Java SE JDK 8标准库来编写一个轻量级的Web应用。

我相信,当前Java Web应用的臃肿和高复杂性(例如:使用Spring)并不是唯一的方法,这既不能怪Java,也不能怪Spring,而应该怪开发者的心态和 "我们一直用Java构建事物的方式"。

让我们看看仅用Java标准库就能做些什么吧!

在Java中实现零依赖的Web应用

设置

我们将只创建一个名为WebApp.java 的文件,它可以使用javac WebApp.java && java WebApp 。我们还可以将其打包到一个裸露的openJDK Docker容器中,以确保我们的classpath上没有额外的库。

有了这些,第一步是启动一个WebServer。在Go或NodeJS中,标准库中有一个强大的http 包,它被广泛使用并有很好的文档。在Java中,情况则有很大不同。

事实上,我和几个经验丰富的Java开发者交谈过,他们创建Java Web应用已经好几年了,没有一个人知道Java标准库里有一个HTTP-Server。

但是有:com.sun.net.httpserver

com.sun.net.httpserver.HttpServer

这个HTTPServer的实现从Java 1.6开始就存在了,甚至有一个相当不错的接口和一些方便的方法。

这就是它在我们的应用程序中的样子。

public class WebApp {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/", new LandingPageHandler());
        server.createContext("/post", new PostHandler());
        server.createContext("/json", new JSONHandler());
        server.createContext("/favicon.ico", new IgnoreHandler());

        server.setExecutor(Executors.newCachedThreadPool());
        server.start();

        System.out.println("Server started on port 8080" );
    }
}

所以,我们创建一个服务器,注册一些路由,然后启动服务器。这与其他语言的工作原理非常相似。

对于每个路由,我们提供一个Handler ,它实现了HttpHandler 接口。这个接口提供了一个名为handle 的方法,它接收一个HttpExchange 对象。

这个HttpExchange 对象封装了一个完整的HTTP交互,包括请求和响应。

好吧,那么这样一个Handler 是怎样的呢?

处理程序

首先,我们需要一个处理程序来提供我们的静态index.html 模板。

class LandingPageHandler implements HttpHandler {
    public void handle(HttpExchange exchange) throws IOException {
        String requestMethod = exchange.getRequestMethod();
        System.out.println(requestMethod + " /");
        if (requestMethod.equalsIgnoreCase("GET")) {
            exchange.getResponseHeaders().set(Constants.CONTENTTYPE, Constants.TEXTHTML);
            exchange.sendResponseHeaders(200, 0);
            OutputStream responseBody = exchange.getResponseBody();
            responseBody.write(Constants.getIndexHTML(null));
            responseBody.close();
        } else {
            new NotImplementedHandler().handle(exchange);
        }
    }
}

我想这是很直接的。当然,这有点冗长,但如果我们创建一个HttpGetHandler 接口和一个writeHTML 便利方法,这可能是两行代码,所以这真的不是什么大事。

当然,我们也想与Web应用程序进行交互,例如使用POST

class PostHandler implements HttpHandler {
    public void handle(HttpExchange exchange) throws IOException {
        String requestMethod = exchange.getRequestMethod();
        System.out.println(requestMethod + " /post");
        if (requestMethod.equalsIgnoreCase("POST")) {
            String body = new BufferedReader(
                    new InputStreamReader(
                            exchange.getRequestBody()
                    )
            ).lines().collect(Collectors.joining("\n"));
            String[] parts = body.split("=");
            String name = null;
            if (parts.length > 1) {
                name = parts[1];
            }
            exchange.getResponseHeaders().set(Constants.CONTENTTYPE, Constants.TEXTHTML);
            exchange.sendResponseHeaders(200, 0);
            OutputStream responseBody = exchange.getResponseBody();
            responseBody.write(Constants.getIndexHTML(name));
            responseBody.close();
        } else {
            new NotImplementedHandler().handle(exchange);
        }
    }
}

这甚至更加冗长,但这主要是由于我的虚拟有效载荷解析逻辑,这可以很容易地抽象在一个parseRequestBody 实用方法中,该方法返回一个带有其分配值的参数地图。

除此以外,我们的HttpExchange 对象提供了我们所需要的一切,我们在getIndexHTML 中把给定的name 传递给我们的template

JSON

在一个现代的Web应用程序中,我们很可能会创建一些服务于JSON的REST端点,然后由移动应用程序或一些风味月-.js的JavaScript单页面应用程序使用。

遗憾的是,本地JSON支持并不在Java标准库中,而且在我上次检查时已经被推到了JDK 10中。因此,如果没有像GSON或Jackson这样好的库,对JSON的编排和解编是相当乏味的,因为我们必须在JVM中使用Nashorn JavaScript引擎,这带来了所有的越界问题。

所以JSON绝对是一个使用低足迹库而不是依赖本地方法的领域。

NotImplemented / IgnoreHandler

在我们没有响应或者只是想告诉客户我们没有实现被调用的端点的情况下,我们可以很容易地将交换传递给另一个HttpHandler

当然,对每一种情况都这样做有点麻烦,这通常由一些漂亮的网络框架来处理,但至少它做起来很简单,而且可以被抽象出来,没有什么大惊小怪的。

class NotImplementedHandler implements HttpHandler {
    public void handle(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(501, -1);
        exchange.close();
    }
}

class IgnoreHandler implements HttpHandler {
    public void handle(HttpExchange exchange) throws IOException {
        exchange.sendResponseHeaders(204, -1);
        exchange.close();
    }
}

就这样吧。这段代码只是一个粗糙的例子,绝对不适合用于任何严肃的应用,但我相信它很有趣,它展示了Java拥有编写低开销网络应用的所有工具。

总结

正如我们在上面的代码示例中所看到的,只用Java SE标准库就可以编写一个功能性的Web应用程序。当然,它不像其他语言那样方便或花哨,甚至可能不适合认真使用,但它很容易做到的事实应该使我们能够以更开放的方式思考用Java编写轻量级应用程序的可能性。

因此,Java、JVM甚至Spring(它是非常模块化的)肯定不是Java网络应用程序经常比它们需要的复杂得多的原因。还有另一种方法。

在过去的几年里,几个流行的、经过实战检验的Java微框架开始涌现。其中一个例子是Spark,它为构建网络应用提供了一个现代化的界面,而且占用的空间和开销最小。如果你已经被困在编写臃肿、庞大的Java应用中一段时间了,我鼓励你试试Spark或下面提到的一个微框架。试试吧--也许是一个小的辅助项目--看看它的感觉如何。

只要你愿意,Java也可以变得轻量级和简单。)