clojure java.jdbc_Clojure驱动的Web开发
Clojure是運行在JVM之上的Lisp方言,提供了強大的函數式編程的支持。由于Java語言進化的緩慢,用Java編寫大型應用程序時,代碼往往十分臃腫,許多語言如Groovy、Scala等都把自身設計為一種可替代Java的,能直接編譯為JVM字節碼的語言。Clojure則提供了Lisp在JVM的實現。
Clojure經過幾年的發展,其社區已經逐漸成熟,有許多活躍的開源項目,足以完成大型應用程序的開發。由Twitter開源的著名的分布式并行計算框架Storm就是用Clojure編寫的。
Clojure提供了對Java的互操作調用,對于那些必須在JVM上繼續開發的項目,Clojure可以利用Java遺留代碼。對大多數基于SSH(Spring Struts Hibernate)的Java項目來說,是時候扔掉它們,用Clojure以一種全新的模式來進行開發了。
本文將簡要介紹使用Clojure構建Web應用程序的開發環境和技術棧。相比SSH,相同的功能使用Clojure僅需極少的代碼,并且無需在開發過程中不斷重啟服務器,可以極大地提升開發效率。
安裝Clojure開發環境
由于Clojure運行在JVM上,我們只需要準備好JDK和Java標配的Eclipse開發環境,就可以開始Clojure開發了!
我們的開發環境是:
Java 8 SDK:可以從Oracle官方網站下載最新64位版本;
Eclipse Luna SR1:可以從Eclipse官方網站下載Eclipse IDE for Java Developers最新64位版本。
安裝完JDK后,通過命令java -version確認JDK是否正確安裝以及版本號:
$ java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
Clojure開發環境可以通過Eclipse插件形式獲得,Counterclockwise提供了非常完善的Clojure開發支持。
首先運行Eclipse,通過菜單“Help”-“Eclipse Marketplace...”打開Eclipse Marketplace,搜索關鍵字counterclockwise,點擊Install安裝:
安裝完Counterclockwise后需要重啟Eclipse,然后,我們就可以新建一個Clojure Project了!
選擇菜單“File”-“New”-“Project...”,選擇“Clojure”-“Clojure Project”,填入名稱“cljweb”,創建一個新的Clojure Project:
找到project.clj文件,把:dependencies中的Clojure版本由1.5.1改為最新版1.6.0:
(defproject cljweb "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.6.0"]])
保存,然后你會注意到Leiningen會自動編譯整個工程。
Leiningen是什么
Leiningen是Clojure的項目構建工具,類似于Maven。事實上,Leiningen底層完全使用Maven的包管理機制,只是Leiningen的構建腳本不是pom.xml,而是project.clj,它本身就是Clojure代碼。
如果Leiningen沒有自動運行,你可以點擊菜單“Project”-“Build Automatically”,勾上后就會讓Leiningen在源碼改動后自動構建整個工程。
第一個Clojure版Hello World
在src目錄下找到自動生成的core.clj文件,注意到已經生成了如下代碼:
(ns cljweb.core)
(defn foo
"I don't do a whole lot."
[x]
(println x "Hello, World!"))
只需要添加一行代碼,調用foo函數:
(println (foo "Clojure"))
然后,點擊菜單“Run”-“Run”就可以直接運行了:
Leiningen會啟動一個REPL,并設置好classpath。第一次REPL啟動會比較慢,原因是JVM的啟動速度慢。在REPL中可以看到運行結果。REPL窗口本身還支持直接運行Clojure代碼,這樣你可以直接在REPL中測試代碼,能極大地提高開發效率。
Clojure函數式編程
Clojure和Java最大的區別在于Clojure的函數是頭等公民,并完全支持函數式編程。Clojure自身提供了一系列內置函數,使得編寫的代碼簡潔而高效。
我們隨便寫幾個函數來看看:
;; 定義自然數序列
(defn natuals []
(iterate inc 1))
;; 定義奇數序列
(defn odds []
(filter odd? (natuals)))
;; 定義偶數序列
(defn evens []
(filter even? (natuals)))
;; 定義斐波那契數列
(defn fib []
(defn fib-iter [a b]
(lazy-seq (cons a (fib-iter b (+ a b)))))
(fib-iter 0 1))
這些函數的特點是擁有Clojure的“惰性計算”特性,我們可以極其簡潔地構造一個無限序列,然后通過高階函數做任意操作:
;; 打印前10個數
(println (take 10 (natuals)))
(println (take 10 (odds)))
(println (take 10 (evens)))
(println (take 10 (fib)))
;; 打印1x2, 2x3, 3x4...
(println (take 10 (map * (natuals)
(drop 1 (natuals)))))
再識Clojure
Clojure自身到底是什么?Clojure自身只是一個clojure.jar文件,它負責把Clojure代碼編譯成JVM可以運行的.class文件。如果預先把Clojure代碼編譯為.class,那么運行時也不需要clojure.jar了。
Clojure自身也作為Maven的一個包,你應該可以在用戶目錄下找到Maven管理的clojure-1.6.0.jar以及源碼:
.m2/repository/org/clojure/clojure/1.6.0/
如果要在命令行運行Clojure代碼,需要自己把classpath設置好,入口函數是clojure.main,參數是要運行的.clj文件:
$ java -cp ~/.m2/repository/org/clojure/clojure/1.6.0/clojure-1.6.0.jar clojure.main cljweb/core.clj
Clojure: Hello, World!
nil
(1 2 3 4 5 6 7 8 9 10)
(1 3 5 7 9 11 13 15 17 19)
(2 4 6 8 10 12 14 16 18 20)
(0 1 1 2 3 5 8 13 21 34)
(2 6 12 20 30 42 56 72 90 110)
在Eclipse環境中,Leiningen已經幫你設置好了一切。
訪問數據庫
Java提供了標準的JDBC接口訪問數據庫,Clojure的數據庫接口clojure.java.jdbc是對Java JDBC的封裝。我們只需要引用clojure.java.jdbc以及對應的數據庫驅動,就可以在Clojure代碼中訪問數據庫。
clojure.java.jdbc是一個比較底層的接口。如果要使用DSL的模式來編寫數據庫代碼,類似Java的Hibernate,則可以考慮幾個DSL庫。我們選擇Korma來編寫訪問數據庫的代碼。
由于Clojure是Lisp方言,它繼承了Lisp強大的“代碼即數據”的功能,在Clojure代碼中,編寫SQL語句對應的DSL十分自然,完全無需Hibernate復雜的映射配置。
我們先配置好MySQL數據庫,然后創建一個表來測試Clojure代碼:
create table courses (
id varchar(32) not null primary key,
name varchar(50) not null,
price real not null,
online bool not null,
days bigint not null
);
新建一個db.clj文件,選擇菜單“File”-“New”-“Other...”,選擇“Clojure”-“Clojure Namespace”,填入名稱db,就可以創建一個db.clj文件。
在編寫代碼前,我們首先要在project.clj文件中添加依賴項:
[org.clojure/java.jdbc "0.3.6"]
[mysql/mysql-connector-java "5.1.25"]
[korma "0.3.0"]
使用Korma操作數據庫十分簡單,只需要先引用Korma:
(ns cljweb.db
(:use korma.db
korma.core))
定義數據庫連接的配置信息:
(defdb korma-db (mysql {:db "test",
:host "localhost",
:port 3306,
:user "www",
:password "www"}))
然后定義一下要使用的entity,也就是表名:
(declare courses)
(defentity courses)
現在,就可以對數據庫進行操作了。插入一條記錄:
(insert courses
(values { :id "s-201", :name "SQL", :price 99.9, :online false, :days 30 })))
使用Clojure內置的map類型,十分直觀。
查詢語句通過select宏實現了SQL DSL到Clojure代碼的自然映射:
(select courses
(where {:online false})
(order :name :asc)))
這完全得益于Lisp的S表達式的威力,既不需要直接拼湊SQL,也不需要重新發明類似HQL的語法。
利用Korma的提供的sql-only和dry-run,可以打印出生成的SQL語句,但實際并不執行。
Web接口
傳統的JavaEE使用Servlet接口來劃分服務器和應用程序的界限,應用程序負責提供實現Servlet接口的類,服務器負責處理HTTP連接并轉換為Servlet接口所需的HttpServletRequest和HttpServletResponse。Servlet接口定義十分復雜,再加上Filter,所需的XML配置復雜度很高,而且測試困難。
Clojure的Web實現最常用的是Ring。Ring的設計來自Python的WSGI和Ruby的Rack,以WSGI為例,其接口設計十分簡單,僅一個函數:
def application(env, start_response)
其中env是一個字典,start_response是響應函數。由于WSGI接口本身是純函數,因此無需Filter接口就可以通過高階函數對其包裝,完成所有Filter的功能。
Ring在內部把Java標準的Servlet接口轉換為簡單的函數接口:
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello World"})
上述函數就完成了Servlet實現類的功能。其中request是一個map,返回值也是一個map,由:status、:headers和:body關鍵字指定HTTP的返回碼、頭和內容。
把一系列handler函數串起來就形成了一個處理鏈,每個鏈都可以對輸入和輸出進行處理,鏈的最后一個處理函數負責根據URL進行路由,這樣,完整的Web處理棧就可以構造出來。
Ring把handler稱為middleware,middleware基于Clojure的函數式編程模型,利用Clojure自帶的->宏就可以直接串起來。
一個完整的Web程序只需要定義一個handler函數,并啟動Ring內置的Jetty服務器即可:
;; hello.clj
(ns cljweb.hello
(:require [ring.adapter.jetty :as jetty]))
(defn handler [request]
{:status 200,
:headers {"Content-Type" "text/html"}
:body "
Hello, world.
"})(defn start-server []
(jetty/run-jetty handler {:host "localhost",
:port 3000}))
(start-server)
運行hello.clj,將啟動內置的Jetty服務器,然后,打開瀏覽器,在地址欄輸入http://localhost:3000/就可以看到響應:
handler函數傳入的request是一個map,如果你想查看request的內容,可以簡單地返回:
(defn handler [request]
{:status 200,
:headers {"Content-Type" "text/html"}
:body (str request)})
URL路由
要處理不同的URL請求,我們就需要在handler函數內根據URL進行路由。Ring本身只負責處理底層的handler函數,更高級的URL路由功能由上層框架完成。
Compojure就是輕量級的URL路由框架,我們要首先添加Compojure的依賴項:
[compojure "1.2.1"]
Compojure提供了defroutes宏來創建handler,它接收一系列URL映射,然后把它們組裝到handler函數內部,并根據URL路由。一個簡單的handler定義如下:
(ns cljweb.routes
(:use [compojure.core]
[compojure.route :only [not-found]]
[ring.adapter.jetty :as jetty]))
(defroutes app-routes
(GET "/" [] "
Index page
")(GET "/learn/:lang" [lang] (str "
Learn " lang "
"))(not-found "
page not found!
"));; start web server
(defn start-server []
(jetty/run-jetty app-routes {:host "localhost",
:port 3000}))
(start-server)
該defroutes創建了3個URL映射:
GET /處理首頁的URL請求,它僅僅簡單地返回一個字符串;
GET /learn/:lang處理符合/learn/:lang這種構造的URL,并且將URL中的參數自動作為參數傳遞進來,如果我們輸入http://localhost:3000/learn/clojure,將得到如下響應:
not-found處理任何未匹配到的URL,例如:
使用模板
復雜的HTML通常不可能在程序中拼接字符串完成,而是通過模板來渲染出HTML。模板的作用是創建一個使用變量占位符和簡單的控制語句的HTML,在程序運行過程中,根據傳入的model——通常是一個map,替換掉變量,執行一些控制語句,最終得到HTML。
已經有好幾種基于Clojure創建的模板引擎,但是基于Django模板設計思想的Selmer最適合HTML開發。
Selmer的使用十分簡單。首先添加依賴:
[selmer "0.7.2"]
然后創建一個cljweb.templ的namespace來測試Selmer:
(ns cljweb.templ)
(use 'selmer.parser)
(selmer.parser/cache-off!)
(selmer.parser/set-resource-path! (clojure.java.io/resource "templates"))
(render-file "test.html" {:title "Selmer Template",
:name "Michael",
:now (new java.util.Date)})
在開發階段,用cache-off!關掉緩存,以便使得模板的改動可以立刻更新。
使用set-resource-path!設定模板的查找路徑。我們把模板的根目錄設置為(clojure.java.io/resource "templates"),因此,模板文件的存放位置必須在目錄resources/templates下:
創建一個test.html模板:
{{ title }}Welcome, {{ name }}
Time: {{ now|date:"yyyy-MM-dd HH:mm" }}
運行代碼,可以看到REPL打印出了render-file函數返回的結果:
配置middleware
Compojure可以方便地定義URL路由,但是,完整的Web應用程序還需要能解析URL參數、處理Cookie、返回JSON類型等,這些任務都可以通過Ring自帶的middleware完成。
我們創建一個cljweb.web的namespace作為入口,Ring自帶的middleware都提供wrap函數,可以用Clojure的->宏把它們串聯起來:
(ns cljweb.web
(:require
[ring.adapter.jetty :as jetty]
[ring.middleware.cookies :as cookies]
[ring.middleware.params :as params]
[ring.middleware.keyword-params :as keyword-params]
[ring.middleware.json :as json]
[ring.middleware.resource :as resource]
[ring.middleware.stacktrace :as stacktrace]
[cljweb.templating :as templating]
[cljweb.urlhandlers :as urlhandlers]))
(def app
(-> urlhandlers/app-routes
(resource/wrap-resource (clojure.java.io/resource "resources")) ;; static resource
templating/wrap-template-response ;; render template
json/wrap-json-response ;; render json
json/wrap-json-body ;; request json
stacktrace/wrap-stacktrace-web ;; wrap-stacktrace-log
keyword-params/wrap-keyword-params ;; convert parameter name to keyword
cookies/wrap-cookies ;; get / set cookies
params/wrap-params ;; query string and url-encoded form
))
每個middleware只負責一個任務,每個middleware接受request,返回response,它們都有機會修改request和response,因此順序很重要:
例如,cookies負責把request的Cookie字符串解析為map并以關鍵字:cookies存儲到request中,后續的處理程序可以直接從request拿到:cookies:
同時,如果在response中找到了:cookies,就把它轉換為Cookie字符串并放入response的:headers中,服務器就會在HTTP響應中加上Set-Cookie的頭:
Ring沒有內置能渲染Selmer模板的middleware,但是middleware不過是一個簡單的函數,我們可以自己編寫一個wrap-template-response,它在response中查找:body以及:body所包含的:model和:template,如果找到了,就通過Selmer渲染模板,并將渲染結果作為string放到response的:body中,服務器就可以讀取response的:body并輸出HTML:
(ns cljweb.templating
(:use ring.util.response
[selmer.parser :as parser]))
(parser/cache-off!)
(parser/set-resource-path! (clojure.java.io/resource "templates"))
(defn- try-render [response]
(let [body (:body response)]
(if (map? body)
(let [[model template] [(:model body) (:template body)]]
(if (and (map? model) (string? template))
(parser/render-file template model))))))
(defn wrap-template-response
[handler]
(fn [request]
(let [response (handler request)]
(let [render-result (try-render response)]
(if (nil? render-result)
response
(let [templ-response (assoc response :body render-result)]
(if (contains? (:headers response) "Content-Type")
templ-response
(content-type templ-response "text/html;charset=utf-8"))))))))
處理REST API
絕大多數Web應用程序都會選擇REST風格的API,使用JSON作為輸入和輸出。在Clojure中,JSON可以直接映射到Clojure的數據類型map,因此,只需添加處理JSON的相關middleware就能處理REST。首先添加依賴:
[ring/ring-json "0.3.1"]
在middleware中,添加wrap-json-response和wrap-json-body:
(def app
(-> urlhandlers/app-routes
(resource/wrap-resource (clojure.java.io/resource "resources")) ;; static resource
templating/wrap-template-response ;; render template
json/wrap-json-response ;; render json
json/wrap-json-body ;; request json
stacktrace/wrap-stacktrace-web ;; wrap-stacktrace-log
keyword-params/wrap-keyword-params ;; convert parameter name to keyword
cookies/wrap-cookies ;; get / set cookies
params/wrap-params ;; query string and url-encoded form
))
wrap-json-body如果讀到Content-Type是application/json,就會把:body從字符串變為解析后的數據格式。wrap-json-response如果讀到:body是一個map或者vector,就會把:body序列化為JSON字符串,并重置:body為字符串,同時添加Content-Type為application/json。
因此,我們在URL處理函數中,如果要返回JSON,只需要返回map,如果要讀取JSON,只需要讀取:body:
(defroutes app-routes
(GET "/rest/courses" [] (response { :courses (get-courses) }))
(POST "/rest/courses" [] (fn [request]
(let [c (:body request)
id (str "c-" (System/currentTimeMillis))]
(create-course! (assoc c :id id, :online true,))
(response (get-course id)))))
(not-found "
page not found!
"))把數據庫操作、模板以及其他的URL處理函數都包含進來,我們就創建好了一個完整的基于Clojure的Web應用程序。
右鍵點擊項目,在彈出菜單選擇“Leiningen”,“Generate Leiningen Command Line”,在彈出的輸入框里:
輸入命令:
lein ring server
將啟動Ring內置的Jetty服務器,并自動打開瀏覽器,定位到http://localhost:3000/:
以這種方式啟動服務器的好處是對代碼做任何修改,無需重啟服務器就可以直接生效,只要在project.clj中加上:
:ring {:handler cljweb.web/app
:auto-reload? true
:auto-refresh? true}
部署
要在服務器部署Clojure編寫的Web應用程序,有好幾種方法,一種是用Leiningen命令:
$ lein uberjar
把所有源碼和依賴項編譯并打包成一個獨立的jar包(可能會很大),打包前需要先編寫一個main函數并在project.clj中指定:
:main cljweb.web
把這個jar包上傳到服務器上就可以直接通過Java命令運行:
$ java -jar cljweb-0.1.0-SNAPSHOT-standalone.jar start
需要加上參數start是因為我們在main函數中通過start參數來判斷是否啟動Jetty服務器:
(defn -main [& args]
(if (= "start" (first args))
(start-server)))
要以傳統的war包形式部署,可以使用命令:
$ lein ring war
這將創建一個.war文件,部署到標準的JavaEE服務器上即可。
小結
Clojure作為一種運行在JVM平臺上的Lisp方言,它既擁有Lisp強大的S表達式、宏、函數式編程等特性,又充分利用了JVM這種高度優化的虛擬機平臺,和傳統的JavaEE系統相比,Clojure不僅代碼簡潔,能極大地提升開發效率,還擁有一種與JavaEE所不同的開發模型。傳統的Java開發人員需要轉變固有思維,利用Clojure替代Java,完全可以編寫出更簡單,更易維護的代碼。
參考資料
Clojure官方網站:了解并下載Clojure的最新版本;
Leiningen官方網站:了解并下載Leiningen的最新版本;
Korma官方網站:獲取Korma源碼并閱讀在線文檔;
Ring官方網站:獲取Ring源碼并閱讀在線文檔;
Compojure官方網站:獲取Compojure源碼并閱讀在線文檔。
關于作者
廖雪峰,精通Java/Objective-C/Python/C#/Ruby/Lisp,獨立iOS開發者,對開源框架有深入研究,著有《Spring 2.0核心技術與最佳實踐》一書,其官方博客是http://www.liaoxuefeng.com/,官方微博是@廖雪峰。
總結
以上是生活随笔為你收集整理的clojure java.jdbc_Clojure驱动的Web开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java时间戳转calender_Jav
- 下一篇: Java怎么重复使用套接字_在java中