在线观看亚洲人成网站,久久亚洲AV无码西西人体,亚洲人成人无码网www电影首页 http://www.tkk7.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.tkk7.com/shinzey/archive/2012/02/13/369874.html蜀山兆孨龘蜀山兆孨龘Mon, 13 Feb 2012 14:23:00 GMThttp://www.tkk7.com/shinzey/archive/2012/02/13/369874.htmlhttp://www.tkk7.com/shinzey/comments/369874.htmlhttp://www.tkk7.com/shinzey/archive/2012/02/13/369874.html#Feedback0http://www.tkk7.com/shinzey/comments/commentRss/369874.htmlhttp://www.tkk7.com/shinzey/services/trackbacks/369874.html

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

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

Java EE 7(JSR-342)

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

Java Persistence 2.1(JSR-338)

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

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

  • 客戶端 API——底層使用構建者模式,可能提供上層封裝。
  • 超媒體——輕松創建和處理關聯了資源的鏈接。
  • 使用 Bean 驗證框架來驗證表單或查詢參數。
  • @Inject 更緊密集成。
  • 服務端的異步請求處理。
  • 使用“qs”進行服務端的內容協商。

Java Servlet 3.1(JSR-340)

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

Expression Language 3.0(JSR-341)

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

Java Message Service 2.0(JSR-343)

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

JavaServer Faces 2.2(JSR-344)

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

Enterprise JavaBeans 3.2(JSR-345)

  • 增強 EJB 架構以支持 PaaS,例如多租戶。
  • 對在 EJB 外使用容器管理的事務進行工廠化。
  • 更進一步使用注解。
  • 與平臺中的其他規范對齊和集成。

Contexts and Dependency Injection 1.1(JSR-346)

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

Bean Validation 1.1(JSR-349)

  • 與其他 Java EE 規范集成。
  • JAX-RS:在 HTTP 調用中驗證參數和返回值。
  • JAXB:將約束條件轉換到 XML 模式描述符中。
  • 方法級別的驗證。
  • 在組集合上應用約束條件。
  • 擴展模型以支持“與”和“或”風格的組合。

JCache: Java Temporary Caching API(JSR-107)

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

Java State Management(JSR-350)

  • 應用程序和 Java EE 容器可使用該 API 將狀態管理的任務交給具有不同 QoS 特征的第三方提供者。
  • 基于 Java SE 的調用者可通過查詢狀態提供者來訪問狀態數據。
  • 可添加具有不同 QoS 的提供者,API 調用者能夠按自己的規則進行查詢。

Batch Applications for the Java Platform(JSR-352)

  • 用于批處理應用程序的編程模型,以及用于調度和執行工作的運行時。
  • 為標準編程模型定義批處理工作、批處理工作步驟、批處理應用程序、批處理執行器和批處理工作管理器。

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 發表評論
]]>
JAX-RS 從傻逼到牛叉 7:注入參數的自動類型轉換http://www.tkk7.com/shinzey/archive/2012/01/10/368030.html蜀山兆孨龘蜀山兆孨龘Tue, 10 Jan 2012 05:17:00 GMThttp://www.tkk7.com/shinzey/archive/2012/01/10/368030.htmlhttp://www.tkk7.com/shinzey/comments/368030.htmlhttp://www.tkk7.com/shinzey/archive/2012/01/10/368030.html#Feedback2http://www.tkk7.com/shinzey/comments/commentRss/368030.htmlhttp://www.tkk7.com/shinzey/services/trackbacks/368030.html

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

轉換規則一覽

JAX-RS 提供了四條自動類型轉換規則,下面我們逐條考察。

原始類型

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

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

提供接受單個 String 參數的構造器的類型

這個也不難理解,JAX-RS 會自動調用該構造器創建一個對象:

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

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

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

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

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

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

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

這條規則適用于多值參數,例如查詢參數:

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

轉換失敗的處理

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



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

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

表單參數

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

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

這兒省略了 @Consumes 注解,JAX-RS 會自動默認為 @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 頭部參數

注入 HTTP 頭部參數簡單得不能再簡單了:

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

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

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

Cookie 參數

注入 Cookie 參數同樣的簡單:

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

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



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

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

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

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

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

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

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

