可以在线观看的黄色,综合色婷婷一区二区亚洲欧美国产,精品国产一区二区三区小蝌蚪http://www.aygfsteel.com/shinzey/category/44807.html庸人不必自擾,智者何需千慮?zh-cnTue, 14 Feb 2012 07:52:41 GMTTue, 14 Feb 2012 07:52:41 GMT60Java EE 7 新功能前瞻http://www.aygfsteel.com/shinzey/archive/2012/02/13/369874.html蜀山兆孨龘蜀山兆孨龘Mon, 13 Feb 2012 14:23:00 GMThttp://www.aygfsteel.com/shinzey/archive/2012/02/13/369874.htmlhttp://www.aygfsteel.com/shinzey/comments/369874.htmlhttp://www.aygfsteel.com/shinzey/archive/2012/02/13/369874.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/369874.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/369874.html

21 世紀(jì)初,Spring 框架的誕生和崛起讓沉重而腐朽的 J2EE 遭到了當(dāng)頭棒喝,隨后大批開發(fā)人員轉(zhuǎn)投 Spring 陣營,呼吸間就讓 J2EE 陣營大傷元氣。然而這種命懸一線的危機(jī)并沒有造成毀滅性的打擊,尤其是對于 Java 這種提倡開放的平臺而言,取長補(bǔ)短,互相促進(jìn)才是正道。于是,JCP 委員會痛定思痛,在 2006 年推出 Java EE 5 規(guī)范,主要是對 EJB 的開發(fā)進(jìn)行了極大幅度的簡化。2008 年發(fā)布的 Java EE 6 引入了 CDI、BV、JAX-RS 等一系列新功能,并且以配置文件(profile)的方式讓 Java EE 向輕量級邁進(jìn)了一步。特別有趣的是,Spring 框架也開始提供對某些 Java EE 注解的支持,是否標(biāo)志著兩大陣營開始合流?Java EE 7 預(yù)定于今年下半年發(fā)布,目標(biāo)是支持云計算。最近幾年來,云計算一直被炒作,卻從來沒有一個準(zhǔn)確的定義和規(guī)范,希望 Java EE 7 能夠在 Java 界扭轉(zhuǎn)這種尷尬的局面。

下面開始詳細(xì)列舉 Java EE 7 的新功能前瞻,數(shù)據(jù)來源于《Java Magazine 2012-01/02》中的《Cloud/Java EE: Looking Ahead to Java EE 7》一文。Java EE 7 是以“日期驅(qū)動”的方式開發(fā)的,也就是說,在計劃日期到達(dá)前沒有完成的功能都將被推遲到 Java EE 8。

Java EE 7(JSR-342)

  • 主題:讓應(yīng)用程序能夠在私有或公共云上容易地運行。
  • 該平臺將定義一個應(yīng)用程序元數(shù)據(jù)描述符,以描述 PaaS 執(zhí)行環(huán)境(例如多租戶、資源共享、服務(wù)質(zhì)量,以及應(yīng)用程序間的依賴)。
  • 支持 HTML5、WebSocket、JSON 等新標(biāo)準(zhǔn),并為它們一一提供 API。
  • 消除受管 Bean、EJB、Servlet、JSF、CDI 和 JAX-RS 之間不一致的地方。
  • 可能在 Web 配置文件中包含 JAX-RS 2.0 和 JMS 2.0 API 修訂版。
  • 更新一些現(xiàn)有的技術(shù),可能引入用于 Java EE 的并發(fā)工具(JSR-236)和 JCache(JSR-107)。

Java Persistence 2.1(JSR-338)

  • 支持多租戶。
  • 支持存儲過程和廠商函數(shù)。
  • 用規(guī)則(Criteria)進(jìn)行更新和刪除。
  • 支持?jǐn)?shù)據(jù)庫大綱(Scheme)的生成。
  • 持久化上下文的同步。
  • 偵聽器中的 CDI 注入。

JAX-RS 2.0: The Java API for RESTful Web Services(JSR-339)

  • 客戶端 API——底層使用構(gòu)建者模式,可能提供上層封裝。
  • 超媒體——輕松創(chuàng)建和處理關(guān)聯(lián)了資源的鏈接。
  • 使用 Bean 驗證框架來驗證表單或查詢參數(shù)。
  • @Inject 更緊密集成。
  • 服務(wù)端的異步請求處理。
  • 使用“qs”進(jìn)行服務(wù)端的內(nèi)容協(xié)商。

Java Servlet 3.1(JSR-340)

  • 為 Web 應(yīng)用程序優(yōu)化 PaaS 模型。
  • 用于安全、會話和資源的多租戶。
  • 基于 NIO2 的異步 I/O。
  • 簡化的異步 Servlet。
  • 利用 Java EE 并發(fā)工具。
  • 支持 WebSocket。

Expression Language 3.0(JSR-341)

  • ELContext 分離為解析和求值上下文。
  • 可定制的 EL 強(qiáng)迫規(guī)則。
  • 在 EL 表達(dá)式中直接引用靜態(tài)方法和成員。
  • 添加運算符,例如等于、字符串連接和取大小。
  • 與 CDI 集成,例如在表達(dá)式求值前/中/后生成事件。

Java Message Service 2.0(JSR-343)

  • 簡化開發(fā)——改變 JMS 編程模型,讓應(yīng)用程序開發(fā)變得更加簡單容易。
  • 清除/澄清現(xiàn)有規(guī)范中的模糊之處。
  • 與 CDI 集成。
  • 澄清 JMS 和其他 Java EE 規(guī)范之間的關(guān)系。
  • 新的強(qiáng)制性 API允許任何 JMS 提供者能與任何 Java EE 容器集成。
  • 來自平臺的多租戶和其他云相關(guān)的功能。

JavaServer Faces 2.2(JSR-344)

  • 簡化開發(fā)——使配置選項動態(tài)化,使復(fù)合組件中的 cc:interface 可選,F(xiàn)acelet 標(biāo)記庫的速記 URL,與 CDI 集成,JSF 組件的 OSGi 支持。
  • 支持 Portlet 2.0 橋(JSR-329)的實現(xiàn)。
  • 支持 HTML5 的功能,例如 HTML5 表單、元數(shù)據(jù)、頭部和區(qū)段內(nèi)容模型。
  • 流管理,頁面導(dǎo)航事件的偵聽器,以及 fileUploadBackButton 等新組件。

Enterprise JavaBeans 3.2(JSR-345)

  • 增強(qiáng) EJB 架構(gòu)以支持 PaaS,例如多租戶。
  • 對在 EJB 外使用容器管理的事務(wù)進(jìn)行工廠化。
  • 更進(jìn)一步使用注解。
  • 與平臺中的其他規(guī)范對齊和集成。

Contexts and Dependency Injection 1.1(JSR-346)

  • 攔截器的全局排序和管理內(nèi)建上下文的裝飾器 API。
  • 可在 Java EE 容器外啟動的嵌入式模式。
  • 聲明式地控制歸檔中的哪些包和 Bean 將被掃描。
  • 注入日志之類的靜態(tài)成員。
  • 將 Servlet 事件作為 CDI 事件發(fā)送。

Bean Validation 1.1(JSR-349)

  • 與其他 Java EE 規(guī)范集成。
  • JAX-RS:在 HTTP 調(diào)用中驗證參數(shù)和返回值。
  • JAXB:將約束條件轉(zhuǎn)換到 XML 模式描述符中。
  • 方法級別的驗證。
  • 在組集合上應(yīng)用約束條件。
  • 擴(kuò)展模型以支持“與”和“或”風(fēng)格的組合。

JCache: Java Temporary Caching API(JSR-107)

  • 在內(nèi)存中暫存 Java 對象的 API 和語義,包括對象的創(chuàng)建、共享訪問、緩存池、失效,以及跨 JVM 的一致性。

Java State Management(JSR-350)

  • 應(yīng)用程序和 Java EE 容器可使用該 API 將狀態(tài)管理的任務(wù)交給具有不同 QoS 特征的第三方提供者。
  • 基于 Java SE 的調(diào)用者可通過查詢狀態(tài)提供者來訪問狀態(tài)數(shù)據(jù)。
  • 可添加具有不同 QoS 的提供者,API 調(diào)用者能夠按自己的規(guī)則進(jìn)行查詢。

Batch Applications for the Java Platform(JSR-352)

  • 用于批處理應(yīng)用程序的編程模型,以及用于調(diào)度和執(zhí)行工作的運行時。
  • 為標(biāo)準(zhǔn)編程模型定義批處理工作、批處理工作步驟、批處理應(yīng)用程序、批處理執(zhí)行器和批處理工作管理器。

