1. 程式人生 > >給Java開發人員的Play Framework(2.4)介紹 Part1:Play的優缺點以及適用場景

給Java開發人員的Play Framework(2.4)介紹 Part1:Play的優缺點以及適用場景

dead 跟著 框架 sta web 錯誤 gist 解壓 環遊世界

1. 關於這篇系列

這篇系列不是Play框架的Hello World,由於這樣的文章網上已經有非常多。

這篇系列會首先結合實際代碼介紹Play的特點以及適用場景。然後會有幾篇文章介紹Play與Spring,JPA(Hibernate)的集成,以及一些Play應用的最佳實踐。 這期間會在Github上提供一個腳手架項目。方便感興趣的朋友直接動手嘗試。

最後會簡單分析Play的部分源碼。幫助大家理解黑盒子的內部機制。

我水平有限,有錯誤歡迎指出。

2. Play介紹

Play Framework是一個開源的Web框架。背後商業公司是Typesafe。

要介紹Play之前。首先理清Play的兩個不同的分支。 Play 1.x 使用Java開發。最新版本號是1.3.1,僅僅支持Java項目。

從11年開始就進入了維護階段,新項目一般不考慮使用Play1。

Play 2.x 使用Scala和Java開發。同一時候支持Java和Scala項目。 這裏主要介紹最新的Play2.4 for Java。有一點須要提前說明,盡管Play2主要由Scala開發,可是對於項目中的一般開發者而言, 使用Play能夠全然不懂Scala。詳細情況後面會說明。

3. 為什麽要了解Play

如今的Web框架或者類庫能夠說是浩如煙海。近十年來,在Web開發領域,JVM陣營的占有率一直不高。 技術分享 數據來源(http://hotframeworks.com/#rankings)
這是國外開源項目的數據,相對來說國內Java框架的使用率會高一些。而近期幾年,Ruby和Python在國內的開發群體也在不斷壯大。

Java框架在Web領域不那麽受歡迎,主要原因在於開發速度遠落後於其它的開發框架。對於初創公司而言,高速開發出產品投入市場試錯比花半年打磨出一款功能性能齊備的 應用更加重要,而對於成熟產品,也須要高速響應頻繁的需求變化,這方面動態語言又更勝一籌。所以說到Web後端框架的技術選型,除非技術團隊有比較深的JVM背景。 否則會傾向於選擇RoR,Django這些框架。

JVM陣營在Web領域逐漸落後主要有三個原因:編譯的鍋,技術棧的鍋和語言的鍋。

大家都知道Java源碼須要編譯之後才幹執行,直接結果是每次改動源碼都須要重新啟動Webserver才幹看到效果。

假設項目比較小類也少,重新啟動時間還勉強能接受。

我曾經參與的一個項目,使用的是WebLogicserver。Spring容器裏大概有上千個Bean,重新啟動一次至少得花5分鐘。還是優化後的結果。工作時間至少有20%花在重新啟動上了。 盡管如今有JRebel之類的熱載入技術,可是國內使用的相對較少。

Servlet規範在1997年出現,在當時能夠說是非常先進的技術。加上Tomcat的橫空出世。直接促成了JSP的崛起。然而時過境遷,Servlet風光不再。 Web容器存在的必要性也被越來越多的人質疑。

原因就在於人為的將應用與容器剝離, 盡管這樣的做法本意是好的,可是結果就是給開發測試部署帶來一系列集成的問題,如今越來越多的項目開始使用內嵌的Jetty或Tomcat就是一個現實的樣例。 Servlet還帶來一個問題,就是有狀態的server。一旦使用了Session,server就無法享受到水平擴展的長處了。由此不得不採用Session復制或者粘性Session(Sticky Session)的 方案來解決問題,不管採取哪種方案都會有性能損耗。而且推高了技術成本。

Servlet說究竟是Java EE家族的一員。由於Sun的領導(Oracle背鍋), 從Java EE 5開始。Java EE的角色已經從技術創新者轉換為尾隨者,這些年基本上能夠說是跟著開源社區的步子在走的。除了政府大單和跨國企業,你非常難再看見它的身影了。

至於語言。事實上從JDK8開始,Java已經非常好用了。

只是從JDK5到JDK8。十年太長。尤其是在Web。

之前Java陣營受累於沒有成熟的高速開發框架。Spring熱衷於提供各種集成方案,可是配置和使用還是相當的麻煩,直到Spring Boot的出現才有改善。 只是近幾年出現了一些相當優秀的框架,如Dropwizard,Play。Vert.x。 這篇系列要介紹的Play,通過ClassLoader在源碼改動的時候動態載入類,攻克了改動代碼須要重新啟動server的問題,全然拋棄了Servlet技術棧,基於Netty實現了自己的 請求響應接口(Request/Result),基於Play的應用就是無狀態的,另外Play處理請求的方式是無堵塞的(Non-Blocking)。Play2在設計的時候借鑒了RoR的很多長處。 學習Play能夠讓你了解一些現代化框架的特點,同一時候能夠為你打開異步編程世界的大門。Promise已經被Scala,JavaScript等語言大量使用,Actor模型也已經遍地開花。 這些你都能夠直接在Play中使用。或者你想保持原來的編程風格也全然沒有問題。

4. Play的特性

1. Play2的模板引擎

Play2的模板是非常強大而且easy上手的. 相對於Java領域其它模板引擎(Freemarker, Velocity, JSP, Groovy, etc), 主要有三個特點.
1) 簡單易上手, 沒有JSP裏面繁雜的內置對象和指令, 全部功能都通過方法調用完畢.
2) 主流IDE中都支持Play模板的靜態類型檢查, 相似JSP.
3) 支持反向路由.
舉個樣例, 一般系統都會有一個固定的頁面布局, 比方分出頁頭頁尾。假設用JSP或者Velocity之類的模板。 一般都是通過sitemesh+filter或者在每一個頁面include來完畢布局。使用Play模板, 完畢這個功能非常easy。 首先定義一個main頁面 main.scala.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@(title: String = "默認標題")(staticFile: Html = Html(""))(content: Html)

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
    <meta charset="utf-8" />
    <title>@title</title>