@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 方法根據服務的名稱返回相應的服務實例,注意該方法只有一個 @Path 注解,因為它并不清楚請求的具體內容;返回對象的類型為 Object,表明動態資源定位不要求服務類實現相同的接口,只需要它們的方法帶有相應的 JAX-RS 注解,就能夠被 JAX-RS 自動發現和處理(專業術語稱為 introspect,內省),以 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 返回相配的服務實例 movieService,然后再由該實例的 find 方法返回結果。比起最開始那三個簡單的類,雖然多了一層調用,但換來了更加清晰的結構。

動態資源定位是一個非常靈活強大的功能,用好的話,完全可以把 URI 層次整理成一個類似于文件目錄結構的抽象文件系統。



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

籠子大了什么鳥都有。同樣的道理,不論多么細心地設計 URI 結構,在系統復雜到一定程度后,仍然難以避免路徑沖突。為此,JAX-RS 使用一些規則來定義路徑匹配的優先級。

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

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

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

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

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



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

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

模板參數

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

        @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 已被自動轉換為 int 類型。JAX-RS 支持多種類型的自動轉換,詳見 @PathParam 的文檔。

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

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

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

正則表達式

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

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

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

查詢參數

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

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

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

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

矩陣參數

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

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

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

編程式訪問

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

一個 PathSegment 對象代表 URI 中的一個路徑段,可以從它得到矩陣參數。它可以通過 @PathParam 來注入,這要求該路徑段必須整個被定義為一個模板參數。例如下面的代碼也可以用來處理 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>。樓主嚴重不推薦用一個模板參數匹配多個路徑段,因為這很容易干擾其他匹配的設計,最后搞成一團亂麻。URI 路徑段應當盡量設計得簡單明晰,再輔以矩陣參數或查詢參數就能應付大多數場景。不論對服務端還是客戶端開發人員來說,簡潔的 URI 既便于管理,又便于使用。網上有不少關于 URI 設計指南的文章,此處不再贅述。

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

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

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


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



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

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

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

很自然地可以想到,如果把關聯表也映射成實體類,就能解決這個問題。speculator_house 包含兩個外鍵,可用作聯合主鍵。如果把它映射為 SpeculatorHouse 類,則該類與 SpeculatorHouse 都是多對一的關系。關聯表實體類的代碼如下(EmbeddedId 的映射技巧見《JPA 應用技巧 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 也要增加相應的關聯信息:

        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;
            // 此處省略若干行
        }
    

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



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

JAX-RS 使用注解進行配置,所以用它開發 REST 風格的服務非常簡單。樓主在本文用一個小例子來說明 JAX-RS 的基本用法。


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

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

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


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

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

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

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


很好很強大,現在開始編寫電影服務類 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"));
            }
    

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


接下來看看如何處理 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);
            }
        }
    

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

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

POST

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

        @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 請求的目標就直接是 http://localhost/ms/rest/movie。Consumes@Produces 相反,表示接受的數據類型,此處 JAX-RS 會自動把 JSON 數據轉換為 Movie 對象。返回的響應碼為 201 Created,并且帶有所創建資源的 URI。

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

PUT

PUT 請求用于創建或更新一個資源。與 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 強制改為 URI 所指定的,以免出現不一致。也可以根據需求,將不一致作為異常處理,給客戶端返回一個錯誤碼。

順便啰嗦一句,反正代碼在自己手中,樓主也可以把 PUT 搞成非冪等的,例如將 PUT 當成 POST 來處理,就像以前把 GET 和 POST 一視同仁那樣。不過咱既然在搞 JAX-RS,就還是要沾染一點 REST 風格,嚴格遵守 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 服務的部署和部署常規 Web 程序一樣,打包成 war 文件就可以了。最后贊一下 NetBeans 可以為 REST 風格的服務自動生成測試頁面,很好用,雖然在 Firefox 下頁面顯示不正常(對此我已經提了一個 bug),但 IE 是可以的。



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

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


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


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

可尋址性(Addressability)

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

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

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

面向表象(Representation-Oriented)