Concurrency Utilities for Java EE(JSR-236)

  • 提供一個整潔、簡單且獨立的 API,使其能用于任何 Java EE 容器中。

Java API for JSON Processing(JSR-353)

  • 處理 JSON 的 Java API。


蜀山兆孨龘 2012-02-13 22:23 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 7:注入?yún)?shù)的自動類型轉(zhuǎn)換http://www.aygfsteel.com/shinzey/archive/2012/01/10/368030.html蜀山兆孨龘蜀山兆孨龘Tue, 10 Jan 2012 05:17:00 GMThttp://www.aygfsteel.com/shinzey/archive/2012/01/10/368030.htmlhttp://www.aygfsteel.com/shinzey/comments/368030.htmlhttp://www.aygfsteel.com/shinzey/archive/2012/01/10/368030.html#Feedback2http://www.aygfsteel.com/shinzey/comments/commentRss/368030.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/368030.html

前面介紹了各種請求參數(shù)的注入,這些參數(shù)在 HTTP 請求中都是以純文本的方式存在的。在處理參數(shù)的時候,往往需要把這些文本參數(shù)轉(zhuǎn)換為 Java 對象。JAX-RS 提供了一些內(nèi)置的規(guī)則里自動完成這種轉(zhuǎn)換。

轉(zhuǎn)換規(guī)則一覽

JAX-RS 提供了四條自動類型轉(zhuǎn)換規(guī)則,下面我們逐條考察。

原始類型

這個早就見識過了,無需多說。舉例回顧一下:

@GET
@Path("{id}")
public Movie getXxx(@PathParam("id") int id) {/*...*/}
    

提供接受單個 String 參數(shù)的構(gòu)造器的類型

這個也不難理解,JAX-RS 會自動調(diào)用該構(gòu)造器創(chuàng)建一個對象:

public class Style {
    public Style(String name) {/* ... */}
    // ...
}

@GET
@Path("{name}")
public Movie getXxx(@PathParam("name") Style style) {
    // JAX-RS 已自動調(diào)用 xxx = new Style(name)
    // ...
}
    

提供靜態(tài)工廠方法 valueOf(String) 的類型

也好理解。特別需要注意的是,所有的枚舉類型都在此列,因為編譯器會自動給枚舉類型加上一個這樣的工廠方法。例如:

public enum Style {/*...*/}

@GET
@Path("{name}")
public Movie getXxx(@PathParam("name") Style style) {
    // JAX-RS 已自動調(diào)用 style = Style.valueOf(name)
    // ...
}
    

類型參數(shù)滿足前兩個條件的 List<T>Set<T>SortedSet<T>

這條規(guī)則適用于多值參數(shù),例如查詢參數(shù):

@GET
@Path("xxx")
public Movie getXxx(@QueryParam("style") Set<Style> styles) {
    // JAX-RS 已自動轉(zhuǎn)換每個 Style 對象并組裝到 Set 中
    // ...
}
    

轉(zhuǎn)換失敗的處理

如果轉(zhuǎn)換失敗,JAX-RS 會根據(jù)情況自動拋出一個包裝了初始異常,但是帶不同 HTTP 錯誤碼的 WebApplicationException:對矩陣參數(shù)(@MatrixParam)、查詢參數(shù) (@QueryParam)或路徑參數(shù)(@PathParam)來說為 HTTP 404 找不到,而對頭部參數(shù)(@HeaderParam)或 Cookie 參數(shù)(@CookieParam)為 HTTP 400 錯誤請求



蜀山兆孨龘 2012-01-10 13:17 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 6:參數(shù)注入http://www.aygfsteel.com/shinzey/archive/2011/12/29/367499.html蜀山兆孨龘蜀山兆孨龘Thu, 29 Dec 2011 08:34:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/12/29/367499.htmlhttp://www.aygfsteel.com/shinzey/comments/367499.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/12/29/367499.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/367499.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/367499.html

《JAX-RS 從傻逼到牛叉 3:路徑匹配》中,我們已經(jīng)見過如何使用 @PathParam@QueryParam@MatrixParam 分別注入 URI 中的路徑參數(shù)、矩陣參數(shù)和查詢參數(shù),以及如何編程訪問這些參數(shù)。本文介紹表單參數(shù)、HTTP 頭部參數(shù)和 Cookie 參數(shù)的注入。

表單參數(shù)

HTTP 請求也可以使用提交表單的方式。這時請求方法一般是 POST,當(dāng)然春哥也無法阻止你用 GET。在前面我們雖然介紹過處理 POST 請求的例子,但那只是利用了 JAX-RS 對 JAXB 的支持,并沒有涉及到對具體請求參數(shù)的注入。JAX-RS 提供了 @FormParam 注解來注入 POST 請求的參數(shù),例如:

@POST
public Response createMovie(@FormParam("title") String title) {
    // 此處省略若干行
}
    

這兒省略了 @Consumes 注解,JAX-RS 會自動默認(rèn)為 @Consumes(MediaType.APPLICATION_FORM_URLENCODED),也就是 application/x-www-form-urlencoded 格式的請求。如果請求格式為 multipart/form-data,就必須顯示指明:

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response createMovie(@FormParam("title") String title) {
    // 此處省略若干行
}
    

JAX-RS 還支持文件的上傳和下載,以后再介紹。

HTTP 頭部參數(shù)

注入 HTTP 頭部參數(shù)簡單得不能再簡單了:

@GET
@Path("xxx")
@Produces(MediaType.TEXT_PLAIN)
public String xxx(@HeaderParam("User-Agent") String userAgent) {
    // 此處省略若干行
}
    

如果有很多頭部參數(shù),為了避免臃腫的參數(shù)列表,可以注入一個頭部對象,然后編程訪問頭部參數(shù):

@GET
@Path("xxx")
@Produces(MediaType.TEXT_PLAIN)
public String xxx(@Context HttpHeaders headers) {
    // 此處省略若干行
}
    

Cookie 參數(shù)

注入 Cookie 參數(shù)同樣的簡單:

@GET
@Path("xxx")
@Produces(MediaType.TEXT_PLAIN)
public String xxx(@CookieParam("userName") String userName) {
    // 此處省略若干行
}
    

如果希望編程訪問,則可以像編程訪問那樣注入一個 HttpHeaders 對象,然后通過它的 getCookies() 方法來獲取所有的 Cookie。



蜀山兆孨龘 2011-12-29 16:34 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 5:資源的動態(tài)定位http://www.aygfsteel.com/shinzey/archive/2011/12/21/366802.html蜀山兆孨龘蜀山兆孨龘Wed, 21 Dec 2011 08:00:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/12/21/366802.htmlhttp://www.aygfsteel.com/shinzey/comments/366802.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/12/21/366802.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/366802.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/366802.html

目前我們的電影服務(wù)只提供了對電影信息的訪問服務(wù),現(xiàn)在我們要再增加兩項級服務(wù),分別用來訪問導(dǎo)演和演員信息。加上原先的電信信息服務(wù),我們把 URI 統(tǒng)一放到 /ms/rest/service/ 的子路徑下。最先想到的方法就是為這三個 URI 分別寫 JAX-RS 服務(wù):

@Singleton
@Path("service/movie")
public class MovieService {
    // 此處省略若干行
}

@Singleton
@Path("service/director")
public class DirectorService {
    // 此處省略若干行
}

@Singleton
@Path("service/director")
public class ActorService {
    // 此處省略若干行
}
    

這種寫法的缺點就是讓三個本來有點關(guān)系(父級 URI 相同)的服務(wù)被放到了毫不相干的三個類里面,不一個個類地查看注解難以看出這點關(guān)系。為此,JAX-RS 提供了動態(tài)資源綁定的功能,讓我們能夠?qū)@種情況做一些整理。

首先,我們引入一個服務(wù)定位器來處理集中管理這三個子級服務(wù):

@Singleton
@Path("service")
public class ServiceLocator {
    @Inject
    private MovieService movieService;
    @Inject
    private DirectorService directorService;
    @Inject
    private ActorService actorService;
    private Map<String, Object> serviceMap;

    @PostConstruct
    private initServiceMap() {
        serviceMap = new HashMap<>();
        serviceMap.put("movie", movieService);
        serviceMap.put("director", directorService);
        serviceMap.put("actor", actorService);
    }

