如何从头开始以正确的面向对象方式创建Java Web Framework
您如何用Java設計Web應用程序? 您安裝了Spring,閱讀了手冊,創建了控制器 ,創建了一些視圖,添加了一些注釋 ,它就可以工作了。 如果沒有Spring (Ruby中沒有Ruby on Rails,PHP中沒有Symphony,也沒有…等),您將怎么辦? 讓我們嘗試從頭開始創建一個Web應用程序,從一個純Java SDK到一個功能齊全的Web應用程序(由單元測試覆蓋)結束。 幾周前,我錄制了第42號網絡研討會 ,但本文應該對此進行更詳細的說明。
蒂芙尼早餐(Blake Edwards,1961年)
首先,我們必須創建一個HTTP服務器,該服務器將打開服務器套接字,偵聽傳入的連接,讀取他們必須說的所有內容(HTTP請求)并返回任何Web瀏覽器想要的信息(HTTP響應)。 您知道HTTP的工作原理吧? 如果您不這樣做,這里有個簡短的提醒:
Web瀏覽器向服務器發送請求,該請求看起來像這樣(這是純文本數據):
GET /index.html HTTP/1.1 Host: www.example.com服務器必須閱讀此文本,準備答案(必須是瀏覽器可讀HTML頁面),然后像這樣返回:
HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Content-Length: 26<html>Hello, world!</html>而已。 這是一個非常簡單的原始協議。 用Java實現Web服務器也不是那么復雜。 這是一個非常簡單的形式:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Arrays; public class Main {public static void main(String... argv) {try (ServerSocket server = new ServerSocket(8080)) {server.setSoTimeout(1000);while (true) {try (Socket socket = server.accept()) {try (InputStream input = socket.getInputStream();OutputStream output = socket.getOutputStream()) {byte[] buffer = new byte[10000];int total = input.read(buffer);String request = new String(Arrays.copyOfRange(buffer, 0, total));String response = "HTTP/1.1 200 OK\r\n\r\nHello, world!";output.write(response.getBytes());}} catch (SocketTimeoutException ex) {if (Thread.currentThread().isInterrupted()) {break;}}}}} }嘗試運行它,它應該可以工作。 您應該能夠在瀏覽器中打開http://localhost:8080頁面,然后看到Hello, world! , Hello, world! 文本。
它還不是Web應用程序,而是一個框架,它可以將HTTP請求簡單地分配到HTTP響應中。 盡管其中沒有嚴重的面向對象的問題。 這是相當程序化的方法,但確實可行。 現在,我們應該關注一個更重要的問題:如何為Web應用程序添加更多功能,并使其能夠處理不同的頁面,呈現更大的內容并處理錯誤? 上面代碼段中的request變量應該以某種方式轉換為response 。
最簡單的方法是1)將請求轉換為內部包含所有詳細信息的DTO ,然后2)將其發送到知道如何處理DTO數據的“控制器”,然后3)接收響應DTO從控制器中取出數據并呈現響應。 這就是春天和 最 所有其他框架都可以做到。 但是,我們不會走這條路,我們將嘗試做到無DTO且純粹面向對象。
我不得不說,可能有多種設計,全部都是OOP風格。 現在,我僅向您顯示這些選項之一。 您無疑會知道我們幾年前誕生的Takes框架-它具有自己的設計,也面向對象。 但是我現在建議的那個似乎更好。 您可能還會提出其他建議,因此不要猶豫,在下面的評論中發表您的想法,甚至創建GitHub存儲庫并在那里分享您的想法。
我建議我們引入兩個接口: Resource和Output 。 Resource是服務器端實體,它根據傳入的請求參數而發生變化。例如,當我們只知道請求是GET / ,它就是一種資源。 但是,如果我們也知道該請求具有例如Accept: text/plain ,則可以更改該請求并創建一個新請求,該請求將傳遞純文本。 這是界面:
interface Resource {Resource refine(String name, String value); }這是我們創建和變異的方法:
Resource r = new DefaultResource().refine("X-Method", "GET").refine("X-Query", "/").refine("Accept", "text/plain");注意:每次調用.refine()返回一個新的接口Resource實例。 它們都是不可變的,就像對象必須是一樣 。 由于這種設計,我們不會將數據與處理器分開。 資源是數據和處理器。 每個資源都知道如何處理數據,并且僅接收應該接收的數據。 從技術上講,我們只是以面向對象的方式實現請求調度 。
然后,我們需要將資源轉換為響應。 我們賦予資源使其能夠響應的能力。 我們不希望數據以某種DTO的形式泄漏資源。 我們希望該資源打印響應。 如何為資源提供其他方法print() :
interface Resource {Resource refine(String name, String value);void print(Output output); }然后,接口Output看起來像這樣:
interface Output {void print(String name, String value); }這是Output的原始實現:
public class StringBuilderOutput implements Output {private final StringBuilder buffer;StringBuilderOutput(StringBuilder buf) {this.buffer = buf;}@Overridepublic void print(String name, String value) {if (this.buffer.length() == 0) {this.buffer.append("HTTP/1.1 200 OK\r\n");}if (name.equals("X-Body")) {this.buffer.append("\r\n").append(value);} else {this.buffer.append(name).append(": ").append(value).append("\r\n");}} }要構建HTTP響應,我們可以這樣做:
StringBuilder builder = new StringBuilder(); Output output = new StringBuilderOutput(builder); output.print("Content-Type", "text/plain"); output.print("Content-Length", "13"); output.print("X-Body", "Hello, world!"); System.out.println(builder.toString());現在,讓我們創建一個類,該類使用Resource的實例作為調度程序 ,以接收傳入的請求String并生成響應String :
public class Session {private final Resource resource;Session(Resource res) {this.resource = res;}String response(String request) throws IOException {Map<String, String> pairs = new HashMap<>();String[] lines = request.split("\r\n");for (int idx = 1; idx < lines.length; ++idx) {String[] parts = lines[idx].split(":");pairs.put(parts[0].trim(), parts[1].trim());if (lines[idx].empty()) {break;}}String[] parts = lines[0].split(" ");pairs.put("X-Method", parts[0]);pairs.put("X-Query", parts[1]);pairs.put("X-Protocol", parts[2]);App.Resource res = this.resource;for (Map.Entry<String, String> pair : pairs.entrySet()) {res = res.refine(pair.getKey(), pair.getValue());}StringBuilder buf = new StringBuilder();res.print(new StringBuilderOutput(buf));return buf.toString();} }首先,我們解析請求,將其標頭分成幾行,并忽略請求的主體。 您可以使用X-Body作為鍵,修改代碼以解析主體并將其傳遞給refine()方法。 目前,上面的代碼無法做到這一點。 但是你明白了。 片段的解析部分準備了可以在請求中找到的對,并將它們一對一地傳遞給封裝的資源,對其進行變異直到最終形式。 始終返回文本的簡單資源可能如下所示:
class TextResource implements Resource {private final String body;public TextResource(String text) {this.body = text;}@Overridepublic Resource refine(String name, String value) {return this;}@Overridepublic void print(Output output) {output.print("Content-Type", "text/plain");output.print("Content-Length", Integer.toString(this.body.length()));output.print("X-Body", this.body);} }根據查詢的路徑,關注查詢字符串并將請求分派給其他資源的資源可能看起來像這樣:
new Resource() {@Overridepublic Resource refine(String name, String value) {if (name.equals("X-Query")) {if (value.equals("/")) {return new TextResource("Hello, world!");} else if (value.equals("/balance")) {return new TextResource("256");} else if (value.equals("/id")) {return new TextResource("yegor");} else {return new TextResource("Not found!");}} else {return this;}}@Overridepublic void print(final Output output) {throws IllegalStateException("This shouldn't happen");} }我希望你有主意。 上面的代碼很粗略,并且大多數用例都沒有實現,但是如果您感興趣,可以自己做。 該代碼位于yegor256 / jpages存儲庫中。 請毫不猶豫地為請求請求做出貢獻,并使這個小型框架成為現實。
翻譯自: https://www.javacodegeeks.com/2019/03/how-to-create-a-java-web-framework-from-scratch-the-right-object-oriented-way.html
總結
以上是生活随笔為你收集整理的如何从头开始以正确的面向对象方式创建Java Web Framework的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iPhone手机应该如何保养
- 下一篇: 红帽 jboss_红帽正式宣布发布JBo