app访问java web_Java Web App体系结构
app訪問java web
我曾經利用Servlet,JSP,JAX-RS,Spring框架,Play框架,帶有Facelets的JSF和一些Spark框架。 以我的拙見,所有這些解決方案都遠非面向對象和優雅的。 它們都充滿了靜態方法,不可測試的數據結構和骯臟的駭客。 因此,大約一個月前,我決定創建自己的Java Web框架。 我將一些基本原則納入其基礎:1)沒有NULL,2)沒有公共靜態方法,3)沒有可變的類以及4)沒有類的轉換,反射和instanceof運算符。 這四個基本原則應保證干凈的代碼和透明的體系結構。 這就是Takes框架的誕生方式。 讓我們看看創建了什么以及它如何工作。
教父的制作(1972),弗朗西斯·福特·科波拉
簡而言之,Java Web體系結構
簡單來說,這就是我理解Web應用程序體系結構及其組件的方式。
首先,要創建Web服務器,我們應該創建一個新的網絡套接字 ,該套接字在某個TCP端口上接受連接。 通常是80,但是我將使用8080進行測試。 這是在Java中使用ServerSocket類完成的:
import java.net.ServerSocket; public class Foo {public static void main(final String... args) throws Exception {final ServerSocket server = new ServerSocket(8080);while (true);} }這足以啟動Web服務器。 現在,套接字已準備就緒,正在偵聽8080端口。當有人在其瀏覽器中打開http://localhost:8080時,將建立連接,瀏覽器將永遠旋轉其等待輪。 編譯此代碼段,然后嘗試。 我們只是構建了一個簡單的Web服務器,而沒有使用任何框架。 我們尚未對傳入的連接做任何事情,但是我們也不拒絕它們。 所有這些都在該server對象內對齊。 它是在后臺線程中完成的。 這就是為什么我們需要將while(true)放在后面。 沒有這種無休止的暫停,應用程序將立即完成執行,服務器套接字將關閉。
下一步是接受傳入的連接。 在Java中,這是通過對accept()方法的阻塞調用來完成的:
final Socket socket = server.accept();該方法正在阻塞其線程,并等待新的連接到達。 一旦發生這種情況,它將返回Socket的實例。 為了接受下一個連接,我們應該再次調用accept() 。 因此,基本上,我們的Web服務器應該像這樣工作:
public class Foo {public static void main(final String... args) throws Exception {final ServerSocket server = new ServerSocket(8080);while (true) {final Socket socket = server.accept();// 1. Read HTTP request from the socket// 2. Prepare an HTTP response// 3. Send HTTP response to the socket// 4. Close the socket}} }這是一個無休止的循環,接受一個新的連接,理解它,創建一個響應,返回響應,然后再次接受一個新的連接。 HTTP協議是無狀態的,這意味著服務器不應記住任何先前連接中發生的情況。 它關心的只是此特定連接中的傳入HTTP請求。
HTTP請求來自套接字的輸入流,看起來像多行文本塊。 如果讀取套接字的輸入流,將看到以下內容:
final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()) ); while (true) {final String line = reader.readLine();if (line.isEmpty()) {break;}System.out.println(line); }您將看到如下內容:
GET / HTTP/1.1 Host: localhost:8080 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4客戶端(例如Google Chrome瀏覽器)將此文本傳遞到已建立的連接中。 它連接到localhost端口8080,一旦連接就緒,它將立即將文本發送到其中,然后等待響應。
我們的工作是使用在請求中獲得的信息來創建HTTP響應。 如果我們的服務器非常原始,那么我們基本上可以忽略請求中的所有信息,而只需返回“ Hello,world!”。 到所有請求(為簡單起見,我使用IOUtils ):
import java.net.Socket; import java.net.ServerSocket; import org.apache.commons.io.IOUtils; public class Foo {public static void main(final String... args) throws Exception {final ServerSocket server = new ServerSocket(8080);while (true) {try (final Socket socket = server.accept()) {IOUtils.copy(IOUtils.toInputStream("HTTP/1.1 200 OK\r\n\r\nHello, world!"),socket.getOutputStream());}}} }而已。 服務器已準備就緒。 嘗試編譯并運行它。 將瀏覽器指向http:// localhost:8080 ,您將看到Hello, world! :
$ javac -cp commons-io.jar Foo.java $ java -cp commons-io.jar:. Foo & $ curl http://localhost:8080 -v * Rebuilt URL to: http://localhost:8080/ * Connected to localhost (::1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.37.1 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK * no chunk, no close, no size. Assume close to signal end < * Closing connection 0 Hello, world!這就是構建Web服務器所需的全部。 現在讓我們討論如何使其面向對象和可組合。 讓我們嘗試看看Takes框架是如何構建的。
路由/調度
最重要的步驟是確定誰負責構建HTTP響應。 每個HTTP請求都有1)查詢,2)方法和3)多個標頭。 使用這三個參數,我們需要實例化一個將為我們建立響應的對象。 在大多數Web框架中,此過程稱為請求分派或路由。 這是我們在Takes中的做法:
final Take take = takes.route(request); final Response response = take.act();基本上有兩個步驟。 第一個是創建的一個實例Take從takes ,而第二個是創建的實例Response從take 。 為什么這樣做呢? 主要是為了分開職責。 實例Takes負責調度請求和實例右Take ,和實例Take負責創建響應。
要在Takes中創建一個簡單的應用程序,您應該創建兩個類。 首先,執行Takes :
import org.takes.Request; import org.takes.Take; import org.takes.Takes; public final class TsFoo implements Takes {@Overridepublic Take route(final Request request) {return new TkFoo();} }我們分別為Takes和Take使用這些Ts和Tk前綴。 您應該創建的第二個類是Take的實現:
import org.takes.Take; import org.takes.Response; import org.takes.rs.RsText; public final class TkFoo implements Take {@Overridepublic Response act() {return new RsText("Hello, world!");} }現在是時候啟動服務器了:
import org.takes.http.Exit; import org.takes.http.FtBasic; public class Foo {public static void main(final String... args) throws Exception {new FtBasic(new TsFoo(), 8080).start(Exit.NEVER);} }該FtBasic類執行與上述完全相同的套接字操作。 它在端口8080上啟動服務器套接字,并通過我們提供給其構造函數的TsFoo實例調度所有傳入的連接。 它以無休止的周期進行此調度,每秒檢查一次是否應該使用Exit實例停止。 顯然, Exit.NEVER永遠不會回答“請別停下來”。
HTTP請求
現在,讓我們看看到達TsFoo的HTTP請求中TsFoo什么以及我們可以從中獲得什么。 這是在Takes中定義Request接口的方式:
public interface Request {Iterable<String> head() throws IOException;InputStream body() throws IOException; }該請求分為兩部分:頭部和身體。 根據RFC 2616中的 HTTP規范,頭部包含開始于正文的空行之前的所有行。 框架中有許多有用的裝飾器用于Request 。 例如, RqMethod將幫助您從標題的第一行獲取方法名稱:
final String method = new RqMethod(request).method();RqHref將幫助提取查詢部分并進行解析。 例如,這是請求:
GET /user?id=123 HTTP/1.1 Host: www.example.com此代碼將提取123 :
final int id = Integer.parseInt(new RqHref(request).href().param("id").get(0) );RqPrint可以將整個請求或其主體打印為String :
final String body = new RqPrint(request).printBody();這里的想法是使Request接口保持簡單,并向其裝飾器提供此請求解析功能。 這種方法有助于框架使類保持較小且具有凝聚力。 每個裝飾器都非常小巧,堅固,只能做一件事。 所有這些裝飾器都在org.takes.rq包中。 您可能已經知道, Rq前綴代表Request 。
第一個Real Web App
讓我們創建第一個真正的Web應用程序,它將做一些有用的事情。 我建議從Entry類開始,這是Java從命令行啟動應用程序所必需的:
import org.takes.http.Exit; import org.takes.http.FtCLI; public final class Entry {public static void main(final String... args) throws Exception {new FtCLI(new TsApp(), args).start(Exit.NEVER);} }此類僅包含一個main()靜態方法,當應用程序從命令行啟動時,JVM將調用該方法。 如您所見,它將實例化FtCLI ,為其提供類TsApp和命令行參數的實例。 我們稍后將創建TsApp類。 FtCLI (轉換為“帶有命令行界面的前端”)創建相同FtBasic的實例,將其包裝到一些有用的修飾符中,并根據命令行參數進行配置。 例如,-- --port=8080將轉換為8080端口號,并作為FtBasic構造函數的第二個參數傳遞。
該Web應用程序本身稱為TsApp并擴展了TsWrap :
import org.takes.Take; import org.takes.Takes; import org.takes.facets.fork.FkRegex; import org.takes.facets.fork.TsFork; import org.takes.ts.TsWrap; import org.takes.ts.TsClasspath; final class TsApp extends TsWrap {TsApp() {super(TsApp.make());}private static Takes make() {return new TsFork(new FkRegex("/robots.txt", ""),new FkRegex("/css/.*", new TsClasspath()),new FkRegex("/", new TkIndex()));} }我們將在稍后討論此TsFork課程。
如果您使用的是Maven,則應使用pom.xml開頭:
<?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>foo</groupId><artifactId>foo</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.takes</groupId><artifactId>takes</artifactId><version>0.9</version> <!-- check the latest in Maven Central --></dependency></dependencies><build><finalName>foo</finalName><plugins><plugin><artifactId>maven-dependency-plugin</artifactId><executions><execution><goals><goal>copy-dependencies</goal></goals><configuration><outputDirectory>${project.build.directory}/deps</outputDirectory></configuration></execution></executions></plugin></plugins></build> </project>運行mvn clean package應該在target目錄中構建foo.jar文件,并在target/deps構建所有JAR依賴項的集合。 現在,您可以從命令行運行該應用程序:
$ mvn clean package $ java -Dfile.encoding=UTF-8 -cp ./target/foo.jar:./target/deps/* foo.Entry --port=8080該應用程序已準備就緒,您可以將其部署到Heroku。 只需在存儲庫的根目錄中創建一個Procfile文件,然后將存儲庫推送到Heroku。 這是Procfile外觀:
web: java -Dfile.encoding=UTF-8 -cp target/foo.jar:target/deps/* foo.Entry --port=${PORT}叉車
這個TsFork類似乎是框架的核心元素之一。 它有助于路線傳入的HTTP請求到右收 。 它的邏輯非常簡單,里面只有幾行代碼。 它封裝了“ forks”的集合,它們是Fork<Take>接口的實例:
public interface Fork<T> {Iterator<T> route(Request req) throws IOException; }它唯一的route()方法要么返回一個空的迭代器,要么返回一個具有單個Take的迭代器。 TsFork遍歷所有fork,調用它們的route()方法,直到其中一個返回take 。 一旦出現這種情況, TsFork返回此取給調用者,這是FtBasic 。
現在讓我們自己創建一個簡單的fork。 例如,我們要在請求/status URL時顯示應用程序的/status 。 這是代碼:
final class TsApp extends TsWrap {private static Takes make() {return new TsFork(new Fork.AtTake() {@Overridepublic Iterator<Take> route(Request req) {final Collection<Take> takes = new ArrayList<>(1);if (new RqHref(req).href().path().equals("/status")) {takes.add(new TkStatus());}return takes.iterator();}});} }我相信這里的邏輯很明確。 我們要么返回一個空的迭代器,要么返回一個內部帶有TkStatus實例的迭代器。 如果返回一個空的迭代器, TsFork將嘗試在集合中找到另一個實際上獲取Take實例的fork,以產生Response 。 順便說一句,如果未找到任何內容,并且所有派生都返回空的迭代器,則TsFork將拋出“找不到頁面”異常。
這種確切的邏輯由一個名為FkRegex即用即用的叉子FkRegex ,它嘗試將請求URI路徑與提供的正則表達式進行匹配:
final class TsApp extends TsWrap {private static Takes make() {return new TsFork(new FkRegex("/status", new TkStatus()));} }我們可以組成TsFork類的多層結構。 例如:
final class TsApp extends TsWrap {private static Takes make() {return new TsFork(new FkRegex("/status",new TsFork(new FkParams("f", "json", new TkStatusJSON()),new FkParams("f", "xml", new TkStatusXML()))));} }同樣,我認為這很明顯。 實例FkRegex會問的一個封裝實例TsFork返回一個take,它會嘗試從一個獲取它FkParams封裝。 如果HTTP查詢為/status?f=xml ,則將返回TkStatusXML的實例。
HTTP響應
現在讓我們討論HTTP響應的結構及其面向對象的抽象Response 。 界面外觀如下:
public interface Response {Iterable<String> head() throws IOException;InputStream body() throws IOException; }看起來非常類似于Request ,不是嗎? 好吧,它是相同的,主要是因為HTTP請求和響應的結構幾乎相同。 唯一的區別是第一行。
有很多有用的裝飾器,可以幫助您建立響應。 它們是可組合的 ,這使它們非常方便。 例如,如果要構建一個包含HTML頁面的響應,則可以這樣編寫它們:
final class TkIndex implements Take {@Overridepublic Response act() {return new RsWithStatus(new RsWithType(new RsWithBody("<html>Hello, world!</html>"),"text/html"),200);} }在此示例中,裝飾器RsWithBody創建一個帶有主體但沒有頭的響應。 然后, RsWithType添加標題Content-Type: text/html 。 然后, RsWithStatus確保響應的第一行包含HTTP/1.1 200 OK 。
您可以創建自己的裝飾器,以重用現有的裝飾器。 看看RsPage在RsPage是如何完成的。
模板如何?
如我們所見,返回簡單的“ Hello,world”頁面不是什么大問題。 但是,諸如HTML頁面,XML文檔,JSON數據集等更復雜的輸出呢? 有一些方便的Response裝飾器可以實現所有這些功能。 讓我們從簡單的模板引擎Velocity開始。 好吧,這不是那么簡單。 它相當強大,但是我建議僅在簡單情況下使用它。 下面是它的工作原理:
final class TkIndex implements Take {@Overridepublic Response act() {return new RsVelocity("Hello, ${name}").with("name", "Jeffrey");} }RsVelocity構造函數接受必須為Velocity模板的單個參數。 然后,調用with()方法,將數據注入Velocity上下文中。 當需要呈現HTTP響應時, RsVelocity將根據配置的上下文“評估”模板。 同樣,我建議您僅對簡單輸出使用這種模板方法。
對于更復雜HTML文檔,我建議您將XML / XSLT與Xembly結合使用。 我在之前的幾篇文章中對此想法進行了解釋: 瀏覽器和RESTful API 中的XML + XSLT,以及相同URL中的網站 。 它簡單而強大-Java生成XML輸出,而XSLT處理器將其轉換為HTML文檔。 這就是我們將表示形式與數據分開的方式。 就MVC而言,XSL樣式表是一個“視圖”,而TkIndex是一個“控制器”。
我將很快寫另一篇關于Xembly和XSL模板的文章。
同時,我們將在Takes中為JSF / Facelets和JSP渲染創建裝飾器。 如果您有興趣提供幫助,請分叉框架并提交拉取請求。
持久性呢?
現在,出現的一個問題是如何處理持久性實體,例如數據庫,內存結構,網絡連接等。我的建議是在Entry類內部對其進行初始化,并將其作為參數傳遞給TsApp構造函數。 然后, TsApp將它們傳遞到構造函數的定制需要 。
例如,我們有一個PostgreSQL數據庫,其中包含一些需要渲染的表數據。 這是在Entry類中初始化與它的連接的方式(我使用的是BoneCP連接池):
public final class Entry {public static void main(final String... args) throws Exception {new FtCLI(new TsApp(Entry.postgres()), args).start(Exit.NEVER);}private static Source postgres() {final BoneCPDataSource src = new BoneCPDataSource();src.setDriverClass("org.postgresql.Driver");src.setJdbcUrl("jdbc:postgresql://localhost/db");src.setUser("root");src.setPassword("super-secret-password");return src;} }現在, TsApp的構造TsApp必須接受類型為java.sql.Source的單個參數:
final class TsApp extends TsWrap {TsApp(final Source source) {super(TsApp.make(source));}private static Takes make(final Source source) {return new TsFork(new FkRegex("/", new TkIndex(source)));} }TkIndex類還接受Source類的單個參數。 我相信您知道如何在TkIndex中使用它來獲取SQL表數據并將其轉換為HTML。 這里的要點是,必須在實例化時將依賴項注入到應用程序中(類TsApp的實例)。 這是一種純凈的依賴注入機制,它絕對沒有容器。 在“依賴注入容器是代碼污染者”中閱讀有關它的更多信息。
單元測試
由于每個類都是不可變的,并且所有依賴項僅通過構造函數注入,因此單元測試非常容易。 假設我們要測試TkStatus ,它應該返回HTML響應(我正在使用JUnit 4和Hamcrest ):
import org.junit.Test; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; public final class TkIndexTest {@Testpublic void returnsHtmlPage() throws Exception {MatcherAssert.assertThat(new RsPrint(new TkStatus().act()).printBody(),Matchers.equalsTo("<html>Hello, world!</html>"));} }此外,我們可以開始在測試HTTP服務器的整個應用程序或任何個人起飛 ,并通過一個真實的TCP套接字測試它的行為; 例如(我正在使用jcabi-http發出HTTP請求并檢查輸出):
public final class TkIndexTest {@Testpublic void returnsHtmlPage() throws Exception {new FtRemote(new TsFixed(new TkIndex())).exec(new FtRemote.Script() {@Overridepublic void exec(final URI home) throws IOException {new JdkRequest(home).fetch().as(RestResponse.class).assertStatus(HttpURLConnection.HTTP_OK).assertBody(Matchers.containsString("Hello, world!"));}});} }FtRemote在隨機的TCP端口啟動測試Web服務器,并在提供的FtRemote.Script實例上調用exec()方法。 此方法的第一個參數是剛啟動的Web服務器主頁的URI。
Takes框架的體系結構非常模塊化且可組合。 任何個體取可以進行測試作為一個獨立的部件,絕對獨立于框架和其他需要 。
為什么叫名字?
這就是我經常聽到的問題。 這個想法很簡單,它起源于電影業。 當影片制成,劇組芽許多需要以捕捉現實,把它放在電影。 每次捕獲稱為一次獲取 。
換句話說, 拍攝就像現實的快照。
同樣適用于此框架。 Take每個實例在某個特定時刻代表一個現實。 然后,將該現實以Response的形式發送給用戶。
翻譯自: https://www.javacodegeeks.com/2015/04/java-web-app-architecture-in-takes-framework.html
app訪問java web
總結
以上是生活随笔為你收集整理的app访问java web_Java Web App体系结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lumia 950 XL刷入Win10
- 下一篇: 组装电脑还有好的前景吗组装电脑这个行业怎