    @Path("{name}")
    public Object locateService(@PathParam("name") String name) {
        Object service = serviceMap.get(name);
        if (service == null) {
            throw new WebApplicationException(Status.SERVICE_UNAVAILABLE);
        }
        return service;
    }
}
    

該類中的 locateService 方法根據(jù)服務(wù)的名稱返回相應(yīng)的服務(wù)實例,注意該方法只有一個 @Path 注解,因為它并不清楚請求的具體內(nèi)容;返回對象的類型為 Object,表明動態(tài)資源定位不要求服務(wù)類實現(xiàn)相同的接口,只需要它們的方法帶有相應(yīng)的 JAX-RS 注解,就能夠被 JAX-RS 自動發(fā)現(xiàn)和處理(專業(yè)術(shù)語稱為 introspect,內(nèi)省),以 MovieService 為例:

@Singleton
public class MovieService {
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Movie find(@PathParam("id") int id) {
        Movie movie = movieDao.get(id);
        if (movie != null) {
            return movie;
        } else {
            throw new WebApplicationException(Status.NOT_FOUND);
        }
    }

    // 此處省略若干行
}

這樣,每個請求實際上都由兩個類先后處理。例如,處理請求 GET /ms/rest/service/movie/1 的時候,先由 ServiceLocator 返回相配的服務(wù)實例 movieService,然后再由該實例的 find 方法返回結(jié)果。比起最開始那三個簡單的類,雖然多了一層調(diào)用,但換來了更加清晰的結(jié)構(gòu)。

動態(tài)資源定位是一個非常靈活強(qiáng)大的功能,用好的話,完全可以把 URI 層次整理成一個類似于文件目錄結(jié)構(gòu)的抽象文件系統(tǒng)。



蜀山兆孨龘 2011-12-21 16:00 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 4:路徑優(yōu)先級規(guī)則http://www.aygfsteel.com/shinzey/archive/2011/12/07/365769.html蜀山兆孨龘蜀山兆孨龘Wed, 07 Dec 2011 07:10:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/12/07/365769.htmlhttp://www.aygfsteel.com/shinzey/comments/365769.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/12/07/365769.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/365769.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/365769.html

籠子大了什么鳥都有。同樣的道理,不論多么細(xì)心地設(shè)計 URI 結(jié)構(gòu),在系統(tǒng)復(fù)雜到一定程度后,仍然難以避免路徑?jīng)_突。為此,JAX-RS 使用一些規(guī)則來定義路徑匹配的優(yōu)先級。

如果某個請求路徑可以對上多個 URI 匹配模式,那么 JAX-RS 就把可能匹配上的 URI 模式先拼接完整,按照下列規(guī)則依次進(jìn)行比較,直到找出最適合的匹配模式:

  1. 首先,字面字符數(shù)量更多的 URI 模式優(yōu)先。“字面字符”就是寫死的路徑段,不包含路徑分隔符 / 和模板參數(shù)。例如 /ms/rest/movie/{id : \\d+} 包含 11 個字面字符。
  2. 其次,模板參數(shù)個數(shù)最多的 URI 模式優(yōu)先。例如 /ms/rest/movie/{id : \\d+} 帶一個模板參數(shù)。
  3. 最后,含正則表達(dá)式的模板參數(shù)個數(shù)最多的 URI 模式優(yōu)先。例如 /ms/rest/movie/{id : \\d+} 帶一個含正則表達(dá)式的模板參數(shù)。

現(xiàn)在看一個例子。回顧一下,/ms/rest/movie/{id : \\d+} 已經(jīng)用來根據(jù) ID 獲取電影信息。為了制造麻煩,現(xiàn)在引入 /ms/rest/movie/{title} 來根據(jù)電影標(biāo)題獲取電影信息。先請你猜一猜 /ms/rest/movie/300 代表啥?ID 為 300 的神秘電影,還是我們可愛的勇士?只能跟著規(guī)則一條一條地看:

  1. 首先,兩個 URI 匹配模式的字面字符都是 11,下一步。
  2. 其次,兩個 URI 匹配模式都帶一個模板參數(shù),下一步。
  3. 最后,只有 /ms/rest/movie/{id : \\d+} 帶了一個含正則表達(dá)式的模板參數(shù),勝利!所以返回 ID 為 300 的片片。

傳說這三條規(guī)則能夠覆蓋 90% 以上的情景。不過我們馬上就能造出一個打破規(guī)則的東西:/ms/rest/movie/{title : [ \\w]+}。經(jīng)過測試,/ms/rest/movie/300 會匹配上 /ms/rest/movie/{id : \\d+}。如何解釋?JAX-RS 規(guī)范文檔 3.7.2 定義了完整的匹配規(guī)則,對于這兩個簡單的 URI 匹配模式,似乎一直進(jìn)行到底都無法比較出優(yōu)先級。莫非有另外的潛規(guī)則?或者是 JAX-RS 的實現(xiàn)(參考實現(xiàn)為 Jersey)自行規(guī)定?但無論如何,搞出這種怪物本身就是一個設(shè)計錯誤,所以也不必去深究原因。



蜀山兆孨龘 2011-12-07 15:10 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 3:路徑匹配http://www.aygfsteel.com/shinzey/archive/2011/10/09/360199.html蜀山兆孨龘蜀山兆孨龘Sun, 09 Oct 2011 04:43:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/10/09/360199.htmlhttp://www.aygfsteel.com/shinzey/comments/360199.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/10/09/360199.html#Feedback1http://www.aygfsteel.com/shinzey/comments/commentRss/360199.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/360199.html

JAX-RS 的核心功能是處理向 URI 發(fā)送的請求,所以它提供了一些匹配模式以便簡化對 URI 的解析。樓主在本系列的上一篇文章中已經(jīng)使用了最簡單的路徑參數(shù),本文將介紹一些稍微高級點的咚咚。

模板參數(shù)