</head>

<body>

@header()  <-- 頁頭 -->

@navigator() <!-- 導航 -->

@content

<script src="@routes.Assets.versioned("js/jquery-1.11.2.min.js")"></script>

@staticFile
1
@(title: String = "默認標題")(staticFile: Html = Html(""))(content: Html)

這一部分是參數聲明。這裏聲明了三個參數:title標題, 有默認值;staticFile為html代碼塊, 能夠傳js等。content為頁面內容。

1
2
3
@header()  <-- 頁頭 -->

@navigator() <!-- 導航 -->

這一部分是引用同文件夾下的另外兩個頁面:header.scala.html和navigator.scala.html。

為什麽能這樣引用,由於這些頁面(main,header,navigator)都會被自己主動 編譯成一個方法(準確地說是一個Scala object,只是這裏先當做方法),所以這裏相當於方法調用。相同。這個main也會被編譯成方法。其它頁面能夠調用main來完畢布局。 比如 login.scala.html

1
2
3
4
5
6
7
8
9
@main() {
    <script type="text/javascript">
        FG.user.login();
    </script>
} {
    <div class="login width1200">
    <!-- login -->
    </div>
}

這就是一個簡單的登錄頁面。

登錄頁面調用main頁面的方法,第一個參數不傳使用默認標題。第二個參數傳入登錄頁面的js代碼,第三個參數傳入登錄頁面的html代碼。 這樣就完畢了頁面布局, 沒有隨處可見的include, 也沒有暗箱操作的filter, 全部的一切都是方法調用, 是不是非常簡單清晰?