表象這個詞有點拗口,傳聞在一個 REST 風格的系統中,服務端和客戶端之間傳輸的咚咚就是表象……表象可以是純文本、XML、JSON……或者自編的山寨格式。唉,不就是數據么?只不過可以用任意的格式來傳輸,因為 HTTP 正文里面啥都能放。Content-Type 頭用來聲明格式,一般是 MIME(多用途因特網郵件擴展),像 text/plaintext/htmlapplication/pdf 這些。MIME 可以帶屬性,例如 text/html; charset=UTF-8

無態通信(Communicate Statelessly)

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

HATEOAS

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

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


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



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

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

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

一開始是最想當然的寫法:

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

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

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

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

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

        @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 現在不得不成為受控方了,好在可以定義級聯操作來達到差不多的效果:

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

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



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

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

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

multipart/form-data 的處理流程

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

        /**
         * 該過濾器幫助 {@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 的內容類型不為 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} 中的請求參數映射是只讀的,所以自己封裝一個。
             */
            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 里面的請求參數映射是只讀的,非得要通過繼承 HttpServletRequestWrapper 這種蛋疼的彎路來黑?



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

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

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

        /**
         * 該類可作為實體類的模板,其 {@link #equals(Object)} 和 {@link hashCode()} 方法基于主鍵實現。
         * 子類只需要實現 {@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 可以進一步擴展。例如,可以擴展出一個 UuidEntity,它使用隨機生成的 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();
            }
        }
    

繼續發揮想象,讓它支持樂觀鎖:

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

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

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

        /**
         * 提供了對實體進行基本 CRUD 操作的實現,可作為會話 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";
            }

            /**
             * 返回用于數據庫操作的 {@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() 的實現即可。假設樓主要做一個養雞場管理系統,對雞圈進行操作的會話 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 發表評論
]]>
JSF 2.0 中定義受管 Bean 的三種途徑的比較http://www.tkk7.com/shinzey/archive/2010/05/15/321050.html蜀山兆孨龘蜀山兆孨龘Sat, 15 May 2010 11:10:00 GMThttp://www.tkk7.com/shinzey/archive/2010/05/15/321050.htmlhttp://www.tkk7.com/shinzey/comments/321050.htmlhttp://www.tkk7.com/shinzey/archive/2010/05/15/321050.html#Feedback0http://www.tkk7.com/shinzey/comments/commentRss/321050.htmlhttp://www.tkk7.com/shinzey/services/trackbacks/321050.htmlweb/WEB-INF/faces-config.xml 不再必需。本文介紹并比較了三種途徑來定義可從頁面上的 EL 表達式中引用的受管 Bean。  閱讀全文

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

蜀山兆孨龘 2010-04-24 11:59 發表評論
]]>
主站蜘蛛池模板: 99精品全国免费观看视频| 一出一进一爽一粗一大视频免费的| 久久九九AV免费精品| 亚洲人成影院在线无码按摩店| 精品特级一级毛片免费观看| 尤物永久免费AV无码网站| 亚洲中文字幕精品久久| 永久免费毛片在线播放| 亚洲av无码一区二区三区天堂古代 | 国产91精品一区二区麻豆亚洲| 四虎影视久久久免费观看| 亚洲欧洲日本在线| 中文在线观看永久免费| 亚洲高清在线视频| 57PAO成人国产永久免费视频 | 久久精品九九亚洲精品| 成年人免费的视频| 亚洲日韩一区二区三区| 亚洲成av人在片观看| 国产线视频精品免费观看视频| 亚洲国产天堂在线观看| 99久久国产热无码精品免费| 亚洲成a人无码亚洲成av无码| 亚洲高清视频一视频二视频三| 97在线视频免费公开视频| 亚洲人成电影亚洲人成9999网| 91精品视频免费| 看免费毛片天天看| 亚洲精选在线观看| 噜噜嘿在线视频免费观看| 特级毛片aaaa级毛片免费| 亚洲最大在线观看| 一级做a爰全过程免费视频毛片| 一本色道久久综合亚洲精品| 最近中文字幕免费完整| 亚洲av色香蕉一区二区三区蜜桃| 免费一看一级毛片| 久久精品免费电影| MM1313亚洲国产精品| 亚洲AV无码专区电影在线观看| 成年女人18级毛片毛片免费|