前面已經(jīng)見過用 @Path("{id}")@PathParam("id") 來匹配路徑參數(shù) id。這種匹配方式可以被嵌入到 @Path 注解中的任何地方,從而匹配多個參數(shù),例如下面的代碼用來查找 ID 在某一范圍內(nèi)的電影:

        @GET
        @Path("{min}~{max}")
        @Produces(MediaType.APPLICATION_JSON)
        public List<Movie> findMovies(@PathParam("min") int min, @PathParam("max") int max) {
    

于是,GET /ms/rest/movie/5~16 就將返回 ID 為 5 到 16 的電影。此處的 minmax 已被自動轉(zhuǎn)換為 int 類型。JAX-RS 支持多種類型的自動轉(zhuǎn)換,詳見 @PathParam 的文檔。

根據(jù) HTTP 規(guī)范,參數(shù)可能會編碼。默認(rèn)情況下,JAX-RS 會自動解碼。如果希望得到未解碼的參數(shù),只需在參數(shù)上再加個 @Encoded 注解。該注解適用于大多數(shù) JAX-RS 注入類型,但并不常用。

模板參數(shù)雖然靈活,也可能會帶來歧義。例如想用 {firstName}-{lastName} 匹配一個人的姓名,但恰好某人的名(lastName)含有“-”字符,像 O-live K 這種,匹配后就會變成姓 live-K,名 O。這種場景很難避免,一種簡單的解決方法就是對參數(shù)值進(jìn)行兩次編碼,然后在服務(wù)端代碼解碼一次,因為 JAX-RS 默認(rèn)會進(jìn)行一次解碼,或者加上 @Encoded 注解,自己進(jìn)行兩次解碼。

另外,在一個復(fù)雜系統(tǒng)中,多個 @Path 可能會造成路徑混淆,例如 {a}-{b}{a}-z 都能匹配路徑 a-z。雖然 JAX-RS 定義了一些規(guī)則來指定匹配的優(yōu)先級,但這些規(guī)則本身就比較復(fù)雜,并且也不能完全消除混淆。樓主認(rèn)為,設(shè)計一個 REST 系統(tǒng)的核心就是對 URI 的設(shè)計,應(yīng)當(dāng)小心處理 URI 的結(jié)構(gòu),合理分類,盡量保證匹配的唯一性,而不要過度使用晦澀的優(yōu)先級規(guī)則。樓主將在下一篇文章介紹優(yōu)先級規(guī)則。

正則表達(dá)式

模板參數(shù)可以用一個正則表達(dá)式進(jìn)行驗證,寫法是在模板參數(shù)的標(biāo)識符后面加一個冒號,然后跟上正則表達(dá)式字符串。例如在根據(jù) ID 查詢電影信息的代碼中,模板參數(shù) {id} 只能是整數(shù),于是代碼可以改進(jìn)為:

        @GET
        @Path("{id : \\d+}")
        @Produces(MediaType.APPLICATION_JSON)
        public List<Movie> findMovies(@PathParam("min") int min, @PathParam("max") int max) {
    

冒號左右的空格將被忽略。用正則表達(dá)式驗證數(shù)據(jù)很有局限性,可惜 JAX-RS 目前并不能直接集成 Bean 驗證框架,因此復(fù)雜的驗證只能靠自己寫代碼。

查詢參數(shù)

查詢參數(shù)很常見,就是在 URI 的末尾跟上一個問號和一系列由“&”分隔的鍵值對,例如查詢 ID 為 5 到 16 的電影也可以設(shè)計為 /ms/rest/movie?min=5&max=16。JAX-RS 提供了 QueryParam 來注入查詢參數(shù):

        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public List<Movie> findMovies(@DefaultValue("0") @QueryParam("min") int min,
                @DefaultValue("0") @QueryParam("max") int max) {
    

查詢參數(shù)是可選的。如果 URI 沒有設(shè)定某個查詢參數(shù),JAX-RS 就會根據(jù)情況為其生成 0、空字符串之類的默認(rèn)值。如果要手動設(shè)定默認(rèn)值,需要像上面的代碼一樣用 @DefaultValue 注解來指定。另外還可以加上 Encoded 注解來得到編碼的原始參數(shù)。

有的查詢參數(shù)是一對多的鍵值對,例如 /xyz?a=def&a=pqr,這種情況只需將注入的參數(shù)類型改為 List 即可。

矩陣參數(shù)

矩陣參數(shù)應(yīng)該屬于 URI 規(guī)范中的非主流類型,但它實際上比查詢參數(shù)更靈活,因為它可以嵌入到 URI 路徑中的任何一段末尾(用分號隔開),用來標(biāo)識該段的某些屬性。例如 GET /ms/rest/movie;year=2011/title;initial=A 表示在 2011 年出品的電影中查找首字母為 A 的標(biāo)題。year 是電影的屬性,而 initial 是標(biāo)題的屬性,這比把它們都作為查詢參數(shù)放在末尾更直觀可讀。匹配 URI 的時候,矩陣參數(shù)將被忽略,因此前面的 URI 匹配為 /ms/rest/movie/title。矩陣參數(shù)可以用 @MatrixParam 來注入:

        @GET
        @Path("title")
        @Produces(MediaType.APPLICATION_JSON)
        public List<String> findTitles(@MatrixParam("year") int year,
                @MatrixParam("initial") String initial) {
    

如果 URI 的多個段中含有相同名稱的矩陣參數(shù),例如 /abc;name=XXX/xyz;name=OOO,這種直接注入就失效了,只能用下面要講的編程式訪問來取得。

編程式訪問

如果簡單的注入不能達(dá)到目的,就需要通過注入 PathSegmentUriInfo 對象來直接編程訪問 URI 的信息。

一個 PathSegment 對象代表 URI 中的一個路徑段,可以從它得到矩陣參數(shù)。它可以通過 @PathParam 來注入,這要求該路徑段必須整個被定義為一個模板參數(shù)。例如下面的代碼也可以用來處理 GET /ms/rest/movie/{id}

        @GET
        @Path("{id}")
        @Produces(MediaType.APPLICATION_JSON)
        public Movie findMovie(@PathParam("id") PathSegment ps) {
    

@PathParam 也可以注入多個段,如果想把 /a/b/c/d 匹配到 /a/{segments}/d,直接注入一個字符串顯然不行,因為 b/c 是兩個路徑段。唯一的選擇是把注入的類型改為 List<PathSegment>。樓主嚴(yán)重不推薦用一個模板參數(shù)匹配多個路徑段,因為這很容易干擾其他匹配的設(shè)計,最后搞成一團(tuán)亂麻。URI 路徑段應(yīng)當(dāng)盡量設(shè)計得簡單明晰,再輔以矩陣參數(shù)或查詢參數(shù)就能應(yīng)付大多數(shù)場景。不論對服務(wù)端還是客戶端開發(fā)人員來說,簡潔的 URI 既便于管理,又便于使用。網(wǎng)上有不少關(guān)于 URI 設(shè)計指南的文章,此處不再贅述。

如果想完全手動解析路徑,則可以用 @Context 注入一個 UriInfo 對象,通過此對象可以得到 URI 的全部信息,詳見 API 文檔。例如:

        @GET
        @Path("{id}/{segments}")
        @Produces(MediaType.PLAIN_TEXT)
        public String getInfo(@PathParam("id") int id, @Context UriInfo uriInfo) {
    

UriInfo 主要用在某些特殊場合下起輔助作用,設(shè)計良好的 URI 用普通的注入就能完成大部分匹配。


工欲善其事必先利其器,為此 JAX-RS 提供了這些利器來解析 URI。至于如何用這些器來做出一個好系統(tǒng),則還是依賴于 URI 本身的設(shè)計。



蜀山兆孨龘 2011-10-09 12:43 發(fā)表評論
]]>
JPA 應(yīng)用技巧 3:映射多對多的關(guān)聯(lián)表http://www.aygfsteel.com/shinzey/archive/2011/09/27/359547.html蜀山兆孨龘蜀山兆孨龘Tue, 27 Sep 2011 03:04:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/09/27/359547.htmlhttp://www.aygfsteel.com/shinzey/comments/359547.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/09/27/359547.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/359547.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/359547.html

實體間的多對多的關(guān)聯(lián)需要一張關(guān)聯(lián)表。如果直接使用 ManyToMany 來映射,JPA 就會隱式地幫我們自動管理關(guān)聯(lián)表,代碼寫出來和其他類型的關(guān)聯(lián)差別不大。例如,某州炒房團(tuán)需要一個炒房跟蹤系統(tǒng),那么該系統(tǒng)中的炒房客和房子就是多對多的關(guān)系:

        public class Speculator implements Serializable {
            @Id
            private Integer id;
            @ManyToMany
            @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                    inverseJoinColumns = @JoinColumn(name = "house_id"))
            private List<House> houses;
            // 此處省略若干行
        }

        public class House implements Serializable {
            @Id
            private Integer id;
            @ManyToMany(mappedBy = "houses")
            private List<Speculator> speculators;
            // 此處省略若干行
        }
    

如果炒房客 s 要賣掉房子 h(嚴(yán)格點說是賣掉房子的產(chǎn)權(quán)部分),那么系統(tǒng)執(zhí)行的代碼差不多就是 s.getHouses().remove(h)。看似簡單,然而底層的操作卻性能低下:JPA 會先從數(shù)據(jù)庫中取出炒房客的所有房產(chǎn)(s.getHouses()),然后再刪除指定的那套房子;從數(shù)據(jù)庫層面上看,這將先從關(guān)聯(lián)表(speculator_house)中找到該炒房客的所有房子的外鍵,然后從 house 表載入這些 House 對象,最后才從 speculator_house 刪除關(guān)聯(lián)。在 ORM 出現(xiàn)前,這種操作只需要改關(guān)聯(lián)表,根本不用關(guān)心其他房子。這種簡單的多對多映射寫法將關(guān)聯(lián)表隱藏起來,雖然簡化了代碼,卻也可能帶來性能隱患。

很自然地可以想到,如果把關(guān)聯(lián)表也映射成實體類,就能解決這個問題。speculator_house 包含兩個外鍵,可用作聯(lián)合主鍵。如果把它映射為 SpeculatorHouse 類,則該類與 SpeculatorHouse 都是多對一的關(guān)系。關(guān)聯(lián)表實體類的代碼如下(EmbeddedId 的映射技巧見《JPA 應(yīng)用技巧 2:主鍵外鍵合體映射》):

        @Embeddable
        public class SpeculatorHouseId implements Serializable {
            private Integer speculatorId;
            private Integer houseId;
            // 此處省略若干行
        }

        @Entity
        @Table(name = "speculator_house")
        public class SpeculatorHouse implements Serializable {
            @EmbeddedId
            private SpeculatorHouseId id;
            @MapsId("speculatorId")
            @ManyToOne
            private Speculator speculator;
            @MapsId("houseId")
            @ManyToOne
            private House house;
            // 此處省略若干行
        }
    

SpeculatorHouse 也要增加相應(yīng)的關(guān)聯(lián)信息:

        public class Speculator implements Serializable {
            @Id
            private Integer id;
            @ManyToMany
            @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                    inverseJoinColumns = @JoinColumn(name = "house_id"))
            private List<House> houses;
            @OneToMany(mappedBy = "speculator")
            private List<SpeculatorHouse> speculatorHouses;
            // 此處省略若干行
        }

        public class House implements Serializable {
            @Id
            private Integer id;
            @ManyToMany(mappedBy = "houses")
            private List<Speculator> speculators;
            @OneToMany(mappedBy = "house")
            private List<SpeculatorHouse> speculatorHouses;
            // 此處省略若干行
        }
    

這樣既保留了多對多關(guān)系,又映射了關(guān)聯(lián)表,然后就可以根據(jù)實際情況選擇隱式或顯示的關(guān)聯(lián)表管理。例如,要得到一個炒房客的全部房子,就使用隱式管理:s.getHouses();而要刪除炒房客和某套房子的關(guān)聯(lián),則用顯示管理:delete from SpeculatorHouse sh where sh.speculator = :s and sh.house = :h



蜀山兆孨龘 2011-09-27 11:04 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 2:開發(fā)一個簡單的服務(wù)http://www.aygfsteel.com/shinzey/archive/2011/09/20/359085.html蜀山兆孨龘蜀山兆孨龘Tue, 20 Sep 2011 09:22:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/09/20/359085.htmlhttp://www.aygfsteel.com/shinzey/comments/359085.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/09/20/359085.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/359085.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/359085.html

JAX-RS 使用注解進(jìn)行配置,所以用它開發(fā) REST 風(fēng)格的服務(wù)非常簡單。樓主在本文用一個小例子來說明 JAX-RS 的基本用法。


假設(shè)樓主要開發(fā)一個小電影服務(wù),客戶端可以通過請求 URI 對電影進(jìn)行 CRUD 操作。為簡明起見,這兒不使用數(shù)據(jù)庫,只在內(nèi)存中模擬。先用一個非常簡單的 Movie 類,在后續(xù)的文章中根據(jù)情況逐步擴(kuò)充:

        public class Movie {
            private int id;
            private String title;
            // 此處省略若干行
        }
    

嗯,就是一個很普通的 JavaBean,實際項目中可以根據(jù)需要加上 @Entity 等注解。接下來看看如何編寫 JAX-RS 服務(wù)。


一個 JAX-RS 服務(wù)就是一個使用了 JAX-RS 注解來將 HTTP 請求綁定到方法的 Java 類,一共支持兩種類型:單請求對象或單例對象。單請求對象意味著每來一個請求,就創(chuàng)建一個服務(wù)對象,在請求結(jié)束時銷毀。單例對象則意味著只有一個服務(wù)對象處理所有的請求,從而可以在多個請求間維持服務(wù)狀態(tài)。JAX-RS 服務(wù)可通過繼承 javax.ws.rs.core.Application 來定義,其中的 getClasses 方法返回單請求對象的類型,getSingletons 方法返回單例對象的類型。這兩個方法是可選的。在 Java EE 6 環(huán)境中,如果這兩個方法都返回 null 或者空集合,那么應(yīng)用程序中的所有 JAX-RS 都將被部署。這時可以用 CDI 的 @javax.inject.Singleton 或者 EJB 的 @javax.ejb.Singleton 注解來指定單例對象。

如果電影服務(wù)的上下文根路徑為 http://localhost/ms,而樓主希望將服務(wù)部署到 http://localhost/ms/rest 下面,只需要寫一個類:

        @ApplicationPath("rest")
        public class RestApplication extends Application {
        }
    

@ApplicationPath 注解指定所有服務(wù)的相對基址,如果為空字符串,則直接使用上下文根路徑。另一種配置方式是在 web.xml 文件中進(jìn)行聲明,那是為了使 JAX-RS 能在 Servlet 容器(例如 Tomcat)中運行,此處略過。這項配置必不可少,否則無法部署服務(wù)。


很好很強(qiáng)大,現(xiàn)在開始編寫電影服務(wù)類 MovieService,先看看聲明和初始化:

        @Singleton
        @Path("movie")
        public class MovieService {
            private AtomicInteger ai;
            private ConcurrentMap<Integer, Movie> movieMap;

            @PostConstruct
            private void init() {
                ai = new AtomicInteger();
                movieMap = new ConcurrentHashMap<>();
                int id = ai.getAndIncrement();
                movieMap.put(id, new Movie().setId(id).setTitle("Avatar"));
            }
    

因為樓主只需要一個“內(nèi)存數(shù)據(jù)庫”,所以用單例對象即可,此處使用 CDI 的 @javax.inject.Singleton 來聲明單例。@Path 聲明了一個服務(wù),它指示 MovieService 負(fù)責(zé)處理發(fā)送到 http://localhost/ms/rest/movie 的請求。路徑的拼接方式非常直觀。init 方法帶有 @PostConstruct 注解,因此將在 MovieService 構(gòu)造完成后立即調(diào)用,它向 movieMap 中存入了一個 ID 為 0 的 Movie 對象。為簡化代碼,Movie 的設(shè)置方法都返回 this,有點偽造構(gòu)建者模式的味道。


接下來看看如何處理 HTTP 請求。

GET

GET 請求用于獲取一個或多個資源。在本例中用來獲取一部電影的信息:

        @GET
        @Path("{id}")
        @Produces(MediaType.APPLICATION_JSON)
        public Movie find(@PathParam("id") int id) {
            Movie movie = movieMap.get(id);
            if (movie != null) {
                return movie;
            } else {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
        }
    

該方法標(biāo)注了 @GET,表示用來處理向 http://localhost/ms/rest/movie/{id} 發(fā)送的 GET 請求。@Path 再次用來綁定路徑,注意其參數(shù) {id},它帶有花括號,對應(yīng) URI 的最后一段,也正好和方法參數(shù) id@PathParam 的值相對應(yīng)。這種參數(shù)還有很多高級用法,以后再介紹。@Produces 注解指定輸出格式為 JSON。JAX-RS 內(nèi)置了很多格式,詳見 MediaType 的文檔。如果找到了相應(yīng) ID 的對象,則將其返回,JAX-RS 會自動加上響應(yīng)碼 200 OK;否則拋出異常,錯誤碼為 404 Not Found。

例如,通過瀏覽器訪問 http://localhost/ms/rest/movie/0,得到的結(jié)果為 {"@id":"0","@title":"Avatar"}。

POST

POST 請求用于創(chuàng)建一個資源。在本例中用來創(chuàng)建一部電影:

        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        public Response create(Movie movie) {
            int id = ai.getAndIncrement();
            movieMap.put(id, movie.setId(id));
            return Response.created(URI.create(String.valueOf(id))).build();
        }
    

由于沒有 @Path 注解,所以 POST 請求的目標(biāo)就直接是 http://localhost/ms/rest/movie。Consumes@Produces 相反,表示接受的數(shù)據(jù)類型,此處 JAX-RS 會自動把 JSON 數(shù)據(jù)轉(zhuǎn)換為 Movie 對象。返回的響應(yīng)碼為 201 Created,并且?guī)в兴鶆?chuàng)建資源的 URI。

例如,向 http://localhost/ms/rest/movie 發(fā)送 POST 請求,正文為 {"@title": "007"},則可以從 FireBug 的網(wǎng)絡(luò)監(jiān)控中看到返回的響應(yīng)碼,以及頭部中 Location 的值為 http://localhost:8080/rest/service/movie/1。多次發(fā)送該 POST 請求,將會創(chuàng)建多個資源,以保證 POST 不是冪等的。

PUT

PUT 請求用于創(chuàng)建或更新一個資源。與 POST 不同,PUT 請求要指定某個特定資源的地址。在本例中用來更新一部電影的信息:

        @PUT
        @Path("{id}")
        @Consumes(MediaType.APPLICATION_JSON)
        public Response update(@PathParam("id") int id, Movie movie) {
            movie.setId(id);
            if (movieMap.replace(id, movie) != null) {
                return Response.ok().build();
            } else {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
        }
    

更新成功就返回 200 OK,否則返回 404 Not Found。這兒先把 movie 對象的 ID 強(qiáng)制改為 URI 所指定的,以免出現(xiàn)不一致。也可以根據(jù)需求,將不一致作為異常處理,給客戶端返回一個錯誤碼。

順便啰嗦一句,反正代碼在自己手中,樓主也可以把 PUT 搞成非冪等的,例如將 PUT 當(dāng)成 POST 來處理,就像以前把 GET 和 POST 一視同仁那樣。不過咱既然在搞 JAX-RS,就還是要沾染一點 REST 風(fēng)格,嚴(yán)格遵守 HTTP 才是。

DELETE

DELETE 請求用于刪除一個資源。在本例中用來刪除一部電影:

        @DELETE
        @Path("{id}")
        public Response delete(@PathParam("id") int id) {
            if (movieMap.remove(id) != null) {
                return Response.ok().build();
            } else {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
        }
    

沒什么特別的,該說的前面都說了。

HEAD 和 OPTIONS 請求就忽略吧,用得不太多,也同樣挺簡單的。


JAX-RS 服務(wù)的部署和部署常規(guī) Web 程序一樣,打包成 war 文件就可以了。最后贊一下 NetBeans 可以為 REST 風(fēng)格的服務(wù)自動生成測試頁面,很好用,雖然在 Firefox 下頁面顯示不正常(對此我已經(jīng)提了一個 bug),但 IE 是可以的。



蜀山兆孨龘 2011-09-20 17:22 發(fā)表評論
]]>
JAX-RS 從傻逼到牛叉 1:REST 基礎(chǔ)知識http://www.aygfsteel.com/shinzey/archive/2011/09/16/358799.html蜀山兆孨龘蜀山兆孨龘Fri, 16 Sep 2011 07:31:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/09/16/358799.htmlhttp://www.aygfsteel.com/shinzey/comments/358799.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/09/16/358799.html#Feedback3http://www.aygfsteel.com/shinzey/comments/commentRss/358799.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/358799.html

JAX-RS(JSR 311 - Java™ API for RESTful Web Services,用于 REST 風(fēng)格的 Web 服務(wù)的 Java™ API)是 Java EE 6 規(guī)范的一部分,其目標(biāo)在于簡化和標(biāo)準(zhǔn)化用 Java 開發(fā) REST 風(fēng)格的 Web 服務(wù)。雖然 Java EE 6 剛出爐的時候,樓主也從頭到尾看過這份規(guī)范,但苦于沒有實際的項目練手,看過又忘了,現(xiàn)在最多算達(dá)到大成傻逼的境界。這次邊看邊寫,期望完成后至少能破入小成牛逼。先從 REST 本身開始。


REST(REpresentational State Transfer,代表性狀態(tài)傳輸)自稱是一種風(fēng)格而非標(biāo)準(zhǔn),這在樓主看來有炒作的嫌疑。如果僅僅是一種風(fēng)格,那么不同的框架如何兼容?所以才有 JAX-RS 的誕生。REST 最大的貢獻(xiàn)是帶來了 HTTP 協(xié)議的復(fù)興。為什么叫復(fù)興呢?本來 HTTP 的功能挺豐富的,可惜長期以來只被用作傳輸數(shù)據(jù),大好青年被埋沒了。樓主記得剛開始學(xué) Servlet 的時候,一向是把 doGetdoPost 兩個方法一視同仁的,因為書上這么教,很多 Web 框架也這么搞,以至于弄了很久才搞清楚 GETPOST 是兩種不同的請求。現(xiàn)在 REST 拍磚說道,HTTP 早就定義好了一堆操作,以前一直被混淆使用,現(xiàn)在應(yīng)該重新賦予它們本來的意義了,而且如果充分發(fā)揮 HTTP 的功能,完全能夠勝任分布式應(yīng)用的開發(fā)(傳說中的 SOA)。


SOA 的理念在于將系統(tǒng)設(shè)計為一系列可重用的、解耦的、分布式的服務(wù)。這也不是新鮮玩意兒了,早期有 CORBA,稍晚有 SOAP 等等。REST 作為后起之秀,能夠快速崛起,也必有其非同尋常的特色。下面一一列舉。

可尋址性(Addressability)

系統(tǒng)中的每個資源都可以通過唯一標(biāo)識符來訪問。小插一句,“標(biāo)識”的正確讀音是 biāozhì。REST 使用 URI(統(tǒng)一資源標(biāo)識符)管理資源的地址。URI 的概念不解釋。一個 URI 可以指向一個或者多個資源。

統(tǒng)一的受限接口(The Uniform, Constrained Interface)

實際上強(qiáng)調(diào)了 HTTP 操作的原意。REST 主要使用了 GET、PUT、DELETE、POST、HEAD 和 OPTIONS 這 6 種操作。此處有兩個曾經(jīng)被忽略的 HTTP 概念:冪等(idempotent)和安全(safe)。冪等應(yīng)該是 HTTP 從數(shù)學(xué)借來的一個術(shù)語(原始的數(shù)學(xué)意義樓主也不懂),意味著若干次請求的副作用與單次請求相同,或者根本沒有副作用。GET、PUT、DELETE、HEAD 和 OPTIONS 都是冪等的:GET、HEAD 和 OPTIONS 都是讀操作,容易理解;PUT 用于創(chuàng)建或更新已知 URI 的資源,多次創(chuàng)建或更新同一個資源顯然和一次的效果相同;DELETE 刪除資源,亦然。安全表示操作不會影響服務(wù)器的狀態(tài)。GET、HEAD 和 OPTIONS 是安全的。POST 既不冪等又不安全,因為和 PUT 不同,POST 創(chuàng)建資源的時候并不知道資源的 URI,所以多個 POST 請求將會創(chuàng)建多個資源。

面向表象(Representation-Oriented)

表象這個詞有點拗口,傳聞在一個 REST 風(fēng)格的系統(tǒng)中,服務(wù)端和客戶端之間傳輸?shù)倪诉司褪潜硐蟆硐罂梢允羌兾谋尽ML、JSON……或者自編的山寨格式。唉,不就是數(shù)據(jù)么?只不過可以用任意的格式來傳輸,因為 HTTP 正文里面啥都能放。Content-Type 頭用來聲明格式,一般是 MIME(多用途因特網(wǎng)郵件擴(kuò)展),像 text/plaintext/htmlapplication/pdf 這些。MIME 可以帶屬性,例如 text/html; charset=UTF-8

無態(tài)通信(Communicate Statelessly)

REST 服務(wù)器只管理資源,而不會像 Web 服務(wù)器一樣記錄客戶的會話狀態(tài),這些應(yīng)該由客戶端來管理,如此就能增強(qiáng) REST 服務(wù)器的伸縮性。此處的客戶端可以是客戶端程序、瀏覽器,甚至一個 Web 應(yīng)用。總之,REST 只負(fù)責(zé)庫管啦!

HATEOAS

猛詞砸來了!HATEOAS = Hypermedia As The Engine Of Application State,超媒體作為應(yīng)用狀態(tài)的引擎,怎么看起來比 SaaS(Software as a Service,軟件作為服務(wù))還要嚇人呢?超文本不過是一只紙老虎,超媒體也瞞不過樓主的天眼:超媒體就是是由文字、圖像、圖形、視頻、音頻……鏈成一體的大雜燴!很簡單的一個例子,有些坑爹的電影網(wǎng)站經(jīng)常發(fā)布一些內(nèi)嵌了廣告的電影,播放的時候會彈出廣告窗口,里面很多鏈接,你去點兩下就中招了:這個電影文件就算是超媒體。

其實這個詞最關(guān)鍵的地方是“狀態(tài)引擎”。例如樓主在去網(wǎng)購,先選了幾個東西,接下來可以干啥呢?可以繼續(xù)選、可以把購物車清空、可以結(jié)賬……樓主可以從現(xiàn)狀“轉(zhuǎn)換”到其他某些狀態(tài),而控制狀態(tài)轉(zhuǎn)換的那個咚咚就被冠名為狀態(tài)引擎。多么聰明的詞匯啊!樓主發(fā)現(xiàn)凡是高手都是造詞磚家呀!用超媒體來控制狀態(tài)轉(zhuǎn)換,就是 HATEOAS:你是要繼續(xù)看電影還是看廣告?看哪個廣告?自己慢慢考慮……


REST 相比 CORBA、SOAP、WS-* 之流確實獨樹一幟,但也難逃玩弄概念的嫌疑。記得大學(xué)里講數(shù)據(jù)庫的老師說過:“你們現(xiàn)在學(xué)了這么多理論,其實以后在工作中未必管用。在大街上隨便找一個軟件培訓(xùn)學(xué)校出來的小伙子,他哪兒懂什么第二第三范式啊?但卻能把數(shù)據(jù)庫玩兒得飛轉(zhuǎn)!”



蜀山兆孨龘 2011-09-16 15:31 發(fā)表評論
]]>
JPA 應(yīng)用技巧 2:主鍵外鍵合體映射http://www.aygfsteel.com/shinzey/archive/2011/09/13/358519.html蜀山兆孨龘蜀山兆孨龘Tue, 13 Sep 2011 03:27:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/09/13/358519.htmlhttp://www.aygfsteel.com/shinzey/comments/358519.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/09/13/358519.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/358519.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/358519.html

考慮兩個具有一對一關(guān)聯(lián)的實體類,受控方除了有一個自己的主鍵,還有一個引用主控方的外鍵。如果主控方和受控方是同生同滅的關(guān)系,換句話說,雙方的一對一關(guān)聯(lián)一旦確立就不可更改,就可以考慮讓雙方共享相同的主鍵,簡化受控方的表結(jié)構(gòu)。下面就讓樓主通過實例來說明如何用 JPA 2.0 來實現(xiàn)這種映射。

盯著眼前的電腦,樓主想到了一個也許不太貼切的例子:員工和公司配的電腦的關(guān)系。員工的主鍵就是工號。雖然電腦可能被換掉,但電腦實體完全可以用工號做主鍵,只是把電腦配置的詳細(xì)信息改掉而已。此處的難點就在與如何將電腦的主鍵字段同時映射一個員工,請看樓主是怎么一步步推導(dǎo)出來的。

一開始是最想當(dāng)然的寫法:

        public class Computer implements Serializable {
            @Id
            @OneToOne
            private Employee employee;
            // 此處省略若干行
        }
    

然而根據(jù)規(guī)范,只有這些類型可以作為主鍵:Java 原始類型(例如 int)、原始包裝類型(例如 Integer)、Stringjava.util.Datejava.sql.Datejava.math.BigDecimaljava.math.BigInteger。所以直接拿 Employee 做主鍵是不行的。順便提一下,也許某些 JPA 實現(xiàn)自己做了擴(kuò)展,使得可以直接拿實體類做主鍵,這已經(jīng)超出了 JPA 規(guī)范的范疇,此處不討論。

直接映射是行不通了,那有什么間接的方式嗎?這時樓主想起了一個特殊的注解:EmbeddedId。該注解的本意是用于聯(lián)合主鍵,不過變通一下,是否可以將 Employee 包裝進(jìn)一個嵌入式主鍵,然后再將這個嵌入式主鍵作為 Computer 的主鍵以達(dá)到目的?帶著這種想法,樓主有了下面的代碼:

        @Embeddable
        public class ComputerId implements Serializable {
            @OneToOne
            private Employee employee;
            // 此處省略若干行
        }
    
        public class Computer implements Serializable {
            @EmbeddedId
            private ComputerId id;
            // 此處省略若干行
        }
    

現(xiàn)在又出現(xiàn)了新的問題:JPA 不支持定義在嵌入式主鍵類中的關(guān)聯(lián)映射。好在天無絕人之路,EmbeddedId 的文檔中直接指出,可以使用 MapsId 注解來間接指定嵌入式主鍵類中的關(guān)聯(lián)映射,而且還附帶了一個例子!于是最終的成品就出爐了:

        @Embeddable
        public class ComputerId implements Serializable {
            private String employeeId;
            // 此處省略若干行
        }
    
        public class Computer implements Serializable {
            @EmbeddedId
            private Employee employee;
            @MapsId("employeeId")
            @OneToOne
            private Employee employee;
            // 此處省略若干行
        }
    

唯一的遺憾是,邏輯上的主控方 Employee 現(xiàn)在不得不成為受控方了,好在可以定義級聯(lián)操作來達(dá)到差不多的效果:

        public class Employee implements Serializable {
            @Id
            private String id;
            @OneToOne(mappedBy = "employee", cascade = CascadeType.ALL)
            private Computer computer;
            // 此處省略若干行
        }
    

雖然做到了,但確實挺繞的。希望未來版本的 JPA 能直接支持將實體類作為主鍵,樓主個人覺得不是一個技術(shù)問題。



蜀山兆孨龘 2011-09-13 11:27 發(fā)表評論
]]>
讓 JSF 支持 multipart/form-data 編碼類型的 POST 請求http://www.aygfsteel.com/shinzey/archive/2011/09/09/358357.html蜀山兆孨龘蜀山兆孨龘Fri, 09 Sep 2011 03:23:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/09/09/358357.htmlhttp://www.aygfsteel.com/shinzey/comments/358357.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/09/09/358357.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/358357.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/358357.html