靜態類型檢查就不說了, 本來Java的一大長處(Que Dian)就是類型檢查,所以在Java裏用Freemarker或者Velocity這樣的模板的做法值得商榷。

反向路由的意思是, 在Play中, 全部的Controller url都配置在一個routes文件裏, 比如

1
GET         /register                           @controllers.user.LoginController.registerPage

之後不管是在Controller裏還是模板中, 都不用硬編碼url。而是使用routes文件。比如在Controller中使用redirect(routes.LoginController.registerPage())就能實現重定向。

而在模板中使用 <a href="@controllers.routes.LoginController.registerPage()">來指向鏈接。

這樣的風格就是REST裏的URI模板。

2. 熱部署

這個上面介紹過。不用重新啟動server。

3. 內置dev/prod環境,內置部署腳本

尋常開發的時候使用run啟動Play,是跑在dev模式。 Play會定時掃描源碼文件夾進行熱更新。而且類都是訪問的時候再載入,提高啟動速度。 使用start啟動項目就執行在prod模式。Play內置dist命令。能夠把全部的文件打包成一個zip,解壓之後直接執行bin文件夾下的可執行文件就可以啟動項目。除了JDK之外無須不論什麽其它外部依賴。

這大大減輕了運維成本,同一時候也能夠非常方便的進行持續集成(CI)。

4. 使用Play開發的Server大部分能做到Stateless

這個之前也說過。Play拋棄了Servlet/JSP裏Session等概念, 內置沒有提供方法將對象與server實例進行綁定(你要使用HashMap存的話Play也沒辦法)。 推薦的做法是使用外部緩存, 比方Redis, Memcached等。可能有人會覺得沒有Session是Play的一個缺點(Play裏的Session和Servlet Session不是一回事), 可是僅僅要你開發過流量大一點的應用, 你就會理解這點。

5. 好用的配置庫

假設你之前開發過Java項目, 肯定寫過**.properties或者管理過一大堆的xml。Java內置庫對properties文件的處理是非常弱的,你不得不自己寫一些工具類去進行處理, 而且properties文件還不支持更復雜的語法。

Play使用Typesafe Config庫,配置文件使用HOCON格式,默認配置文件為application.conf。

你能非常easy讀取裏面的配置, 而且你也能夠把自己的配置寫在裏面。

所以項目中基本不須要使用properties或者xml文件了,除了第三方庫須要的。

6. Play插件

RoR框架之所以好用。主要原因之中的一個就是環繞RoR有相當豐富的插件可供選擇,非常多業務功能甚至都不須要開發就能實現。

Play的插件數量當然相對於RoR還是要少一些, 只是你遇到的需求基本都有現成的插件能夠使用。比方發郵件, 授權和驗證, sitemap生成,第三方登錄等等。

自己寫一個插件也非常簡單。

7. 優秀的測試支持

由於Play誕生的時候TDD已經非常火熱。所以Play對測試的支持非常好。

比如以下的幾行代碼就能對Controller進行測試。

1
2
3
Http.RequestBuilder request = new Http.RequestBuilder().method(POST).uri(routes.LoginController.requestPhoneCode(phone).url());
Result result = route(request);
assertThat(result.status(), is(OK));

Play還內置了對 Selenium WebDriver的支持。能夠模擬瀏覽器進行測試。以下是官方的樣例:

1
2
3
4
5
6
7
8
9
public class BrowserFunctionalTest extends WithBrowser {

    @Test
    public void runInBrowser() {
        browser.goTo("/");
        assertNotNull(browser.$("title").getText());
    }

}

8. 優秀的REST支持

Play2從誕生起就能非常easy的支持RESTful風格的架構(由於Play2在設計的時候REST就已經大行其道), 在Play2中實現RESTful API的演示樣例能夠參考Stackoverflow上的這個回答

5. 使用Play過程中遇到的坑

1. 首次編譯速度過慢

