神奇好望角 The Magical Cape of Good Hope

          庸人不必自擾,智者何需千慮?
          posts - 26, comments - 50, trackbacks - 0, articles - 11
            BlogJava :: 首頁 ::  :: 聯系 :: 聚合  :: 管理

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

          posted @ 2011-09-16 15:31 蜀山兆孨龘 閱讀(5486) | 評論 (3)編輯 收藏

          考慮兩個具有一對一關聯的實體類,受控方除了有一個自己的主鍵,還有一個引用主控方的外鍵。如果主控方和受控方是同生同滅的關系,換句話說,雙方的一對一關聯一旦確立就不可更改,就可以考慮讓雙方共享相同的主鍵,簡化受控方的表結構。下面就讓樓主通過實例來說明如何用 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 能直接支持將實體類作為主鍵,樓主個人覺得不是一個技術問題。

          posted @ 2011-09-13 11:27 蜀山兆孨龘 閱讀(3906) | 評論 (0)編輯 收藏

          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 這種蛋疼的彎路來黑?

          posted @ 2011-09-09 11:23 蜀山兆孨龘 閱讀(1969) | 評論 (0)編輯 收藏

          最近閑來無事(樓主確實太懶了),重翻舊賬,搗鼓了下 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;
                      }
          
                      // 更多方法……
                  }
              

          posted @ 2011-09-07 17:40 蜀山兆孨龘 閱讀(3580) | 評論 (8)編輯 收藏

               摘要: JSF 2.0 大量采用標注,從而使 web/WEB-INF/faces-config.xml 不再必需。本文介紹并比較了三種途徑來定義可從頁面上的 EL 表達式中引用的受管 Bean。  閱讀全文

          posted @ 2010-05-15 19:10 蜀山兆孨龘 閱讀(4086) | 評論 (0)編輯 收藏

          僅列出標題
          共8頁: 上一頁 1 2 3 4 5 6 7 8 下一頁 
          主站蜘蛛池模板: 仪陇县| 奉新县| 阜新市| 大悟县| 绩溪县| 南投县| 桃江县| 石林| 布拖县| 浑源县| 富裕县| 垫江县| 屯昌县| 昌吉市| 融水| 洛隆县| 桐梓县| 思南县| 安溪县| 安图县| 阜新| 扎囊县| 芦溪县| 万荣县| 万盛区| 曲阜市| 广宗县| 冕宁县| 桦南县| 道孚县| 河西区| 沭阳县| 龙州县| 林芝县| 普陀区| 武夷山市| 叙永县| 陆川县| 普格县| 平南县| 仁化县|