JSF 都 2.0 了,尼瑪居然還是無法識別 multipart/form-data(至少參考實現(xiàn) Mojarra 如此),綁定的屬性一個都讀不出來,坑爹啊!!!既然官方不支持,咱就自己搞一個補(bǔ)丁,讓它不從也得從。

說到底,JSF 的屬性綁定功能不外乎是利用 FacesServlet 幫我們把參數(shù)進(jìn)行轉(zhuǎn)換和校驗,然后拼裝成受管 Bean。而 FacesServlet 必定是通過 HttpServletRequest 的相關(guān)方法來讀取請求參數(shù),因此只需要在 FacesServlet 之前增加一個過濾器,把文本類型的 Part 參數(shù)轉(zhuǎn)換為普通參數(shù)就可以了。至于文件類型的 Part,則可以使用一些第三方工具來綁定,例如使用 PrimeFaces 將文件綁定到 File 對象。下圖是這種思路的流程:

multipart/form-data 的處理流程

第二步的過濾器就是給 JSF 打的“補(bǔ)丁”:

        /**
         * 該過濾器幫助 {@link FacesServlet} 識別 {@code multipart/form-data} 格式的 POST 請求。
         */
        @WebFilter("*.xhtml")
        public class MultipartFilter implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                String contentType = request.getContentType();
                // 判斷請求的格式是否為 multipart/form-data。
                if (contentType != null && contentType.startsWith("multipart/form-data")) {
                    MultipartRequest req = new MultipartRequest((HttpServletRequest) request);
                    for (Part part : req.getParts()) {
                        // 如果該 Part 的內(nèi)容類型不為 null, 那它是一個文件,忽略。
                        if (part.getContentType() == null) {
                            req.addParameter(part.getName(), decode(part));
                        }
                    }
                    chain.doFilter(req, response);
                } else {
                    chain.doFilter(request, response);
                }
            }

            @Override
            public void destroy() {
            }

            /**
             * 將 {@link Part} 對象解碼為字符串。
             */
            private String decode(Part part) throws IOException {
                try (InputStreamReader in = new InputStreamReader(
                        part.getInputStream(), StandardCharsets.UTF_8)) {
                    char[] buffer = new char[64];
                    int nread = 0;
                    StringBuilder sb = new StringBuilder();
                    while ((nread = in.read(buffer)) != -1) {
                        sb.append(buffer, 0, nread);
                    }
                    return sb.toString();
                }
            }

            /**
             * {@link HttpServletRequest} 中的請求參數(shù)映射是只讀的,所以自己封裝一個。
             */
            private static class MultipartRequest extends HttpServletRequestWrapper {
                private Map<String, String[]> parameters;

                public MultipartRequest(HttpServletRequest request) {
                    super(request);
                    parameters = new HashMap<>();
                }

                private void addParameter(String name, String value) {
                    String[] oldValues = parameters.get(name);
                    if (oldValues == null) {
                        parameters.put(name, new String[] {value});
                    } else {
                        int size = oldValues.length;
                        String[] values = new String[size + 1];
                        System.arraycopy(oldValues, 0, values, 0, size);
                        values[size] = value;
                        parameters.put(name, values);
                    }
                }

                @Override
                public String getParameter(String name) {
                    String[] values = getParameterValues(name);
                    return values == null ? null : values[0];
                }

                @Override
                public Map<String, String[]> getParameterMap() {
                    return parameters;
                }

                @Override
                public Enumeration<String> getParameterNames() {
                    final Iterator<String> it = parameters.keySet().iterator();
                    return new Enumeration<String>() {
                        @Override
                        public boolean hasMoreElements() {
                            return it.hasNext();
                        }

                        @Override
                        public String nextElement() {
                            return it.next();
                        }
                    };
                }

                @Override
                public String[] getParameterValues(String name) {
                    return parameters.get(name);
                }
            }
        }
    