這是Scala的鍋。Scala在編譯過程中要經歷至少30個步驟, 導致編譯速度相當慢。在我的機器上(Core? i5-4590 CPU @ 3.30GHz,RAM 8GB)。編譯100多個Scala類大約須要1到2分鐘。

好在sbt能夠增量編譯, 即首次編譯之後,你再改動代碼。編譯器僅僅會編譯那些它覺得須要編譯的類,編譯幾個類的時候速度非常快,基本刷新頁面就能完畢。

2. IDE的Scala插件偶爾會誤報錯誤

首先得說明。最適合開發Play項目的IDE是IntelliJ IDEA。

如今IDEA最新的Scala插件相比之前的版本號,已經有非常大的提升。 只是偶爾還是會出現誤報的情況,這個問題隨著新版本號插件的公布應該會慢慢解決。

3. Scala和Sbt的學習成本較高

這可能是初次接觸Play的用戶遇到的最大障礙。事實上對於大多數業務開發者來說。這不是問題。使用Play for Java版本號,項目代碼99%都是Java代碼, 而Sbt相似於Maven,一旦項目搭建好後不須要過多接觸,僅僅要學會幾個經常使用的命令就能夠了,比如project root(切換項目), run(啟動server在dev模式)。

我們團隊大部分成員之前都沒有接觸過Scala和Play,經過一兩周的磨合期之後都能非常順利的使用Play進行開發了。

4. Play的API變化速度比較快

Play的版本號號遵循Semantic Versioning,不同主版本號的API變化非常大。比方Play1和Play2就是兩個不同的框架。 而副版本號之間API也會有一些變化,而且不一定全然向後兼容。比如使用Play2.3.x的項目在升級到2.4的時候,須要依照官方提供的遷移手冊進行代碼改動, 不然是執行不了的。

這對於其它背景的開發者來說可能比較easy理解,可是假設是一直習慣於使用Spring MVC或Struts2的話,可能會對這點感到不適。

6.總結

Play2能夠算是一個現代化的框架,吸收了RoR諸多長處。同一時候又攻克了Java開發中的一些痛點,在國外已經被大量使用。

參見 技術分享 數據來源(http://www.infoq.com/research/jvm-web-frameworks)

Play和Spring MVC的定位有些相似。可是比Spring MVC提供更豐富的功能,和Web有關的項目都能夠使用Play。可是假設要用好Play,對團隊有一定的要求。

首先,你的團隊應該不是墨守成規的團隊。

大部分人都害怕變化,這是不爭的事實。

JDK的發展緩慢加上國內的技術氛圍,著實讓Java開發者過了幾年的舒服日子。

你假設是05年學會了ibatis和Spring。然後這十年去環遊世界了,在15年你照樣能輕松找到一份待遇還算能夠的工作。然而事情已經開始發生變化,不會學習可能會被淘汰。

其次。你的團隊應該重視工作效率和質量,而且有時間做出改進。國內非常多團隊信奉的是人海戰術。

以低薪聘請大量不合格的開發者來開發業務功能。 而不是註重單人的工作效率和質量,非常多項目的加班和延期都源於此。這樣的團隊就不適合用Play。非常難想象每天都要加班去應付工作的團隊有時間打磨升級自己的工具和技能。

可是反過來低效率的工具和技能又拖累了自己的工作效率。這是一個惡性循環。

最後。團隊中須要有人對Scala和Sbt有一定的了解。盡管Play有Java版本號能夠使用,可是假設不會Scala和Sbt,在搭建好開發環境。使用一些高級功能(如Filter)的時候可能會遇到麻煩。

下篇我會介紹Play和Spring還有JPA(Hibernate)的集成,畢竟Spring在大部分Java項目還是主流。有問題和建議歡迎指出。

本文借鑒:http://skaka.me/blog/2015/07/27/play1/

給Java開發人員的Play Framework(2.4)介紹 Part1:Play的優缺點以及適用場景