這兒噴一下,為什么 HttpServletRequest 里面的請求參數(shù)映射是只讀的,非得要通過繼承 HttpServletRequestWrapper 這種蛋疼的彎路來黑?



蜀山兆孨龘 2011-09-09 11:23 發(fā)表評論
]]>
JPA 應(yīng)用技巧 1:實體類和實體 DAO 模板http://www.aygfsteel.com/shinzey/archive/2011/09/07/358223.html蜀山兆孨龘蜀山兆孨龘Wed, 07 Sep 2011 09:40:00 GMThttp://www.aygfsteel.com/shinzey/archive/2011/09/07/358223.htmlhttp://www.aygfsteel.com/shinzey/comments/358223.htmlhttp://www.aygfsteel.com/shinzey/archive/2011/09/07/358223.html#Feedback8http://www.aygfsteel.com/shinzey/comments/commentRss/358223.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/358223.html

最近閑來無事(樓主確實太懶了),重翻舊賬,搗鼓了下 JPA 2.0,通過不斷地寫代碼和谷歌,又有了一些舊瓶裝新酒的發(fā)現(xiàn)和吐槽。樓主將在這一系列文章中慢慢道來。本次開篇帶來的是兩個模板類:用作實體類基礎(chǔ)框架的 AbstractEntity, 以及實現(xiàn)了對實體的基本 CRUD 操作的 BasicEntityDao

一個實體類必須實現(xiàn) java.io.Serializable 接口,必須有一個 ID 字段作為主鍵,且最好覆蓋 equalshashCode 方法。因為實體類和數(shù)據(jù)表有對應(yīng)關(guān)系,所以往往根據(jù) ID 來實現(xiàn) equalshashCode。這很自然地可以引出一個模板類,所有的實體類都可以從它繼承:

        /**
         * 該類可作為實體類的模板,其 {@link #equals(Object)} 和 {@link hashCode()} 方法基于主鍵實現(xiàn)。
         * 子類只需要實現(xiàn) {@link #getId()} 方法。
         */
        public abstract class AbstractEntity implements Serializable {
            /**
             * 返回主鍵。
             */
            public abstract Object getId();

            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null || getClass() != obj.getClass()) {
                    return false;
                }
                return getId() == null ? false
                        : getId().equals(((AbstractEntity) obj).getId());
            }

            @Override
            public int hashCode() {
                return Objects.hashCode(getId());
            }
        }
    

針對主鍵的類型,AbstractEntity 可以進(jìn)一步擴(kuò)展。例如,可以擴(kuò)展出一個 UuidEntity,它使用隨機(jī)生成的 UUID 作為主鍵:

        @MappedSuperclass
        public class UuidEntity extends AbstractEntity {
            @Id
            private String id;

            @Override
            public String getId() {
                return id;
            }

            @PrePersist
            private void generateId() {
                // 僅在持久化前生成 ID,提升一點性能。
                id = UUID.randomUUID().toString();
            }
        }
    

繼續(xù)發(fā)揮想象,讓它支持樂觀鎖:

        @MappedSuperclass
        public class VersionedUuidEntity extends UuidEntity {
            @Version
            private int version;
        }
    

這兒順便插嘴吐槽下主鍵的類型。用整數(shù)還是 UUID 好呢?這個問題在網(wǎng)上也是爭論紛紛。在樓主看來,兩者各有優(yōu)劣:整數(shù)主鍵性能高,可讀性也好,但會對數(shù)據(jù)遷移,例如合并兩個數(shù)據(jù)庫,造成不小的麻煩,因為可能出現(xiàn)一大堆重復(fù)的主鍵;UUID 性能差些,看起來晃眼,雖然據(jù)說有些數(shù)據(jù)庫針對性地做了優(yōu)化,想來也不大可能優(yōu)于整數(shù),不過好處就是理論上出現(xiàn)重復(fù)主鍵的概率比中彩票還小(福彩除外)。說這么一大堆,其實還是蠻糾結(jié)啊……樓主一般傾向于用 UUID,只要服務(wù)器的配置夠勁,想來不會出現(xiàn)明顯的性能問題。

接下來說說 BasicEntityDao,它提供了基本的 CRUD 實現(xiàn),可以用來為會話 Bean 做模板:

        /**
         * 提供了對實體進(jìn)行基本 CRUD 操作的實現(xiàn),可作為會話 Bean 的模板。
         */
        public abstract class BasicEntityDao<T> {
            private Class<T> entityClass;
            private String entityClassName;
            private String findAllQuery;
            private String countQuery;

            protected BasicEntityDao(Class<T> entityClass) {
                this.entityClass = Objects.requireNonNull(entityClass);
                entityClassName = entityClass.getSimpleName();
                findAllQuery = "select e from " + entityClassName + " e";
                countQuery = "select count(e) from " + entityClassName + " e";
            }

            /**
             * 返回用于數(shù)據(jù)庫操作的 {@link EntityManager} 實例。
             */
            protected abstract EntityManager getEntityManager();

            public void persist(T entity) {
                getEntityManager().persist(entity);
            }

            public T find(Object id) {
                return getEntityManager().find(entityClass, id);
            }

            public List<T> findAll() {
                return getEntityManager().createQuery(findAllQuery, entityClass).getResultList();
            }

            public List<T> findRange(int first, int max) {
                return getEntityManager().createQuery(findAllQuery, entityClass)
                        .setFirstResult(first).setMaxResults(max).getResultList();
            }

            public long count() {
                return (Long) getEntityManager().createQuery(countQuery).getSingleResult();
            }

            public T merge(T entity) {
                return getEntityManager().merge(entity);
            }

            public void remove(T entity) {
                getEntityManager().remove(merge(entity));
            }
        }
    

子類只需要提供 getEntityManager() 的實現(xiàn)即可。假設(shè)樓主要做一個養(yǎng)雞場管理系統(tǒng),對雞圈進(jìn)行操作的會話 Bean 就可以簡單地寫成:

        @Stateless
        public class CoopDao extends BasicEntityDao<Coop> {
            @Persistence
            private EntityManager em;

            public CoopDao() {
                super(Coop.class);
            }

            @Override
            protected EntityManager getEntityManager() {
                return em;
            }

            // 更多方法……
        }
    


蜀山兆孨龘 2011-09-07 17:40 發(fā)表評論
]]>
JSF 2.0 中定義受管 Bean 的三種途徑的比較http://www.aygfsteel.com/shinzey/archive/2010/05/15/321050.html蜀山兆孨龘蜀山兆孨龘Sat, 15 May 2010 11:10:00 GMThttp://www.aygfsteel.com/shinzey/archive/2010/05/15/321050.htmlhttp://www.aygfsteel.com/shinzey/comments/321050.htmlhttp://www.aygfsteel.com/shinzey/archive/2010/05/15/321050.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/321050.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/321050.htmlweb/WEB-INF/faces-config.xml 不再必需。本文介紹并比較了三種途徑來定義可從頁面上的 EL 表達(dá)式中引用的受管 Bean。  閱讀全文

蜀山兆孨龘 2010-05-15 19:10 發(fā)表評論
]]>
在 Servlet 3.0 中處理 multipart/form-data 請求的兩個輔助方法http://www.aygfsteel.com/shinzey/archive/2010/04/24/319255.html蜀山兆孨龘蜀山兆孨龘Sat, 24 Apr 2010 03:59:00 GMThttp://www.aygfsteel.com/shinzey/archive/2010/04/24/319255.htmlhttp://www.aygfsteel.com/shinzey/comments/319255.htmlhttp://www.aygfsteel.com/shinzey/archive/2010/04/24/319255.html#Feedback0http://www.aygfsteel.com/shinzey/comments/commentRss/319255.htmlhttp://www.aygfsteel.com/shinzey/services/trackbacks/319255.htmljavax.servlet.http.Part 接口,從而提供了對 multipart/form-data 類型的 HTTP 請求的直接支持,我們從此就可以擺脫諸如 Apache Commons FileUpload 之類的第三方依賴。然而,該支持太過單純,所以還要多做點事情,以便能更有效地進(jìn)行工作。我將在本文中介紹兩個輔助方法。  閱讀全文

蜀山兆孨龘 2010-04-24 11:59 發(fā)表評論
]]>
主站蜘蛛池模板: 定远县| 邻水| 竹溪县| 多伦县| 永宁县| 子洲县| 迁安市| 闵行区| 呈贡县| 芷江| 淮南市| 长宁区| 东海县| 平乡县| 万全县| 西乡县| 米脂县| 平舆县| 建平县| 焉耆| 秭归县| 内黄县| 江山市| 大洼县| 镇巴县| 武陟县| 马边| 芜湖市| 德格县| 拜泉县| 福贡县| 白山市| 瑞金市| 米易县| 镇宁| 清水县| 裕民县| 新河县| 焉耆| 东山县| 莱芜市|