Khởi đầu với JavaServer Faces 1.2, Phần 2: Vòng đời, phép chuyển đổi, duyệt tính hợp lệ, và trình nghe pha của JSF

Loạt bài hướng dẫn bao gồm cách làm thế nào để bắt đầu khởi động công nghệ Java™ Server Faces (JSF), một khung công tác phía máy chủ, cung cấp một cách tiếp cận dựa vào thành phần để phát triển giao diện người dùng web. Phần 1 dẫn bạn bắt đầu với một tổng quan JSF 1.2 và một ứng dụng cơ bản. Phần tiếp theo này sẽ làm cho bạn nắm vững các tính năng cao cấp hơn của JSF: các trình duyệt tính hợp lệ, các trình chuyển đổi và trình nghe pha tùy biến theo yêu cầu. Cùng với hướng dẫn này bạn sẽ hiểu rõ về vòng đời của ứng dụng JSF.

Trước khi bạn bắt đầu

Về loạt bài này

Giới thiệu mở đầu về công nghệ Java™ Server Faces (JSF), một khung công tác thành phần giao diện người dùng phía máy chủ cho các ứng dụng web dựa trên Java. Loạt bài này dành cho các nhà phát triển, những người mới bắt đầu tìm hiểu JSF và muốn tiến nhanh — không chỉ với JSF, mà với cả việc sử dụng các thành phần JSF để giảm công sức. Loạt bài này trình bày chỉ những điều cốt yếu, với rất nhiều ví dụ.

JSF là một môi trường phát triển GUI khá truyền thống, giống như AWT, SWT, và Swing. Một trong những lợi ích chính của nó là nó làm cho việc phát triển Web dễ dàng hơn bằng cách giao những công việc khó khăn cho các nhà phát triển khung công tác, chứ không phải cho các nhà phát triển ứng dụng. Cứ cho là bản thân JSF phức tạp hơn nhiều so với các khung công tác Web khác, nhưng sự phức tạp này được che giấu không để cho các nhà phát triển ứng dụng biết. Phát triển các ứng dụng Web trong JSF dễ dàng hơn nhiều so với hầu hết các khung công tác khác: nó đòi hỏi viết mã ít hơn, ít phức tạp hơn, và ít việc cấu hình hơn.

Nếu bạn đang thực hiện phát triển Java phía máy chủ, JSF là khung công tác dễ nhất để tìm hiểu. Nó được định hướng để tạo các ứng dụng Web (không chỉ là các trang web). Nó cho phép bạn tập trung vào việc mã hóa Java của bạn mà không cần đối phó với các đối tượng yêu cầu, các đối tượng phiên, các thông số yêu cầu, hoặc đối phó với các tệp tin XML phức tạp. Với JSF, nhiều thứ thực hiện nhanh hơn so với các khung công tác Web Java khác.

Về hướng dẫn này

Về hướng dẫn này Phần 1 đã để lại. Nếu bạn là người mới bắt đầu với JSF, hoặc chỉ muốn ôn lại, thì hãy đọc bài đã đăng đầu tiên trước khi bạn bắt đầu phần này. Thậm chí nếu bạn là một người chuyên nghiệp về JSF già dặn, có một hay hai khả năng được đánh giá cao thì điều đó cũng sẽ giúp bạn.

Mặc dù hỗ trợ công cụ là một lợi ích chính của JSF, bạn sẽ không sử dụng các công cụ ưa thích hay sự hỗ trợ của IDE trong hướng dẫn này. Hướng dẫn này đề cập những điểm cốt yếu, với thông tin nền tảng để tiếp tục trình bày và để giữ cho bạn cách học tập có hiệu quả việc sử dụng JSF để xây dựng các ứng dụng Web.

Các mục tiêu

Trong hướng dẫn này sẽ: tiếp tục nhìn tổng quan về các tính năng của JSF và học cách làm việc với tất cả các thành phần JSF; xây dựng một ứng dụng đơn giản quản lý các mối giao tiếp — một danh sách liệt kê CRUD cơ bản (tạo, đọc, cập nhật, xóa); sau khi tìm hiểu về vòng đời của ứng dụng JSF, cải tiến ứng dụng này với các trình chuyển đổi (converter) và các trình duyệt tính hợp lệ (validator). Hướng dẫn này uốn khúc với một chút hương vị về lập trình JSF cao cấp: tạo một khung công tác duyệt tính hợp lệ mức đối tượng bằng cách sử dụng một trình nghe pha.

Ai nên tìm hiểu hướng dẫn này?

Nếu bạn là người mới bắt đầu tìm hiểu JSF, hướng dẫn này để dành cho bạn. Ngay cả khi bạn đã sử dụng JSF nhưng chưa thử nghiệm các tính năng JSF 1.2 hoặc chỉ sử dụng các công cụ GUI để xây dựng các ứng dụng JSF, bạn sẽ có khả năng học hỏi được rất nhiều từ cả hai hướng dẫn trong loạt bài này.

Các yêu cầu tiên quyết

Hướng dẫn này được viết cho các nhà phát triển Java có kinh nghiệm ở mức bắt đầu tới mức trung cấp. Bạn cần phải có hiểu biết chung về cách sử dụng ngôn ngữ Java, cùng với một số kinh nghiệm phát triển giao diện đồ họa người dùng (GUI).

Các yêu cầu hệ thống

Để chạy các ví dụ trong hướng dẫn này, bạn cần có một môi trường phát triển Java (JDK) và Maven Apache. Nó giúp để có một IDE Java. Các tệp tin dự án Maven và các tệp tin dự án trong Eclipse Java EE và Web Tools Project (WTP) được cung cấp. Xem Tải về để nhận được mã ví dụ. Hãy truy cập vào trang web sách hướng dẫn của tác giả (xem Tài nguyên) để biết thêm thông tin về cách làm thế nào để chạy các ví dụ này.


Ứng dụng CRUD JSF mẫu

Phần này giới thiệu một ứng dụng CRUD đơn giản mà bạn sẽ xây dựng trong các phần tiếp sau để tìm hiểu về:

  • Từng thành phần HTML tiêu chuẩn của JSF
  • Tạo các trình biến đổi tùy biến
  • Làm việc với các trình duyệt tính hợp lệ
  • Làm việc với các trình nghe pha

Một ứng dụng quản lý các mối giao tiếp

Ứng dụng mà bạn sẽ xây dựng trong phần này là một ứng dụng quản lý giao tiếp có cấu trúc giống như ứng dụng máy tính bỏ túi trong Phần 1. Như bạn có thể thấy trong Hình 1, ứng dụng này là một danh sách CRUD tiêu chuẩn. Nó không yêu cầu các quy tắc dẫn hướng do toàn bộ ứng dụng sử dụng chỉ một khung nhìn đơn (trang contacts.jsp).

Hình 1. Ứng dụng mẫu quản lý các mối giao tiếp
Ứng dụng mẫu quản lý các mối giao tiếp

Hình 2 Cho thấy lưu đồ cơ sở của ứng dụng:

Hình 2. Ứng dụng mẫu quản lý các mối giao tiếp, sơ đồ liên kết
Ứng dụng mẫu quản lý các mối giao tiếp, sơ đồ liên kết

Ứng dụng CRUD này bao gồm các phần tử sau đây:

  • ContactController: Trình điều khiển JSF
  • Contact: Đối tượng mô hình để mô tả thông tin về các mối giao tiếp
  • ContactRepository: Đối tượng mô hình để tạo, đọc, cập nhật, và xóa các đối tượng Contact
  • contacts.jsp: Trang JavaServer Pages (JSP) để bố trí cây thành phần JSF quản lý các giao tiếp
  • faces-config.xml: Cấu hình JSF, nơi bạn khai báo ContactControllerContactRepository như là các bean quản lý và nội xạ ContactRepositoryContactController

Trình điều kiển giao tiếp (ContactController)

ContactController hậu thuẫn trang contacts.jsp. Listing 1 hiển thị mã cho ContactController:

Listing 1. ContactController
package com.arcmind.contact.controller;

import java.util.List;

import javax.faces.application.FacesMessage;
import javax.faces.component.UICommand;
import javax.faces.component.UIForm;
import javax.faces.context.FacesContext;

import com.arcmind.contact.model.Contact;
import com.arcmind.contact.model.ContactRepository;

public class ContactController {
    /** Contact Controller collaborates with contactRepository. */
    private ContactRepository contactRepository;

    /** The current contact that is being edited. */
    private Contact contact = new Contact();

    /** Contact to remove. */
    private Contact selectedContact;

    /** The current form. */
    private UIForm form;

    /** Add new link. */
    private UICommand addNewCommand;

    /** Persist command. */
    private UICommand persistCommand;

    /** For injection of collaborator. */
    public void setContactRepository(ContactRepository contactRepository) {
        this.contactRepository = contactRepository;
    }

    public void addNew() {
        form.setRendered(true);
        addNewCommand.setRendered(false);
        persistCommand.setValue("Add");
    }

    public void persist() {
        form.setRendered(false);
        addNewCommand.setRendered(true);
        if (contactRepository.persist(contact) == null) {
            addStatusMessage("Added " + contact);
        } else {
            addStatusMessage("Updated " + contact);
        }
    }

    public void remove() {
        contactRepository.remove(selectedContact);
        addStatusMessage("Removed " + selectedContact);
    }

    public void read() {
        contact = selectedContact;
        form.setRendered(true);
        addNewCommand.setRendered(false);
        addStatusMessage("Read " + contact);
        persistCommand.setValue("Update");
    }

    private void addStatusMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null,
                new FacesMessage(FacesMessage.SEVERITY_INFO, message, null));
    }

   //most getter/setter omitted

}

Listing 1 tạo ra một GUI CRUD với chưa đến 74 dòng mã — không quá tệ có phải không.ContactController được quản lý trong phạm vi, yêu cầu , do đó một Contact mới được tạo ra khi một ContactController được khởi tạo. Ba thành phần — biểu mẫu (form) (có kiểu là UIForm), addNewCommand (có kiểu là UICommand), và persistCommand (có kiểu là UICommand) — được liên kết với ContactController.

Phương thức addNew() đảm bảo rằng:

  • biểu mẫu (form) được bật lên để người sử dụng có thể nhập vào một giao tiếp mới — form.setRendered(true)
  • addNewCommand được tắt đi —addNewCommand.setRendered(false)
  • nhãn của persistCommand được đặt là AddpersistCommand.setValue("Add")

Phương thức persist() vừa xử lý cập nhật mối giao tiếp hiện có và vừa xử lý bổ sung thêm mối giao tiếp mới bằng cách sử dụng contactRepository. Phương thức persist() tắt form bật addNewCommand. Phương thức remove() loại bỏ mối giao tiếp ra khỏi hệ thống bằng cách sử dụng contactRepository.

Phương thức read() sao chép selectedContact (có kiểu là Contact) vào một mối giao tiếp. (Mối giao tiếp — cũng có kiểu là Contact— có giá trị liên kết với biểu mẫu). Bạn có thể tự hỏi selectedContact bắt nguồn từ đâu. Nó được chọn khi người dùng nhấn vào một mối giao tiếp trong danh sách các mối giao tiếp. (Hướng dẫn này sẽ đề cập khi thảo luận về JSP.) Như trong Phần 1, addStatusMessage bổ sung thêm các thông báo trạng thái để bạn có thể hiển thị chúng với <h:messages>.

Khung nhìn các mối giao tiếp

Trang JSP sử dụng một <h:dataTable> (một thành phần không được giới thiệu trong Phần 1), như hiển thị trong Listing 2:

Listing 2. contacts.jsp
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Contacts</title>
<link rel="stylesheet" type="text/css"
   href="<%=request.getContextPath()%>/css/main.css" />
</head>

<body>
<f:view>
   <h4>Contacts</h4>

   <h:messages infoClass="infoClass" errorClass="errorClass"
      layout="table" globalOnly="true" />

   <h:form>
      <h:commandLink binding="#{contactController.addNewCommand}"
         action="#{contactController.addNew}" value="Add New..." />
   </h:form>

   <h:form binding="#{contactController.form}" rendered="false"
      styleClass="form">

      <h:inputHidden value="#{contactController.contact.id}" />
      <h:panelGrid columns="6">
         <%-- First Name --%>
         <h:outputLabel value="First Name" for="firstName" accesskey="f" />
         <h:inputText id="firstName" label="First Name" required="true"
            value="#{contactController.contact.firstName}" size="10" />
         <h:message for="firstName" errorClass="errorClass" />

         <%-- Last Name --%>
         <h:outputLabel value="Last Name" for="lastName" accesskey="l" />
         <h:inputText id="lastName" required="true"
            value="#{contactController.contact.lastName}" size="15" />
         <h:message for="lastName" errorClass="errorClass" />
      </h:panelGrid>
      <h:panelGroup>
         <h:commandButton binding="#{contactController.persistCommand}"
            action="#{contactController.persist}" />
      </h:panelGroup>
   </h:form>
   <h:form>
      <h:dataTable value="#{contactController.contacts}" var="contact"
         rowClasses="oddRow, evenRow"
         rendered="#{not empty contactController.contacts}"
         styleClass="contactTable" headerClass="headerTable"
         columnClasses="normal,centered">
         <h:column>
            <f:facet name="header">
               <h:column>
                  <h:outputText value="Name" />
               </h:column>
            </f:facet>
            <h:outputText value="#{contact.lastName}, #{contact.firstName}" />
         </h:column>
         <h:column>
            <f:facet name="header">
               <h:column>
                  <h:outputText value="Action" />
               </h:column>
            </f:facet>
            <h:panelGrid columns="2">
               <h:commandLink value="remove" action="#{contactController.remove}">
                  <f:setPropertyActionListener
                     target="#{contactController.selectedContact}" value="#{contact}" />
               </h:commandLink>
               <h:commandLink value="edit" action="#{contactController.read}">
                  <f:setPropertyActionListener
                     target="#{contactController.selectedContact}" value="#{contact}" />
               </h:commandLink>
            </h:panelGrid>
         </h:column>

      </h:dataTable>
   </h:form>
</f:view>
</body>

</html>

<h:dataTable> là giá trị được liên kết để hiển thị các mối giao tiếp từ contactController, khi sử dụng "#{contactController.contacts}". Mỗi mối giao tiếp được ánh xạ vào bảng bằng cách sử dụng thuộc tính: var="contact". Trong contacts.jsp, các kiểu dáng (style) oddRow evenRow (được định nghĩa trong một tệp tin CSS) được đặt vào trong thuộc tính rowClasses với rowClasses="oddRow, evenRow". Điều này cho phép <h:dataTable> sử dụng các kiểu dáng luân phiên cho các hàng trong bảng. Hãy kiểm tra tài liệu hướng dẫn trực tuyến <h:dataTable> để tìm một danh sách đầy đủ về những cái có thể làm với <h:dataTable> và các kiểu dáng, bởi vì <h:dataTable> có khá nhiều khả năng. (Xem Tài nguyên để tìm liên kết đến JSF API Javadocs).

Bạn cũng có thể thiết lập một kiểu dáng cho toàn bộ <h:dataTable> vớistyleClass="contactTable" hoặc cho vùng tiêu đề của nó với headerClass="headerTable". Hoặc bạn có thể thực hiện các kiểu dáng luân phiên tương tự như thế cho các cột bằng cách sử dụng thuộc tính columnClasses: columnClasses="normal,centered". <h:dataTable> được thiết lập để nó không hoàn trả nếu không có mối giao tiếp: rendered="#{not empty contactController.contacts}"nào. Thành phần <h:dataTable> rất linh hoạt và dễ sử dụng.

Bên trong <h:dataTable>, bạn sử dụng các <h:column> để hiển thị các giá trị theo các thuộc tính. Mỗi cột định nghĩa một phần tiêu đề trong một thẻ <f:facet>. Một facet là một thành phần tên mà các thành phần khác sử dụng. Vậy thì sau <f:facet> và bên trong thành phần <h:column> bạn hãy sử dụng thành phần <h:outputText> để đưa ra các thuộc tính firstNamelastName.

Mỗi hàng có một liên kết loại bỏ và một liên kết chỉnh sửa, mỗi liên kết sử dụng một <h:commandLink>. commandLink loại bỏ được liên kết với phương thức contactController.remove. Liên kết chỉnh sửa được liên kết với phương thức contactController.read . Thuộc tính contactController.selectedContact được điền bằng hàng hiện tại theo cách bạn cấu hình <f:setPropertyActionListener>. <f:setPropertyActionListener> làm cho mối giao tiếp của hàng hiện tại được sao chép vào selectedContact trước khi phương thức hành động được gọi.

Tệp faces-config.xml cho ứng dụng CRUD của các mối giao tiếp

Tệp tin faces-config.xml nối ContactRepository vào trong ContactController, như hiển thị trong Listing 3:

Listing 3. faces-config.xml
<managed-bean>
   <managed-bean-name>contactRepository</managed-bean-name>
   <managed-bean-class>
   com.arcmind.contact.model.ContactRepository
   </managed-bean-class>
   <managed-bean-scope>application</managed-bean-scope>
</managed-bean>   
<managed-bean>
   <managed-bean-name>contactController</managed-bean-name>
   <managed-bean-class>
      com.arcmind.contact.controller.ContactController
   </managed-bean-class>
   <managed-bean-scope>request</managed-bean-scope>
   <managed-property>
      <property-name>contactRepository</property-name>
      <property-class>
         com.arcmind.contact.model.ContactRepository
      </property-class>
      <value>#{contactRepository}</value>
   </managed-property>
</managed-bean>

Lưu ý rằng contactRepository nằm trong phạm vi ứng dụng và nó được nội xạ vào trong contactRepository của contactController bằng cách sử dụng phần tử <managed-property>. Kỹ thuật này cho phép bạn nội xạ các phụ thuộc/các trình cộng tác vào trong trình điều khiển để hỗ trợ việc duy trì mô hình và khung nhìn riêng biệt, và nó cho phép bạn nội xạ các đối tượng giả để sau này có thể được thay thế bằng các đối tượng thực. Nhiều hơn một lần, tôi đã làm giả một đối tượng mô hình như ContactRepository và sau đó thay thế nó bằng phiên bản thực sau khi hoàn thành GUI.

Mô hình của ứng dụng là khá đơn giản, như hiển thị trong các Listings 4 và 5. Listing 4 hiển thị lớp Contact:

Listing 4. Contact
package com.arcmind.contact.model;

public class Contact { private String firstName;
private String lastName; protected long id;

public Contact(String firstName, String lastName) {
this.firstName = firstName; this.lastName =
lastName; }

public Contact() { }

public String getFirstName() { return firstName; }

public void setFirstName(String firstName) {
this.firstName = firstName; }

public String getLastName() { return lastName; }

public void setLastName(String lastName) {
this.lastName = lastName; }

@Override public int hashCode() { final int prime =
31; int result = 1; result = prime * result + (int)
(id ^ (id >>> 32)); return result; }

@Override public boolean equals(Object obj) { if
(this == obj) return true; if (obj == null) return
false; if (getClass() != obj.getClass()) return
false; final Contact other = (Contact) obj; if (id
!= other.id) return false; return true; }

@Override public String toString() { return
String.format("Contact: %s %s", firstName,
lastName); }

public long getId() { return id; }

public void setId(long id) { this.id = id; }

}

Listing 5 hiển thị lớp ContactRepository, để mô phỏng việc viết các mối giao tiếp vào một cơ sở dữ liệu:

Listing 5. ContactRepository
package com.arcmind.contact.model;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

public class ContactRepository {
   private Map<Long, Contact> contacts = new LinkedHashMap<Long, Contact>();
   private static long counter = 1l;

   public List<Contact> getContacts() {
      return new ArrayList<Contact>(contacts.values());
   }

   public synchronized Contact persist(Contact contact) {
      if (contact.id == 0) {
         contact.id = counter++;
      }
      return contacts.put(contact.id, contact);
   }

   public synchronized void remove(Contact contact) {
      contacts.remove(contact.id);
   }

}

Những gì bạn có bây giờ là một ứng dụng CRUD khá cơ bản. Trong phần kế tiếp, bạn sẽ xây dựng bên trên cơ sở này để tìm hiểu cách làm thế nào để sử dụng các thành phần JSF khác nhau.


Làm việc với các thành phần JSF

Trong phần này, bạn sẽ sử dụng nhiều thành phần JSF để nâng cao ứng dụng CRUD:

  • <f:subview>
  • <h:selectOneMenu>
  • <h:selectOneRadio>
  • <h:selectBooleanCheckbox>
  • <h:selectManyCheckbox>
  • <h:inputTextarea>

Hình 3 cho thấy các thành phần này xuất hiện trong GUI như thế nào:

Hình 3. Quản lý các mối giao tiếp với một số lượng lớn các thành phần JSF phổ biến
Quản lý các mối giao tiếp với một số lượng lớn các thành phần JSF phổ biến

Các khung nhìn phụ

Như bạn có thể hình dung, thật khó để bao gồm tất cả các thành phần JSF của bạn trên một trang web. May mắn thay, bạn có thể phân chia các thành phần JSF theo các khung nhìn riêng biệt bằng cách sử dụng một <f:subview>, như hiển thị trong Listing 6:

Listing 6. Các khung nhìn phụ contacts.jsp
<body>
<f:view>
   <h3>Contacts (2nd version)</h3>
   <h:messages infoClass="infoClass" errorClass="errorClass"
      layout="table" globalOnly="true" />

   <h:form>
      <h:commandLink binding="#{contactController.addNewCommand}"
         action="#{contactController.addNew}" value="Add New..." />
   </h:form>

   <f:subview id="form">
      <jsp:include page="form.jsp" />
   </f:subview>

   <f:subview id="listing">
      <jsp:include page="listing.jsp" />
   </f:subview>

</f:view>
</body>

Bạn có thể sử dụng các <f:subview> trong trang cha mẹ hay trong trang bao gồm (chứ không phải cả hai). Trong JSF 1.2 <f:subview> là tùy chọn. Trong các phiên bản JSF cũ hơn, nó là bắt buộc. Một số các IDE hình như cũng chờ đợi các <f:subview>, do đó bạn có thể cần sử dụng chúng ngay cả khi bạn đang sử dụng JSF 1.2 hoặc phiên bản mới hơn.

Chọn một

Trong JSF, các thành phần được tách thành hai phần: thành phần JSF chính nó và một trình hoàn trả có trách nhiệm hiển thị thành phần đó. Thành phần UISelectOne có một số trình hoàn trả. Nó hậu thuẫn HtmlSelectOneListbox, HtmlSelectOneMenu, và HtmlSelectOneRadio.

Ứng dụng quản lý các mối giao tiếp (phiên bản thứ 2) sử dụng <h:selectOneMenu>. Để điều này hoạt động, bạn bổ sung thêm ba đối tượng mô hình mới: Group (chỉ ra trong Listing 7), Thẻ (Tag), và ContactType. Bạn cũng thêm hai đối tượng kho lưu trữ mới: GroupRepositoryTagRepository, tương tự như ContactRepository. ContactType không cần có một kho lưu trữ bởi vì nó là một Bảng kê (Enum). Lớp Contact hiện nay có ba thuộc tính mới dành cho nhóm có nó ở trong đó (được gọi là group), dành cho các thẻ có kết hợp với nó (tags), và cuối cùng là dành cho kiểu của nó type.

Listing 7. Các khung nhìn phụ contacts.jsp/form.jsp
<%-- Group --%>
<h:outputLabel value="Group" for="group" accesskey="g" />
<h:selectOneMenu id="group" validatorMessage="required"
   value="#{contactController.selectedGroupId}">
   <f:selectItems value="#{contactController.groups}" />
   <f:validateLongRange minimum="1" />
</h:selectOneMenu>
<h:message for="group" errorClass="errorClass" />

Lưu ý rằng selectOneMenu sử dụng thuộc tính giá trị để liên kết selectOneMenu với selectedGroupId. Phần thân của selectOneMenu chứa một <f:selectItems>, đó là giá trị liên kết với thuộc tính các nhóm: value=#{contactController.groups}. Bạn tạo ra một danh sách các nhóm trong bean phía sau. Mã cho thuộc tính selectedGroupId và thuộc tính các nhóm có trong Listing 8:

Listing 8. Xây dựng một danh sách của các nhóm
public class ContactController {
   ...
   private GroupRepository groupRepository;
   ...
   private Long selectedGroupId;
   ...
   public List<SelectItem> getGroups() {
      List<Group> groups = groupRepository.list();
      List<SelectItem> list = new ArrayList<SelectItem>(groups.size()+1);
      list.add(new SelectItem(Long.valueOf(-1L), "select one"));
      for (Group group : groups) {
         SelectItem selectItem = new SelectItem(group.getId(), group.getName());
         list.add(selectItem);
      }
      return list;
   }
   //Other getter/setters removed
   ...

Thuộc tính các nhóm (groups) trả về một danh sách các SelectItem. Lớp SelectItem được sử dụng để biểu diễn một mục trong một danh sách. Nó được cả hai thành phần UISelectManyUISelectOne sử dụng. Lưu ý rằng phương thức getGroups sử dụng groupRepository, đây là một đối tượng kho lưu trữ, được nội xạ như contactRepository để nhận một danh sách các nhóm. The groupRepository quản lý các đối tượng Group . Một Group đại diện cho một nhóm. Một Contact đại diện một mối giao tiếp. Phương thức getGroups() tạo ra một danh sách các SelectItem bằng cách sử dụng thuộc tính group.id như là một giá trị và thuộc tính group.name như là một nhãn.

Lưu ý rằng bạn thêm một SelectItem "chọn một" với giá trị là -1. Bạn sử dụng giá trị này để xác định một mục có được lựa chọn không. Nếu nó không được lựa chọn, bạn chọn mục chưa được chọn bằng cách sử dụng <f:validateLongRange minimum="1" /> ở trong selectOneMenu (xem lại Listing 7). Cũng cần chú ý rằng selectOneMenu sử dụng validatorMessage="required" để hiển thị một thông báo lỗi ngắn.

Chú ý rằng Listing 8 gọi trực tiếp kho lưu trữ. Nếu kho lưu trữ đã thực sự đang trao đổi với một cơ sở dữ liệu hoặc bộ nhớ cache, bạn muốn thực hiện lời gọi đến phương thức hành động để thực hiện xử lý lỗi ở đó và phơi bày ra các kết quả như các mục đã chọn trong thuộc tính.

Khi biểu mẫu (form) được gửi đi selectedGroupId được thiết lập. Phương thức persist() được liên kết với nút cập nhật và nút tạo ra, sẽ sử dụng selectedGroupId để tìm kiếm nhóm trong kho lưu trữ, như hiển thị trong Listing 9:

Listing 9. Cập nhật persist() để sử dụng selectedGroupId
public class ContactController {
   ...
   public void persist() {
      /* Setup the group into contact. */
      contact.setGroup(groupRepository.lookup(selectedGroupId));
            
      /* Turn form off, turn link on. */
      form.setRendered(false);
      addNewCommand.setRendered(true);
      
      /* Add a status message. */
      if (contactRepository.persist(contact) == null) {
         addStatusMessage("Added " + contact);
      } else {
         addStatusMessage("Updated " + contact);
      }
   }
   ...
   public void read() {
      /* Prepare selected contact. */
      contact = selectedContact;
      
      /* Turn form on and the link off. */
      form.setRendered(true);
      addNewCommand.setRendered(false);

      /* Prepare selected group id. */
      selectedGroupId = contact.getGroup().getId();

      ...
      this.selectedTagIds = tagIds.toArray(new Long[tags.size()]);
      
      addStatusMessage("Read " + contact);
      persistCommand.setValue("Update");
   }
   ...

Lưu ý rằng lớp Contact bổ sung thêm một thuộc tính nhóm. Vì vậy Contact có mối quan hệ nhiều-với-một với Group.

Phương thức read() trong Listing 9 khởi tạo this.selectedTagIds sao cho nó hiển thị dưới dạng đã được chọn trong khung nhìn.

Bạn đã thấy cách bạn có thể xử lý một mối quan hệ một-một với <h:selectOne> như thế nào. Contact cũng có một mối quan hệ nhiều-với-nhiều với các Thẻ (Tags), đươc biểu diễn bằng thuộc tính tags của Contact. Để xử lý điều này, hãy sử dụng một <h:selectManyCheckbox>, như hiển thị trong Listing 10:

Listing 10. selectManyCheckbox cho Contact.tags
<h:selectManyCheckbox id="tags"
   value="#{contactController.selectedTagIds}">
   <f:selectItems value="#{contactController.availableTags}" />
</h:selectManyCheckbox>

Sự khác biệt chính với <h:selectManyCheckbox> là ở chỗ nó được liên kết với một mảng của các số nguyên dài, thay vì một số nguyên số nguyên dài, như hiển thị trong Listing 10.

Mã Java tương tự như Listing 8, ngoại trừ bây giờ bạn đang làm việc với một mảng các ID được chọn các (số nguyên dài), thay cho một số nguyên dài, như hiển thị trong Listing 11:

Listing 11. Mã Java để hậu thuẫn selectManyCheckbox
public class ContactController {
    ...
   private Long[] selectedTagIds;

   private TagRepository tagRepository;
    ...
   public List<SelectItem> getAvailableTags() {
      List<Tag> tags = tagRepository.list();
      List<SelectItem> list = new ArrayList<SelectItem>(tags.size());
      for (Tag tag : tags) {
         SelectItem selectItem = new SelectItem(tag.getId(), tag.getName());
         list.add(selectItem);
      }
      return list;
   }
    ...
   public void persist() {
        ...      
      /* Setup the tags into contact. */
      List<Tag> tags = new ArrayList<Tag>(selectedTagIds.length);
      for (Long selectedTagId : selectedTagIds) {
         tags.add(tagRepository.lookup(selectedTagId));
      }
      contact.setTags(tags);
        ...      
   }
    ...
   public void read() {
        ...      
      /* Prepare selected tag IDs. */
      List<Tag> tags = contact.getTags();
      List<Long> tagIds = new ArrayList<Long>(tags.size());
      for (Tag tag : tags) {
         tagIds.add(tag.getId());
      }
      this.selectedTagIds = tagIds.toArray(new Long[tags.size()]);
      ...
   }
    ...

Phương thức persist() sử dụng phương thức tagRepository.lookup() để tìm kiếm các đối tượng kiểu Thẻ (Tag) dựa trên các selectedTagId, và sau đó đặt thuộc tính contact.tags bằng với các giá trị đã tìm thấy. Phương thức read() khởi tạo các selectedTagId dựa trên cơ sở các Thẻ (Tags) có trong thuộc tính contact.tags.

Lớp UISelectMany hậu thuẫn <h:selectManyCheckbox>, nhưng nó cũng hậu thuẫn <h:selectManyListbox><h:selectManyMenu>. Bạn có thể sử dụng chúng thay cho <h:selectManyCheckbox>, như hiển thị trong Listings 12 và 13, và Hình 4.

Listing 12. <selectManyListbox>
<h:selectManyListbox id="tags" value="#{contactController.selectedTagIds}">
   <f:selectItems value="#{contactController.availableTags}"/>
</h:selectManyListbox>
Listing 13. <selectManyMenu>
<h:selectManyMenu id="tags" value="#{contactController.selectedTagIds}">
   <f:selectItems value="#{contactController.availableTags}"/>
</h:selectManyMenu>
Hình 4: <selectManyListbox><selectManyMenu>
Hình 4. selectManyCheckbox, selectManyListbox và selectManyMenu

Lưu ý rằng thiết lập cho tất cả ba thẻ là như nhau.

JSF 1.2 có các trình biến đổi cho các Enum, do đó, bạn không cần phải tìm kiếm các giá trị. Bạn có thể sử dụng chúng trực tiếp và liên kết với chúng mà không cần có một thuộc tính trung gian trong trình điều khiển giống như bạn đã dành cho các thẻ nhóm (xem Listing 8Listing 11). Lớp Contact có một thuộc tính enum, như hiển thị trong Listing 14:

Listing 14. Contact sử dụng enum cho thuộc tính kiểu (type)
public enum ContactType {
   BUSINESS, PERSONAL;
   
   public String toString () {
      return this.name().toLowerCase();
   }
}
...
public class Contact implements Serializable {
    ...
   private Group group;
   private List<Tag> tags;
   ...
   private ContactType type = ContactType.PERSONAL;
    ...   
   public ContactType getType() {
      return type;
   }

   public void setType(ContactType type) {
      this.type = type;
   }
   ...
}

Vì JSF có một trình biến đổi cho các Enum, bạn có thể liên kết trực tiếp vào thuộc tính type, như hiển thị trong Listing 15:

Listing 15. Liên kết trực tiếp vào contact.type
<h:selectOneRadio id="type" value="#{contactController.contact.type}">
   <f:selectItem itemValue="PERSONAL" itemLabel="personal" />
   <f:selectItem itemValue="BUSINESS" itemLabel="business" />
</h:selectOneRadio>

Listing 15 cũng giải thích việc sử dụng <f:selectItem>, nó tạo ra các giá trị đơn. Trình biến đổi kèm sẵn của JSF cho các Enum chờ đợi đầu vào là các giá trị chuỗi ký tự — thực sự đó là các tên trong các Enum. Bạn cũng có thể sử dụng một <h:selectOneListbox>, như hiển thị trong Listing 16:

Listing 16. Sử dụng <h:selectOneListbox>
<h:selectOneListbox id="type" value="#{contactController.contact.type}">
   <f:selectItem itemValue="PERSONAL" itemLabel="personal"/>
   <f:selectItem itemValue="BUSINESS" itemLabel="business"/>
</h:selectOneListbox>

Các thành phần khác của ứng dụng quản lý các mối giao tiếp

Ứng dụng CRUD quản lý các mối giao tiếp cũng giải thích việc sử dụng <h:selectBooleanCheckbox><h:inputTextarea>, như hiển thị trong Listing 17:

Listing 17. Sử dụng <h:selectBooleanCheckbox> and <h:textArea>
<h:inputHidden value="#{contactController.contact.id}" />
...
<%-- active --%>
<h:outputLabel value="Active" for="active" accesskey="a" />
<h:selectBooleanCheckbox id="active"
   value="#{contactController.contact.active}" />
<h:message for="active" errorClass="errorClass" />

<%-- Description --%>
...
<h:outputLabel value="Description" for="description" 
   accesskey="d" style="font: large;" /> 
<h:inputTextarea id="description" cols="80" rows="5" 
   value="#{contactController.contact.description}" /> 
<h:message for="description" errorClass="errorClass" />

<h:inputTextarea> có hai thuộc tính bổ sung được đặt với cols="80" rows="5". Các liên kết hoạt động như trước.

Listing 18 cho thấy các thuộc tính mà bạn liên kết tới trong Listing 17:

Listing 18. Lớp Contact
public class Contact implements Serializable {
    ...
   private String description;
   private boolean active;
   protected long id;
    ...
   public String getDescription() {
      return description;
   }

   public void setDescription(String description) {
      this.description = description;
   }

   public boolean isActive() {
      return active;
   }

   public void setActive(boolean active) {
      this.active = active;
   }
   public long getId() {
      return id;
   }

   public void setId(long id) {
      this.id = id;
   }   
    ...
}

Bạn liên kết các thuộc tính mã nhận dạng id mô tả (description), kiểu (type), tên (firstName), và họ (lastName) của Contact trực tiếp với UI. Các thuộc tính nhóm (group)các thẻ (tags) có thể không được liên kết trực tiếp bởi vì chúng không có ("trình biến đổi dữ liệu JSF"). Trong một phần sau của hướng dẫn này ("Các trình biến đổi dữ liệu JSF"- JSF data converters), bạn sẽ tìm hiểu về các trình biến đổi và tạo ra một số cái cho ứng dụng này. Giữa bây giờ và sau này, hãy tham gia một hành trình ngắn bên lề đi vào vòng đời của ứng dụng JSF.


Vòng đời của ứng dụng JSF

Bạn có thể (trái với suy nghĩ chung) viết các ứng dụng JSF mà không hiểu biết mọi chi tiết nhỏ về cách thức hoạt động của công nghệ này; bạn có thể học được rất nhiều chỉ bằng cách tự mình kiếm một dự án và ráp nối lại thông qua nó. Tuy nhiên, sự hiểu biết các nguyên tắc cơ bản nhất định sẽ làm cho các nỗ lực phát triển của bạn được đền đáp nhiều hơn — và tiêu tốn ít thời gian hơn. Phần này vòng ra ngoài, cách xa khỏi ứng dụng quản lý các mối giao tiếp và đưa bạn dạo qua sáu pha trong vòng đời yêu cầu – xử lý của JSF. Hãy xem xét những gì xảy ra trong mỗi pha và các pha liên kết với nhau như thế nào. Cuộc thảo luận này sẽ thông báo công việc bạn sẽ làm trong phần còn lại của hướng dẫn này.

Các pha của vòng đời của ứng dụng JSF

Sáu pha trong vòng đời của ứng dụng JSF là:

  1. Khôi phục khung nhìn
  2. Áp dụng các giá trị yêu cầu; xử lý các sự kiện
  3. Xử lý duyệt tính hợp lệ; xử lý các sự kiện
  4. Cập nhật các giá trị của mô hình; xử lý các sự kiện
  5. Gọi ứng dụng; xử lý các sự kiện
  6. Hoàn trả đáp ứng

Sáu pha xuất hiện theo trình tự mà JSF xử lý tiêu biểu một biểu mẫu (form) GUI. Danh sách hiển thị các pha theo thứ tự thực hiện của chúng với việc xử lý sự kiện ở mỗi pha, nhưng vòng đời JSF hầu như không được thiết lập cứng nhắc như vậy. Bạn có thể thay đổi thứ tự thực hiện bằng cách bỏ qua các pha hay bỏ hẳn vòng đời. Ví dụ, nếu một giá trị yêu cầu không hợp lệ được sao chép vào một thành phần, khung nhìn hiện tại sẽ được hiển thị lại, và một số pha có thể không thực hiện được.

Bạn cũng có thể chọn bỏ hẳn JSF và khi đó có lẽ giao việc cho servlet hay một khung công tác ứng dụng khác. Trong trường hợp này, bạn có thể phát ra một lời gọi phương thức FacesContext.responseComplete để chuyển hướng người dùng đến một trang Web hoặc nguồn tài nguyên Web khác, sau đó sử dụng bộ điều vận (dispatcher) yêu cầu (lấy ra từ đối tượng yêu cầu trong FacesContext) để chuyển tiếp đến một nguồn tài nguyên Web thích hợp. Ngoài ra, bạn cũng có thể gọi FacesContext.renderResponse để hoàn trả lại khung nhìn gốc.

Vấn đề là cho phép vòng đời ứng dụng cấu trúc các nỗ lực phát triển của bạn nhưng không cảm thấy hoàn toàn bị buộc vào nó. Bạn có thể thay đổi vòng đời mặc định khi cần mà không lo sợ làm đổ vỡ ứng dụng của bạn. Trong hầu hết trường hợp, bạn sẽ nhận thấy rằng vòng đời JSF đáng được tuân theo vì nó khá logic.

Các biểu mẫu phải được duyệt tính hợp lệ trước khi có thể thực hiện bất kỳ logic ứng dụng nào, và dữ liệu của trường phải được biến đổi trước khi duyệt tính hợp lệ. Việc bám sát vòng đời giải phóng cho bạn, để tập trung suy nghĩ về các chi tiết trong duyệt tính hợp lệ và biến đổi, hơn là nghĩ về chính các pha của quá trình xử lý yêu cầu. Cũng rất quan trọng khi lưu ý rằng các khung công tác Web khác cũng có vòng đời tương tự; chỉ có điều không được quảng cáo hay giải thích tốt.

Một số nhà phát triển khi sử dụng JSF có thể chẳng bao giờ viết một thành phần hoặc mở rộng một khung công tác trong khi những người khác chỉ tập trung vào các nhiệm vụ đó. Mặc dù vòng đời của JSF sẽ giống nhau với hầu hết các dự án, bạn sẽ có khả năng đề cập đến nó ở các giai đoạn khác nhau dựa trên vai trò của bạn trong dự án. Nếu bạn đang tập trung nhiều hơn về phát triển ứng dụng tổng thể, có nhiều khả năng bạn sẽ quan tâm đến các pha giữa của chu kỳ xử lý yêu cầu:

  • Áp dụng các giá trị yêu cầu
  • Xử lý duyệt tính hợp lệ
  • Cập nhật các giá trị mô hình
  • Gọi ứng dụng

Nếu bạn đang tập trung vào việc phát triển thành phần JSF, nhiều khả năng bạn sẽ tập trung vào các pha đầu tiên và cuối cùng của vòng đời:

  • Khôi phục khung nhìn
  • Hoàn trả đáp ứng

Trong phần còn lại của phần này, bạn sẽ đi qua từng giai đoạn của chu kỳ xử lý yêu cầu của JSF, bao gồm cả xử lý sự kiện và duyệt tính hợp lệ. Trước khi bạn bắt đầu, hãy xem Hình 5, một sơ đồ vòng đời của ứng dụng JSF để có cái nhìn tổng thể:

Hình 5. Vòng đời của ứng dụng JSF
Vòng đời của JSF

Pha 1: Khôi phục khung nhìn

Trong pha đầu của vòng đời JSF —khôi phục khung nhìn— một yêu cầu đi qua FacesServlet. Servlet xem xét yêu cầu và trích ra mã nhận dạng ID của khung nhìn, được xác định bởi tên của trang JSP.

Trình điều khiển của khung công tác JSF sử dụng mã nhận dạng khung nhìn để tìm kiếm các thành phần dùng cho khung nhìn hiện tại. Nếu khung nhìn còn chưa tồn tại, trình điều khiển JSF tạo ra nó. Nếu khung nhìn đã tồn tại, trình điều khiển JSF sử dụng nó. Khung nhìn có chứa tất cả các thành phần GUI.

Pha này của vòng đời đưa ra ba cá thể khung nhìn: khung nhìn mới, khung nhìn khởi đầu, và khung nhìn hiện lại (postback), mỗi khung nhìn được xử lý khác nhau. Trong trường hợp khung nhìn mới, JSF xây dựng khung nhìn của trang Faces và nối các trình xử lý sự kiện và trình duyệt tính hợp lệ tới các thành phần. Khung nhìn được lưu trong một đối tượng FacesContext.

FacesContext lưu trữ các thông tin trạng thái mà JSF cần để quản lý trạng thái của thành phần GUI cho yêu cầu hiện tại. FacesContext lưu khung nhìn trong thuộc tính viewRoot của nó; viewRoot chứa tất cả các thành phần JSF của khung nhìn hiện tại.

Trong trường hợp của khung nhìn khởi đầu (lần đầu tiên một trang web được nạp), JSF tạo ra một khung nhìn rỗng. Khung nhìn rỗng được điền dần dần khi trang JSP được xử lý. Từ khung nhìn khởi đầu, JSF tiến thẳng đến pha hoàn trả đáp ứng.

Trong trường hợp khung nhìn hiện lại (postback - người dùng quay lại một trang mà anh/chị ấy đã truy cập trước đó), khung nhìn tương ứng với trang này đã tồn tại, do đó nó chỉ cần được khôi phục lại. Trong trường hợp này, JSF sử dụng thông tin trạng thái của khung nhìn hiện có để xây dựng lại trạng thái của nó.

Pha 2: Áp dụng các giá trị yêu cầu

Mục đích của pha áp dụng các giá trị yêu cầu là để cho mỗi thành phần lấy ra trạng thái hiện tại của nó. Các thành phần trước tiên phải được lấy ra hoặc được tạo ra từ đối tượng FacesContext theo sau là các giá trị của chúng. Các giá trị thành phần thường được lấy ra từ các tham số yêu cầu, mặc dù chúng cũng có thể được lấy ra từ cookie hoặc các tiêu đề. Giá trị từ tham số yêu cầu cho nhiều thành phần được lưu trong submittedValue của thành phần.

Nếu một thuộc tính xử lý-sự kiện tức thời của thành phần được đặt là true (đúng), các giá trị sẽ được biến đổi sang kiểu đúng và được duyệt tính hợp lệ (pha sau sẽ nói nhiều hơn về phép biến đổi). Sau đó, giá trị đã biến đổi được lưu trong thành phần. Nếu việc biến đổi giá trị hoặc việc duyệt tính hợp lệ thất bại, một thông báo lỗi được tạo ra và xếp hàng đợi trong FacesContext, nơi nó sẽ được hiển thị trong pha hoàn trả đáp ứng, cùng với bất kỳ lỗi duyệt tính hợp lệ nào khác.

Pha 3: Xử lý duyệt tính hợp lệ

Phép biến đổi và duyệt tính hợp lệ thông thường xảy ra trong pha xử lý duyệt tính hợp lệ . Thành phần biến đổi submittedValue của nó và lưu trữ giá trị. Vì vậy, nếu trường này được liên kết với một thuộc tính Số nguyên (Integer) (ví dụ thế), thì giá trị được biến đổi thành Số nguyên. Nếu phép biến đổi giá trị không thành công, một thông báo lỗi được tạo ra và xếp hàng đợi trong FacesContext, nơi nó sẽ được hiển thị trong pha hoàn trả đáp ứng, cùng với bất kỳ lỗi duyệt tính hợp lệ nào khác.

Việc xử lý sự kiện đầu tiên của vòng đời diễn ra sau pha áp dụng các giá trị yêu cầu. Ở giai đoạn này, các giá trị của mỗi thành phần được duyệt tính hợp lệ theo các quy tắc duyệt tính hợp lệ của ứng dụng. Các quy tắc duyệt tính hợp lệ có thể được định nghĩa trước (được gửi đi cùng với JSF), hoặc được các nhà phát triển xác định. Các giá trị do người sử dụng nhập vào được so với các quy tắc duyệt tính hợp lệ. Nếu một giá trị nhập vào không hợp lệ, một thông báo lỗi sẽ được thêm vào FacesContext, và thành phần này được đánh dấu là không hợp lệ. Nếu một thành phần được đánh dấu là không hợp lệ, JSF chuyển tiếp đến pha hoàn trả đáp ứng và bỏ qua các pha còn lại. Nó sẽ hiển thị khung nhìn hiện tại với các thông báo lỗi duyệt tính hợp lệ. Nếu không có lỗi duyệt tính hợp lệ nào xảy ra, JSF tiến đến pha cập nhật các giá trị của mô hình.

Pha 4: Cập nhật các giá trị của mô hình

Pha thứ tư của vòng đời ứng dụng JSF — cập nhật các giá trị của mô hình — cập nhật các giá trị thực tại của mô hình phía máy chủ bằng cách cập nhật các thuộc tính của bean quản lý của bạn. Chỉ có các thuộc tính của bean bị liên kết với một giá trị của thành phần được cập nhật. Lưu ý rằng pha này xảy ra sau khi duyệt tính hợp lệ, vì vậy bạn có thể chắc chắn rằng các giá trị được sao chép vào các thuộc tính các bean của bạn là hợp lệ (ít nhất là ở mức trường biểu mẫu; chúng có thể vẫn còn không hợp lệ ở mức quy tắc nghiệp vụ).

Pha 5: Gọi ứng dụng

Tại pha thứ năm của vòng đời — gọi ứng dụng — trình điều khiển của JSF gọi ứng dụng để xử lý đệ trình biểu mẫu. Các giá trị thành phần sẽ được biến đổi, được duyệt tính hợp lệ, và được áp dụng cho các đối tượng của mô hình, do đó bây giờ bạn có thể sử dụng chúng để thi hành logic nghiệp vụ của ứng dụng.

Chính là trong pha này các phương thức của trình xử lý-hành động của bạn được gọi, ví dụ như các phương thức persist()read() trong ContactController của ứng dụng mẫu.

Ở pha này, bạn cũng bắt đầu phải chỉ rõ khung nhìn logic tiếp theo đối với một diễn tiến đã cho hoặc một số diễn tiến có khả năng xảy ra. Bạn làm điều này bằng cách định nghĩa một kết quả cụ thể cho một đệ trình biểu mẫu thành công và trả về kết quả đó. Ví dụ: khi kết quả đúng, di chuyển người dùng đến trang tiếp theo. Để dẫn hướng này hoạt động, bạn phải tạo ra một ánh xạ tới kết quả thành công đó làm quy tắc dẫn hướng trong tệp tin faces-config.xml. Sau khi chuyển hướng xảy ra, bạn tiến tới pha cuối cùng của vòng đời. JSF nhận đối tượng trả về từ phương thức hoạt động và gọi phương thức toString() của nó. Sau đó, nó sử dụng giá trị này như là kết quả cho các quy tắc dẫn hướng. (Phần 1 đã đề cập cấu hình các quy tắc dẫn hướng.)

Pha 6: Hoàn trả đáp ứng

Trong pha thứ sáu của vòng đời — hoàn trả đáp ứng — bạn hiển thị khung nhìn với tất cả các thành phần của nó trong trạng thái hiện tại của chúng.

Hình 6 là một sơ đồ trạng thái đối tượng của sáu pha của vòng đời ứng dụng JSF bao gồm việc duyệt tính hợp lệ và xử lý sự kiện:

Hình 6. Sự tiến triển sáu-pha của vòng đời ứng dụng JSF
Sự tiến triển sáu-pha của vòng đời ứng dụng JSF

Các trình biến đổi dữ liệu JSF

Phép biến đổi là một quá trình đảm bảo dữ liệu của đúng đối tượng hoặc đúng kiểu, do đó bạn biến đổi các giá trị chuỗi ký tự thành các kiểu khác như các đối tượng Ngày tháng (Date), các đối tượng số Dấu phẩy động (Float) nguyên thủy, hoặc số Dấu phảy động (Float). Bạn có thể sử dụng các trình biến đổi kèm sẵn hoặc viết trình biến đổi tùy biến theo nhu cầu. Phần này cho bạn thấy các trình biến đổi tiêu chuẩn của JSF và sau đó trình bày chi tiết về viết các trình biến đổi tùy biến.

Trình biến đổi tiêu chuẩn của JSF

JSF cung cấp nhiều trình biến đổi dữ liệu tiêu chuẩn, và hầu hết các phép biến đổi dữ liệu sẽ diễn ra tự động. Bảng 1 cho thấy các mã nhận dạng ID của trình biến đổi và các lớp thực thi tương ứng được JSF sử dụng cho các phép biến đổi dữ liệu đơn giản.

Các trình biến đổi tiêu chuẩn của JSF
Trình biến đổi Lớp thực thi
javax.faces.BigDecimaljavax.faces.convert.BigDecimalConverter
javax.faces.BigIntegerjavax.faces.convert.BigIntegerConverter
javax.faces.Booleanjavax.faces.convert.BooleanConverter
javax.faces.Bytejavax.faces.convert.ByteConverter
javax.faces.Characterjavax.faces.convert.CharacterConverter
javax.faces.DateTimejavax.faces.convert.DateTimeConverter
javax.faces.Doublejavax.faces.convert.DoubleConverter
javax.faces.Floatjavax.faces.convert.FloatConverter

Vì vậy, nếu bạn liên kết tới một int hoặc một Số nguyên (Integer), phép biến đổi sẽ diễn ra tự động. Listing 19 cho thấy một thành phần của ứng dụng mẫu quản lý các mối giao tiếp liên kết trực tiếp đến age.#{contactController.contact.age}:

Listing 19. Liên kết tới age: JSF biến đổi tự động
   <%-- age --%>
<h:outputLabel value="Age" for="age" accesskey="age" />
<h:inputText id="age" size="3" value="#{contactController.contact.age}">
</h:inputText>

JSF thực hiện điều này cho tất cả các thuộc tính có kiểu nguyên thủy, kiểu bao gói, kiểu chuỗi ký tự (String), và Enum. Thật tuyệt. Nó cũng có các trình biến đổi cho ngày tháng cũng như các số. Các số có thể có nhiều định dạng, do đó, nó có một trình biến đổi cho phép bạn mô tả định dạng mà người sử dụng cuối sẽ dùng. Cũng như thế cho các ngày tháng. Listing 20 cho thấy việc sử dụng các trình biến đổi của JSF để biến đổi một ngày tháng bằng cách sử dụng một định dạng tùy biến:

Mặc dù JSF xử lý các kiểu nguyên thủy và gần nguyên thủy khá tốt theo mặc định, khi làm việc với dữ liệu ngày tháng, bạn phải chỉ rõ thẻ biến đổi <f:convertDateTime/>. Thẻ này dựa vào gói java.text và sử dụng các mẫu ngắn, dài và tùy biến. Listing 20 giải thích cách sử dụng <f:convertDateTime/> như thế nào để bảo đảm ngày tháng năm sinh của người sử dụng biến đổi được thành một đối tượng ngày tháng, được định dạng là MM/yyyy (tháng/năm). Xem tài liệu hướng dẫn API Java để tìm java.text.SimpleDateFormat với một danh sách các mẫu (xem Tài nguyên).

Listing 20. Chỉ rõ định dạng cho ngày tháng
   <%-- birthDate --%>
<h:outputLabel value="Birth Date" for="birthDate" accesskey="b" /> 
   <h:inputText id="birthDate" value="#{contactController.contact.birthDate}">
      <f:convertDateTime pattern="MM/yyyy"/>
   </h:inputText>
<h:message for="birthDate" errorClass="errorClass" />

Giới thiệu các trình biến đổi tùy biến của JSF

Trình biến đổi dữ liệu tùy biến là cần thiết nếu bạn cần biến đổi dữ liệu vào trong một đối tượng giá trị đặc thù của ứng dụng, như trong ví dụ sau:

  • Chuỗi ký tự thành PhoneNumber object (PhoneNumber.areaCode, PhoneNumber.prefix, ...)
  • Chuỗi ký tự thành đối tượng Name (Name.first, Name.last)
  • Chuỗi ký tự thành đối tượng ProductCode object (ProductCode.PartNum,ProductCode.rev, ...)
  • Chuỗi ký tự thành đối tượng Group
  • Chuỗi ký tự thành đối tượng Tags

Để tạo một trình biến đổi tùy biến, bạn cần phải:

  1. Thực hiện giao diện trình biến đổi (còn được gọi là javax.faxes.convert.Converter).
  2. Thực hiện phương thức getAsObject(), trong đó biến đổi một trường (chuỗi ký tự) thành một đối tượng (ví dụ PhoneNumber).
  3. Thực hiện phương thức getAsString, trong đó biến đổi một đối tượng (ví dụ, PhoneNumber) thành một chuỗi ký tự.
  4. Đăng ký các trình biến đổi tùy biến của bạn vào bối cảnh Faces.

Bạn có thể xem trong hình 7 các bước này khớp với vòng đời của ứng dụng JSF như thế nào:

Hình 7. Các phương thức getAsObject()getAsString() của trình biến đổi tùy biến
Biến đổi phướng thức getAsObject và getAsString

Trong Hình 7, JSF gọi phương thức getAsObject() của trình biến đổi tùy biến trong pha tiến hành duyệt tính hợp lệ. Đây là nơi trình biến đổi phải biến đổi giá trị chuỗi ký tự yêu cầu thành kiểu đối tượng mong muốn và sau đó trả về đối tượng để lưu trữ trong thành phần JSF tương ứng. Khi giá trị được hoàn trả trở lại trong khung nhìn, JSF sẽ gọi phương thức getAsString trong pha hoàn trả đáp ứng. Điều này có nghĩa là trình biến đổi cũng chịu trách nhiệm chuyển dữ liệu đối tượng trở lại thành một biểu diễn chuỗi ký tự .

Thực hiện giao diện của Trình biến đổi

Nhắc lại rằng đối tượng miền Contact của ứng dụng mẫu có mối quan hệ nhiều-với-một với Group và mối quan hệ nhiều-với-nhiều với Tag. Trước đó (xem Listing 9Listing 11) bạn đã thực hiện phép biến đổi từ giá trị id thành các đối tượng miền trong ContactController. Thay vì liên kết trực tiếp vào các thuộc tính miền trong khung nhìn, bạn đã liên kết tới trường id trong ContactController. Nếu bạn sử dụng các trình biến đổi JSF, bạn có thể loại bỏ rất nhiều đoạn mã kiểu này và làm đơn giản trình điều khiển và khung nhìn.

Listings 21 và 22 thực hiện giao diện Converter cho các trình biến đổi GroupTag, tương ứng:

Listing 21. Trình biến đổi Group thực hiện giao diện Converter
package com.arcmind.contact.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.application.FacesMessage;

import com.arcmind.contact.model.Group;
import com.arcmind.contact.model.GroupRepository;

public class GroupConverter implements Converter {

   ...
}
Listing 22. Trình biến đổi Thẻ (Tag) thực hiện giao diện Converter
package com.arcmind.contact.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import com.arcmind.contact.model.Tag;
import com.arcmind.contact.model.TagRepository;

public class TagConverter implements Converter {

   ...
}

Thực hiện phương thức getAsObject()

Bước kế tiếp là thực hiện phương thức getAsObject() biến đổi một trường (chuỗi ký tự) thành một đối tượng. Listing 23 cho thấy phương thức getAsObject() cho GroupConverter:

Listing 23. Phương thức getAsObject() cho GroupConverter
...
public class GroupConverter implements Converter {

   public Object getAsObject(FacesContext facesContext, UIComponent component,
         String value) {
      GroupRepository repo = (GroupRepository) facesContext
            .getExternalContext().getApplicationMap()
            .get("groupRepository");
      Long id = Long.valueOf(value);
      if (id == -1L) {
         throw new ConverterException(new FacesMessage(
               FacesMessage.SEVERITY_ERROR, "required", "required"));
      }
      return repo.lookup(id);
   }
   ...

}

Lưu ý rằng GroupConverter tìm kiếm groupRepository và sử dụng groupRepository để đọc Group từ kho lưu trữ. Cũng cần chú ý rằng nó kiểm tra để xem giá trị có là -1L không, và nếu đúng thế, nó gửi một báo lỗi bắt buộc bằng cách đưa ra một ConverterException mới.

TagConverter cũng làm tương tự. Nó sử dụng tagRepository để tìm kiếm giá trị thẻ. Listing 24 cho thấy phương thức getAsObject() cho TagConverter:

Listing 24. Phương thức getAsObject() cho TagConverter
...

public class TagConverter implements Converter {

   public Object getAsObject(FacesContext facesContext, UIComponent component,
         String value) {
      TagRepository repo = (TagRepository) facesContext
            .getExternalContext().getApplicationMap()
            .get("tagRepository");
      return repo.lookup(Long.valueOf(value));
   }

   ...
}

Không trình biến đổi nào thực hiện kiểm tra lỗi. Nếu đối tượng kho lưu trữ của bạn thực tại đang trao đổi với máy chủ cơ sở dữ liệu hoặc cache, bạn có thể muốn bao bọc phương thức getAsObject() trong một khối lệnh try/catch, và thêm một FacesMessage với mức nghiêm trọng là SEVERITY_FATAL nếu có một vấn đề xảy ra với cơ sở dữ liệu — tương tự như cách bạn xử lý trường hợp -1L/bắt buộc báo lỗi GroupConverter như trong Listing 23.

Thực hiện phương thức getAsString

JSF cần hiển thị giá trị được chọn hiện tại. Nó thực hiện điều này bằng cách gọi phương thức getAsString() của Trình biến đổi (Converter). Listing 25 thực hiện phương thức getAsString() cho GroupConverter:

Listing 25. Phương thức getAsString() cho GroupConverter
...
public class GroupConverter implements Converter {

   ...
   
   public String getAsString(FacesContext facesContext, UIComponent component,
         Object value) {
      return value == null ? "-1" : "" + ((Group) value).getId();
   }

}

Listing 26 Thực hiện phương thức getAsString() cho TagConverter:

Listing 26. Phương thức getAsString() cho TagConverter
...
public class TagConverter implements Converter {

   ...
   
   public String getAsString(FacesContext facesContext, UIComponent component,
         Object value) {
      return value == null ? "-1" : "" + ((Tag) value).getId();
   }

}

Đăng ký trình biến đổi tùy biến của bạn trong bối cảnh Faces

Sau khi bạn viết các trình biến đổi của mình, bạn cần JSF sử dụng chúng mỗi khi nó thấy một giá trị liên kết sinh ra trong một Group hay Tag. Bạn thực hiện điều này bằng cách đăng ký các trình biến đổi tùy biến của bạn trong tệp tin Faces-config.xml, khi sử dụng phần tử <converter>, như hiển thị trong Listing 27:

Listing 27. Đăng ký các trình biến đổi với faces-config.xml
<converter>
   <converter-for-class>
      com.arcmind.contact.model.Group
   </converter-for-class>
   <converter-class>
      com.arcmind.contact.converter.GroupConverter
   </converter-class>
</converter>

<converter>
   <converter-for-class>
      com.arcmind.contact.model.Tag
   </converter-for-class>
   <converter-class>
      com.arcmind.contact.converter.TagConverter
   </converter-class>
</converter>

Listing 27 chỉ rõ lớp trình biến đổi ứng với phần tử <converter-class> và lớp được bạn cung cấp phép biến đổi cho nó, ứng với phần tử <converter-for-class>.

Làm cho các trình biến đổi làm việc với một danh sách các thẻ

Không may là, các trình biến đổi không làm việc với các mẫu tổng quát (generics) như là List<Tag>. JSF không cho phép biến đổi thành các danh sách mẫu tổng quát. (Nó lẽ ra phải cho phép. Các API lâu bền Java [JPA] không có vấn đề gì khi xử lý List<Tag> để định nghĩa các mối quan hệ. JPA và JSF 1.2 được xuất bản cùng thời gian với Java EE 5, do đó bạn đã nghĩ rằng cả hai sẽ hỗ trợ mẫu tổng quát). Để khắc phục vấn đề này, bạn có thể sử dụng các mảng. Listing 28 cho thấy việc sử dụng một mảng Tag, thay cho List<Tag>:

Listing 28. Sử dụng các mảng thay cho các mẫu tổng quát
public class Contact implements Serializable {
    ...
    private List<Tag> tags;
   public Tag[] getTags() {
       if (tags != null) {
           return tags.toArray(new Tag[tags.size()]);
       } else {
           return null;
       }
   }

   public void setTags(Tag[] tags) {
      this.tags = Arrays.asList(tags);
   }

Các trình duyệt tính hợp lệ của JSF

Mục đích chính của phép biến đổi và phép duyệt tính hợp lệ là đảm bảo các giá trị đã được làm sạch hoàn toàn trước khi cập nhật dữ liệu của mô hình. Rồi sau đó, khi đến lúc gọi các phương thức của ứng dụng để làm một cái gì đó với dữ liệu, bạn có thể yên tâm đặt một số giả định nào đó về trạng thái của mô hình. Phép biến đổi và phép duyệt tính hợp lệ cho phép bạn tập trung vào logic nghiệp vụ hơn là vào các tiêu chuẩn tẻ nhạt về dữ liệu đủ tư cách cho đầu vào như kiểm tra rỗng, chiều dài của các từ hạn định, các giới hạn phạm vi và v.v.

Vậy thì điều quan trọng là các quá trình biến đổi và duyệt tính hợp lệ xảy ra trước khi dữ liệu thành phần được liên kết vào bean quản lý của bạn trong pha cập nhật dữ liệu mô hình của vòng đời. Như bạn đã thấy trong phần "Vòng đời của ứng dụng JSF", phép biến đổi và phép duyệt tính hợp lệ xảy ra trong pha xử lý duyệt tính hợp lệ — đầu tiên là phép biến đổi, sau đó là phép duyệt tính hợp lệ.

Có bốn dạng duyệt tính hợp lệ trong JSF:

Phần này giải thích và biểu diễn từng dạng duyệt tính hợp lệ này.

Phép duyệt tính hợp lệ tiêu chuẩn

JSF cung cấp ba thành phần duyệt tính hợp lệ tiêu chuẩn:

  • DoubleRangeValidator: Giá trị tại chỗ của thành phần phải là kiểu số; phải nằm trong phạm vi đã ấn định bởi các giá trị nhỏ nhất, các giá trị lớn nhất, hoặc cả hai.
  • LongRangeValidator: Giá trị tại chỗ của thành phần phải là kiểu số và có thể biến đổi thành số nguyên dài; phải nằm trong phạm vi đã ấn định bởi các giá trị nhỏ nhất, các giá trị lớn nhất, hoặc cả hai.
  • LengthValidator: Kiểu phải là chuỗi ký tự; chiều dài phải nằm trong phạm vi đã ấn định bởi các giá trị nhỏ nhất, các giá trị lớn nhất, hoặc cả hai.

Trong ví dụ ứng dụng này, tuổi của một cá nhân cần giao tiếp có thể là bất kỳ số nguyên nào hợp lệ. Bởi vì sẽ không có ý nghĩa gì nếu cho phép tuổi là -2, có lẽ bạn sẽ muốn thêm một phép duyệt tính hợp lệ cho trường này. Liệt kê 29 cho thấy một số mã lệnh duyệt tính hợp lệ đơn giản khi sử dụng <f:validateLongRange> để đảm bảo tính toàn vẹn dữ liệu của mô hình trong một trường tuổi:

Listing 29. Duyệt tính hợp lệ cho tuổi với các giá trị hợp lý, sử dụng <f:validateLongRange>
   <%-- age --%>
<h:outputLabel value="Age" for="age" accesskey="age" />
<h:inputText id="age" size="3" value="#{contactController.contact.age}">
   <f:validateLongRange minimum="0" maximum="150"/>
</h:inputText>
<h:message for="age" errorClass="errorClass" />

Một khi bạn đã giải quyết được trường tuổi, bạn có thể muốn xác định các giới hạn chiều dài cho trường First Name, như hiển thị trong Listing 30.

Listing 30. Đảm bảo chắc chắn firstName không quá dài hay quá ngắn
   <%-- First Name --%>
<h:outputLabel value="First Name" for="firstName" accesskey="f" />
<h:inputText id="firstName" label="First Name" required="true"
 value="#{contactController.contact.firstName}" size="10" >
   <f:validateLength minimum="2" maximum="25" />
</h:inputText>
<h:message for="firstName" errorClass="errorClass" />

Mặc dù nó hoạt động với nhiều kịch bản, nhưng phép duyệt tính hợp lệ kèm sẵn của JSF cũng bị hạn chế một chút. Nếu bạn đang làm việc với phép duyệt tính hợp lệ của e-mail, các số điện thoại, các URL, các ngày tháng, và v.v, đôi khi tốt hơn là viết riêng trình duyệt tính hợp lệ của bạn; vấn đề này sẽ được thảo luận sau trong phần này. Bạn cũng có thể sử dụng các trình duyệt tính hợp lệ được Tomahawk, Shale, JSF-validations, và Crank cung cấp (xem Tài nguyên).

Phép duyệt tính hợp lệ mức ứng dụng

Theo khái niệm, phép duyệt tính hợp lệ mức-ứng dụng thực sự là phép duyệt tính hợp lệ logic nghiệp vụ. JSF tách phép duyệt tính hợp lệ mức biểu mẫu hay mức trường dữ liệu khỏi phép duyệt tính hợp lệ logic nghiệp vụ. Về cơ bản, phép duyệt tính hợp lệ mức ứng dụng đòi hỏi phải bổ sung thêm mã vào các phương thức bean quản lý, sử dụng mô hình để xác định tiêu chuẩn của dữ liệu đã liên kết tới mô hình của bạn. Trong trường hợp của một xe đẩy mua hàng trong siêu thị, phép duyệt tính hợp lệ mức biểu mẫu có thể xác nhận số lượng món hàng đã nhập vào biểu mẫu có hợp lệ không, nhưng bạn sẽ cần có phép duyệt tính hợp lệ mức logic kinh doanh để kiểm tra xem người mua hàng đã vượt quá giới hạn tín dụng của mình hay không. Đây là một ví dụ khác về sự tách biệt các mối quan tâm trong JSF.

Ví dụ, giả sử rằng người dùng nhấp vào một nút bấm được liên kết với một phương thức hành động, mà sẽ được gọi trong pha gọi ứng dụng (xem lại Hình 5 để biết thêm chi tiết). Trước khi thực hiện bất kỳ thao tác dữ liệu nào, mà dữ liệu có lẽ đã được cập nhật trong pha cập nhật dữ liệu mô hình, bạn có thể thêm mã để kiểm tra xem dữ liệu nhập vào có hợp lệ không, dựa vào các quy tắc nghiệp vụ của ứng dụng.

Chẳng hạn, trong ví dụ ứng dụng, người sử dụng nhấn chuột vào nút Update/Add đã được liên kết với phương thức persist() của trình điều khiển ứng dụng. Bạn có thể thêm mã duyệt tính hợp lệ vào phương thức persist() để xác định xem liệu cặp firstName/lastName đã có trong hệ thống chưa. Trong trường hợp mối giao tiếp này đã tồn tại, bạn cũng có thể thêm một thông báo đến FacesContext và sau đó trực tiếp tới JSF để lưu lại khung nhìn hiện tại bằng cách trả về không (null) làm kết quả (nếu các quy tắc dẫn hướng áp dụng cho hành động này).

Chúng ta hãy xem lại ứng dụng quản lý các mối giao tiếp và thực hiện một số logic mức ứng dụng trong phương thức hành động persist(), như hiển thị trong Listings 31 và 32. Listing 31 hiển thị logic duyệt tính hợp lệ mức ứng dụng trong trình điều khiển:

Listing 31. Logic duyệt tính hợp lệ mức ứng dụng trong trình điều khiển
public class ContactController {
   public String persist() {
       
       /* Perform the application level validation. */
        try {
            contact.validate();
        } catch (ContactValidationException contactValidationException) {
            addErrorMessage(contactValidationException.getLocalizedMessage());
            return null;
        }
      
      
      /* Turn form off, turn link on. */
      form.setRendered(false);
      addNewCommand.setRendered(true);
      
      
      /* Add a status message. */
      if (contactRepository.persist(contact) == null) {
         addStatusMessage("Added " + contact);
      } else {
         addStatusMessage("Updated " + contact);
      }
      return "contactPersisted";
   }               
    private void addErrorMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                FacesMessage.SEVERITY_ERROR, message, null));
    }

Trong Listing 31, bạn có thể thấy rằng phương thức persist() gọi validate() trên đối tượng mối giao tiếp. Nó bắt giữ bất kỳ trường hợp ngoại lệ nào và biến đổi thông báo lỗi trường hợp ngoại lệ thành một FacesMessage. Nếu một trường hợp ngoại lệ xảy ra, nó trả về một kết quả null, có nghĩa là lưu lại khung nhìn hiện tại và không dẫn hướng tới khung nhìn tiếp theo.

Mã duyệt tính hợp lệ thực có trong mô hình — đó là phương thức validate() của lớp Contact, như hiển thị trong Listing 32. Điều này rất quan trọng: khi bạn thêm quy tắc duyệt tính hợp lệ vào lớp Contact, bạn không cần phải thay đổi trình điều khiển hoặc các tầng khung nhìn.

Listing 32. Duyệt tính hợp lệ trong mô hình, chứ không phải trong trình điều khiển
...
public class Contact implements Serializable {
   ...               
    public void validate() throws ContactValidationException {
      if (
         (homePhoneNumber == null || "".equals(homePhoneNumber))  &&
         (workPhoneNumber == null || "".equals(workPhoneNumber))  &&
         (mobilePhoneNumber == null || "".equals(mobilePhoneNumber))             
      ) {
        throw new ContactValidationException("At least one phone number" +
              "must be set");
        
    }
}

Việc duyệt tính hợp lệ mức ứng dụng rõ ràng là trực tiếp và dễ dàng thực hiện. Lợi thế của nó là:

  • Dễ thực hiện
  • Không cần có một lớp tách riêng (trình duyệt tính hợp lệ tùy biến)
  • Không cần biên tập trang để chỉ rõ trình duyệt tính hợp lệ

Những nhược điểm của việc duyệt tính hợp lệ mức ứng dụng là ở chỗ nó xảy ra sau các hình thức duyệt tính hợp lệ khác (tiêu chuẩn, tuỳ biến và thành phần), và các thông báo lỗi chỉ hiển thị sau khi các hình thức duyệt tính hợp lệ khác đã xảy ra.

Cuối cùng, việc duyệt tính hợp lệ mức ứng dụng nên được sử dụng chỉ cho các trường hợp yêu cầu duyệt tính hợp lệ logic nghiệp vụ.

Trình duyệt tính hợp lệ tùy biến trong các bean phía sau

Bạn cần phải xây dựng các thành phần duyệt tính hợp lệ tùy biến riêng của bạn cho các kiểu dữ liệu không được hỗ trợ bởi các trình duyệt tính hợp lệ JSF tiêu chuẩn, bao gồm cả các địa chỉ e-mail và mã ZIP. Bạn cũng cần phải xây dựng trình duyệt tính hợp lệ riêng của bạn trong trường hợp bạn muốn kiểm soát rõ ràng các thông báo duyệt tính hợp lệ được hiển thị cho người dùng cuối. Với JSF, bạn có thể tạo các thành phần duyệt tính hợp lệ cắm chạy được và sử dụng lại trong toàn bộ các ứng dụng web của bạn.

Như một cách thay thế cho việc tạo ra một lớp duyệt tính hợp lệ riêng biệt, bạn có thể chỉ cần thực hiện duyệt tính hợp lệ tuỳ biến theo yêu cầu trong phương thức bean phía sau. Điều này là tốt hơn cho các nhà phát triển ứng dụng. Ví dụ, bạn có thể viết một phương thức trong một bean quản lý để duyệt tính hợp lệ các số điện thoại, như hiển thị trong Listing 33:

Listing 33. Duyệt tính hợp lệ của các số điện thoại (Phone-number)
public class ContactValidators {
    private static Pattern phoneMask;

    static {
        String countryCode = "^[0-9]{1,2}";
        String areaCode = "(|-|\\(){1,2}[0-9]{3}(|-|\\)){1,2}";
        String prefix = "(|-)?[0-9]{3}";
        String number = "(|-)[0-9]{4}$";
        phoneMask = Pattern.compile(countryCode + areaCode + prefix + number);
    }

    public void validatePhone(FacesContext context, UIComponent component,
            Object value) throws ValidatorException {

        String sValue = (String)value;

        Matcher matcher = phoneMask.matcher(sValue);

        if (!matcher.matches()) {
            FacesMessage message = new FacesMessage();
            message.setDetail("Phone number not valid");
            message.setSummary("Phone number not valid");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            throw new ValidatorException(message);
        }

    }
... //ADD MORE VALIDATION METHODS FOR THE APP HERE!

}

Lớp ContactValidators có một phương thức validatePhone(). Phương thức validatePhone() sử dụng API các biểu thức chính quy của Java (API regex Java) để đảm bảo chắc chắn rằng chuỗi ký tự đã nhập vào là một số điện thoại hợp lệ. Nếu giá trị không khớp với mẫu, thì phương thức validatePhone() đưa ra một ValidatorException.

Để sử dụng lớp ContactValidators, bạn đăng ký nó trong faces-config.xml file, như hiển thị trong Listing 34:

Listing 34. Đăng ký ContactValidators như là một bean quản lý
<managed-bean>
 <managed-bean-name>contactValidators</managed-bean-name>
 <managed-bean-class>com.arcmind.contact.validators.ContactValidators</managed-bean-class>
 <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

Để sử dụng trình duyệt tính hợp lệ, bạn dùng thuộc tính validator, như hiển thị trong Listing 35, cho các mục công việc (work), địa chỉ nhà ở (home) và số điện thoại di động (mobile phone-number):

Listing 35. Sử dụng trình duyệt tính hợp lệ trong khung nhìn với thuộc tính validator
   <%-- Work --%>
<h:outputLabel value="Work" for="work" accesskey="w" />
<h:inputText id="work" 
   value="#{contactController.contact.workPhoneNumber}" size="11" 
   validator="#{contactValidators.validatePhone}" />
   <h:message for="work" errorClass="errorClass" />
   <%-- Home --%>
<h:outputLabel value="Home" for="home" accesskey="h" />
<h:inputText id="home" 
   value="#{contactController.contact.homePhoneNumber}" size="11" 
   validator="#{contactValidators.validatePhone}" />
<h:message for="home" errorClass="errorClass" />
   <%-- Mobile --%>
<h:outputLabel value="Mobile" for="mobile" accesskey="m" />
<h:inputText id="mobile" 
   value="#{contactController.contact.mobilePhoneNumber}" size="11" 
   validator="#{contactValidators.validatePhone}" />
<h:message for="mobile" errorClass="errorClass" />

Như bạn có thể thấy, bạn liên kết phương thức validatePhone() với thành phần <h:inputText>: <h:inputText id="mobile" ... validator="#{contactValidators.validatePhone}".

Việc sử dụng một bean quản lý để duyệt tính hợp lệ là tốt cho các nhà phát triển ứng dụng. Tuy nhiên, nếu bạn đang phát triển một khung công tác có thể sử dụng lại được hoặc một tập các thành phần có thể sử dụng lại được, sẽ là tốt hơn khi tạo các trình duyệt tính hợp lệ tùy biến độc lập.

Trình duyệt tính hợp lệ tùy biến độc lập

Với JSF, bạn có thể tạo các thành phần duyệt tính hợp lệ có khả năng cắm chạy được và sử dụng lại trong suốt các ứng dụng web của bạn.

Để tạo một trình duyệt tính hợp lệ, bạn thực hiện theo các bước sau:

  1. Tạo ra một lớp mà sẽ thực hiện giao diện Validator (javax.faces.validator.Validator).
  2. Thực hiện phương thức validate().
  3. Đăng ký trình duyệt tính hợp lệ tùy biến của bạn trong tệp tin faces-config.xml.
  4. Sử dụng thẻ <f:validator/> trong tệp JSP của bạn.

Chúng ta hãy nhìn vào từng bước một trong các bước ở trên, bao gồm ví dụ mã để tạo một trình duyệt tính hợp lệ tùy biến theo yêu cầu.

Bước 1: Thực hiện giao diện Validator

Bước đầu tiên là thực hiện giao diện Validator, như hiển thị trong Listing 36:

Listing 36. Thực hiện giao diện Validator
package com.arcmind.validators;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
               
public class ZipCodeValidator implements Validator {

    /** Accepts zip codes like 85710 */
    private static final String ZIP_REGEX = "[0-9]{5}";

    /** Optionally accepts a plus 4 */
    private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?";

    private static Pattern mask = null;

    static {
        mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX);
    }

Bước 2: Thực hiện phương thức validate()

Tiếp theo, bạn cần phải thực hiện phương thức validate(), như hiển thị trong Listing 37:

Listing 37. Thực hiện phương thức validate()
public class ZipCodeValidator implements Validator {

    ...
    public void validate(FacesContext context, UIComponent component,
            Object value) throws ValidatorException {

        /* Get the string value of the current field */
        String zipField = (String) value;

        /* Check to see if the value is a zip code */
        Matcher matcher = mask.matcher(zipField);

        if (!matcher.matches()) {

            FacesMessage message = new FacesMessage();
            message.setDetail("Zip code not valid");
            message.setSummary("Zip code not valid");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            throw new ValidatorException(message);
        }

    }
}

Bước 3: Đăng ký trình duyệt tính hợp lệ tùy biến của bạn

Mã để đăng ký trình duyệt tính hợp lệ tùy biến với FacesContext, như hiển thị trong Listing 38, bây giờ trông quen thuộc với bạn:

Listing 38. Đăng ký trình duyệt tính hợp lệ tùy biến của bạn trong faces-config.xml
<validator>
   <validator-id>arcmind.zipCode</validator-id>
   <validator-class>com.arcmind.validators.ZipCodeValidator</validator-class>
</validator>

Bước 4: Sử dụng thẻ <f:validator> trong tệp JPS của bạn

Thẻ <f:validator/> khai báo việc sử dụng trình duyệt tính hợp lệ zipCode, như hiển thị trong Listing 39:

Listing 39. Sử dụng thẻ <f:validator> trong tệp JSP của bạn
   <%-- zip --%>
<h:outputLabel value="Zip" for="zip" accesskey="zip" />
<h:inputText id="zip" size="5"
   value="#{contactController.contact.zip}">
   <f:validator validatorId="arcmind.zipCode"/>
</h:inputText>
<h:message for="zip" errorClass="errorClass" />

Nhìn chung, việc tạo các trình duyệt tính hợp lệ tùy biến khá đơn giản và làm cho việc duyệt tính hợp lệ có thể sử dụng lại trên nhiều ứng dụng. Mặt tiêu cực là bạn cần phải tạo một lớp khác và quản lý việc đăng ký trình duyệt tính hợp lệ trong bối cảnh faces. Tuy nhiên, bạn có thể làm cho việc triển khai thực hiện trình duyệt tính hợp lệ tuỳ biến của bạn tiến thêm một bước nữa và làm cho nó trông giống như phép duyệt tính hợp lệ kèm sẵn bằng cách tạo ra một thẻ tùy biến để sử dụng trình duyệt tính hợp lệ này. Với các trường hợp duyệt tính hợp lệ được quan tâm chung như của e-mail, cách tiếp cận này có thể hỗ trợ triết lý thiết kế, khi mà sử dụng lại mã và hành vi nhất quán của ứng dụng là quan trọng nhất.

Quay lại chủ đề phép duyệt tính hợp lệ và phép biến đổi

Khi bạn bắt đầu tới pha duyệt tính hợp lệ, việc biến đổi đã xảy ra. Ví dụ, nếu bạn có một thuộc tính int được liên kết vào một trường inputText, thì trường này sẽ được biến đổi trước khi bạn bắt đầu duyệt tính hợp lệ của nó.

Giả sử bạn có một đối tượng giá trị PhoneNumber, và thay vì sử dụng các Chuỗi ký tự (String) để lưu các số điện thoại trong Contact, bạn sử dụng PhoneNumber. Thế thì quy tắc duyệt tính hợp lệ trong Listing 33 cho các số điện thoại sẽ không có nhiều ý nghĩa. Thực vậy, quy tắc duyệt tính hợp lệ ấy chỉ chứng tỏ rằng String đã có định dạng của một số điện thoại. Logic đó thực sự nên là một phần của trình biến đổi, như Listing 40 đã chứng tỏ:

Listing 40. Quay lại chủ đề phép biến đổi và phép duyệt tính hợp lệ: PhoneConverter
package com.arcmind.contact.converter;

import java.util.regex.Pattern;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import com.arcmind.contact.model.PhoneNumber;

/**
 * @author Richard Hightower
 * 
 */
public class PhoneConverter implements Converter {
    private static Pattern phoneMask;
    static {
        String countryCode = "^[0-9]{1,2}";
        String areaCode = "( |-|\\(){1,2}[0-9]{3}( |-|\\)){1,2}";
        String prefix = "( |-)?[0-9]{3}";
        String number = "( |-)[0-9]{4}$";
        phoneMask = Pattern.compile(countryCode + areaCode + prefix + number);
    }

    public Object getAsObject(FacesContext context, UIComponent component,
            String value) {
        System.out.println("PhoneConverter.getAsObject()");

        if (value.isEmpty()) {
            return null;
        }
        /* Before we parse, let's see if it really is a phone number. */
        if (!phoneMask.matcher(value).matches()) {
            FacesMessage message = new FacesMessage();
            message.setDetail("Phone number not valid");
            message.setSummary("Phone number not valid");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            throw new ConverterException(message);
        }
        
        /* Now let's parse the string and populate a phone number object. */
        PhoneNumber phone = new PhoneNumber();
        phone.setOriginal(value);
        String[] phoneComps = value.split("[ ,()-]");
        String countryCode = phoneComps[0];
        phone.setCountryCode(countryCode);

        if ("1".equals(countryCode) && phoneComps.length == 4) {
            phone.setAreaCode(phoneComps[1]);
            phone.setPrefix(phoneComps[2]);
            phone.setNumber(phoneComps[3]);
        } else if ("1".equals(countryCode) && phoneComps.length != 4) {
            throw new ConverterException(new FacesMessage(
                    "No Soup for you butter fingers!"));
        } else if (phoneComps.length == 1 && value.length() > 10){
            phone.setCountryCode(value.substring(0,1));
            phone.setAreaCode(value.substring(1,4));
            phone.setPrefix(value.substring(4,7));
            phone.setNumber(value.substring(7));            
        } else {
            phone.setNumber(value);
        }
        return phone;
    }

    public String getAsString(FacesContext context, UIComponent component,
            Object value) {
        System.out.println("PhoneConverter.getAsString()");
        return value.toString();
    }
}

Điều tốt đẹp về các trình biến đổi, không giống như các trình duyệt tính hợp lệ, là bạn có thể đăng ký chúng trong faces-config.xml (xem Listing 27) sao cho trình biến đổi được gắn với một lớp riêng. Bất kỳ thời điểm nào mà lớp xuất hiện trong một liên kết giá trị của ngôn ngữ biểu thức (EL), trình biến đổi được sử dụng tự động, bạn không cần phải thêm một thẻ<f:converter> cho JSP. Trình biến đổi số điện thoại mới sẽ tự động được áp dụng cho các PhoneNumber, và bạn không cần phải chỉ rõ trình biến đổi trong khung nhìn.

Bởi vì việc duyệt tính hợp lệ của số điện thoại cũ là một phần trong việc biến đổi số điện thoại, bạn có thể hỏi trình duyệt tính hợp lệ của một số điện thoại bây giờ sẽ như thế nào. Câu hỏi này có thể được trả lời bằng cách viết một trình duyệt tính hợp lệ, như hiển thị trong Listing 41, để chứng tỏ rằng một số điện thoại là từ Arizona:

Listing 41. Hãy bảo đảm số điện thoại là ở AZ
package com.arcmind.contact.validators;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;

import com.arcmind.contact.model.PhoneNumber;


public class ContactValidators {

    public void validatePhone(FacesContext context, UIComponent component,
            Object value) throws ValidatorException {

        System.out.println("ContactValidators.validatePhone()");
        PhoneNumber phoneNumber = (PhoneNumber)value; 

        if (!phoneNumber.getAreaCode().equals("520")
            && !phoneNumber.getAreaCode().equals("602")) {
            FacesMessage message = new FacesMessage();
            message.setDetail("Arizona residents only");
            message.setSummary("Arizona residents only");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            throw new ValidatorException(message);
        }

    }

}

Lưu ý rằng trình duyệt tính hợp lệ của số điện thoại này, không giống như cái trước đó, không làm việc với các Chuỗi ký tự (String). Khi nó bắt đầu được gọi, trình biến đổi đã được gọi rồi. Vì thế, giá trị không phải là một String mà là một PhoneNumber.


Làm việc với các trình nghe pha (phase listener)

Trình nghe pha, theo tài liệu hướng dẫn API của JSF (xem Tài nguyên), là "một giao diện được thực hiện bởi các đối tượng muốn được thông báo tại thời điểm bắt đầu và kết thúc của quá trình xử lý của mỗi pha tiêu chuẩn trong chu kỳ xử lý yêu cầu" (Sun Microsystems Inc., 2006). Bây giờ khi bạn đã viết một số trình biến đổi, trình duyệt tính hợp lệ và phương thức hành động, bạn sẽ viết một số trình nghe pha trong phần này. Trước JSF 1.2, các PhaseListener đã được định nghĩa toàn cục. Trong JSF 1.2, các sự kiện PhaseListener có thể được đăng ký ở mức khung nhìn hoặc sử dụng thẻ <f:phaseListener binding="..." />.

Thực hiện các trình nghe pha

Để thực hiện một PhaseListener, bạn thực hiện giao diện PhaseListener, như hiển thị trong Listing 42:

Listing 42. DebugPhaseListener
package com.arcmind.phase;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

@SuppressWarnings("serial")
public class DebugPhaseListener implements PhaseListener {

    public void beforePhase(PhaseEvent phaseEvent) {
        System.out.println("------ BEFORE PHASE " + phaseEvent.getPhaseId());
    }

    public void afterPhase(PhaseEvent phaseEvent) {
        System.out.println("------ AFTER PHASE " + phaseEvent.getPhaseId());

        if (phaseEvent.getPhaseId() == PhaseId.RENDER_RESPONSE) {
            System.out.println("REQUEST END\n\n");
        }

    }

    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }

}

PhaseListener thông báo cho bạn biết trước và sau mỗi pha JSF. Bạn báo cho JSF biết bạn quan tâm đến pha nào bằng phương thức getPhaseId. PhaseId.ANY_PHASE ngụ ý rằng bạn muốn được thông báo trước và sau mỗi pha. DebugPhaseListener in ra tên sự kiện pha, do đó bạn có thể thấy sự việc xảy ra ở đâu. Tôi đã thêm lệnh System.out.println vào mọi phương thức duyệt tính hợp lệ, biến đổi, hành động. Tôi cũng đã thêm một lệnh System.out.println vào các phương thức get và set của thuộc tính firstName để bạn có thể thấy nó khi nó được truy cập.

Tiếp theo, bạn phải đăng ký trình nghe pha trong faces-config.xml, như hiển thị trong Listing 43:

Listing 43. Đăng ký DebugPhaseListener trong faces-config.xml
<lifecycle>
   <phase-listener>com.arcmind.phase.DebugPhaseListener</phase-listener>
</lifecycle>

Đầu ra của trình nghe pha

Listing 44 cho thấy điều gì sẽ xảy ra khi bạn nạp một biểu mẫu lần đầu tiên:

Listing 44. Đầu ra của DebugPhaseListener khi nạp biểu mẫu lần đầu tiên
------ BEFORE PHASE RESTORE_VIEW 1
------ AFTER PHASE RESTORE_VIEW 1
------ BEFORE PHASE RENDER_RESPONSE 6
ContactController.getContacts()
------ AFTER PHASE RENDER_RESPONSE 6
REQUEST END

Dựa vào mã trong Listing 42, JSF thấy rằng đây là một yêu cầu khởi đầu cho một khung nhìn, nó sử dụng JSP để xây dựng khung nhìn, và sau đó đi ngay lập tức sang pha hoàn trả đáp ứng. Lưu ý rằng phương thức getContacts() của trình điều khiển được gọi trong pha hoàn trả đáp ứng.

Khi bạn nhấn vào liên kết Add New, bạn thấy kết quả được hiển thị trong Listing 45:

Listing 45. Đầu ra của DebugPhaseListener sau khi phương thức addNew() được gọi
------ BEFORE PHASE RESTORE_VIEW 1
------ AFTER PHASE RESTORE_VIEW 1
------ BEFORE PHASE APPLY_REQUEST_VALUES 2
------ AFTER PHASE APPLY_REQUEST_VALUES 2
------ BEFORE PHASE PROCESS_VALIDATIONS 3
------ AFTER PHASE PROCESS_VALIDATIONS 3
------ BEFORE PHASE UPDATE_MODEL_VALUES 4
------ AFTER PHASE UPDATE_MODEL_VALUES 4
------ BEFORE PHASE INVOKE_APPLICATION 5
ContactController.addNew()
------ AFTER PHASE INVOKE_APPLICATION 5
------ BEFORE PHASE RENDER_RESPONSE 6
Contact.getFirstName()
ContactController.getGroups()
ContactController.getGroups()
ContactController.getAvailableTags()
ContactController.getContacts()
------ AFTER PHASE RENDER_RESPONSE 6
REQUEST END

Phương thức addNew() gây ra sự hoàn trả biểu mẫu. Bởi vì addNew() là một khung nhìn hiện lại (postback), JSF trải qua tất cả các pha. Bởi vì biểu mẫu đã không được hoàn trả trước khi phương thức addNew() được gọi, các trường của nó không được xử lý trước khi phương thức addNew() được gọi.

Tiếp theo, nhập một mã ZIP sai (vi dụ như aaa) và một số điện thoại sai (ví dụ như aaa). Sau đó chọn hai thẻ và một nhóm, và nhấn nút Add . Bạn sẽ thấy các kết quả hiển thị trong Listing 46:

Listing 46. Kết quả sau khi nhập vào một mã zip sai, số điện thoại sai, một nhóm, và một số thẻ
------ BEFORE PHASE RESTORE_VIEW 1
------ AFTER PHASE RESTORE_VIEW 1
------ BEFORE PHASE APPLY_REQUEST_VALUES 2
------ AFTER PHASE APPLY_REQUEST_VALUES 2
------ BEFORE PHASE PROCESS_VALIDATIONS 3
GroupConverter.getAsObject
ContactController.getAvailableGroups()
ZipCodeValidator.validate()
ContactValidators.validatePhone()
TagConverter.getAsObject
TagConverter.getAsObject
ContactController.getAvailableTags()
ContactController.getAvailableTags()
------ AFTER PHASE PROCESS_VALIDATIONS 3
------ BEFORE PHASE RENDER_RESPONSE 6
ContactController.getAvailableGroups()
GroupConverter.getAsString
GroupConverter.getAsString
GroupConverter.getAsString
ContactController.getAvailableTags()
TagConverter.getAsString
TagConverter.getAsString
TagConverter.getAsString
TagConverter.getAsString
ContactController.getContacts()
------ AFTER PHASE RENDER_RESPONSE 6
REQUEST END

Trong pha xử lý duyệt tính hợp lệ, phương thức GroupConverter.getAsObject() được gọi cho nhóm đã được chọn. TagConverter.getAsObject()được gọi hai lần, một lần cho mỗi thẻ đã được chọn. Cả hai trình duyệt tính hợp lệ tuỳ biến được bạn viết — ZipCodeValidator.validate()ContactValidators.validatePhone() — được gọi trong pha xử lý duyệt tính hợp lệ. Trong pha hoàn trả đáp ứng phương thức getAsString() của các trình biến đổi được gọi cho mỗi đối tượng trong danh sách các nhóm đang có và các thẻ đang có.

Bây giờ bạn có thể tưởng tượng nếu ContactController.getAvailableGroups()ContactController.getAvailableTags() truy cập vào cơ sở dữ liệu mỗi lần thì bạn sẽ truy cập vào cơ sở dữ liệu bốn lần để có được các danh sách này. Bạn sẽ muốn thêm một số logic để bạn sẽ truy cập cơ sở dữ liệu chỉ một lần cho mỗi yêu cầu. Bạn có thể, ví dụ, nạp dữ liệu từ hành động chỉ khi nó chưa được nạp.

Một cuộc thảo luận về các pha JSF sẽ chưa hoàn tất nếu không cho thấy kết quả của việc đệ trình một biểu mẫu hợp lệ, như hiển thị trong Listing 47:

Listing 47. Tất cả các con đường đã thông
------ BEFORE PHASE RESTORE_VIEW 1
------ AFTER PHASE RESTORE_VIEW 1
------ BEFORE PHASE APPLY_REQUEST_VALUES 2
------ AFTER PHASE APPLY_REQUEST_VALUES 2
------ BEFORE PHASE PROCESS_VALIDATIONS 3
GroupConverter.getAsObject
ContactController.getAvailableGroups()
ZipCodeValidator.validate()
ContactValidators.validatePhone()
ContactValidators.validatePhone()
ContactValidators.validatePhone()
TagConverter.getAsObject
TagConverter.getAsObject
ContactController.getAvailableTags()
ContactController.getAvailableTags()
------ AFTER PHASE PROCESS_VALIDATIONS 3
------ BEFORE PHASE UPDATE_MODEL_VALUES 4
Contact.setFirstName()
------ AFTER PHASE UPDATE_MODEL_VALUES 4
------ BEFORE PHASE INVOKE_APPLICATION 5
ContactController.persist()
------ AFTER PHASE INVOKE_APPLICATION 5
------ BEFORE PHASE RENDER_RESPONSE 6
ContactController.getContacts()
ContactController.getContacts()
ContactController.getContacts()
ContactController.getContacts()
ContactController.getContacts()
ContactController.getContacts()
Contact.getFirstName()
ContactController.getContacts()
ContactController.getContacts()
------ AFTER PHASE RENDER_RESPONSE 6
REQUEST END

Listing 47 cho thấy những gì đang diễn ra qua tất cả các pha của vòng đời một yêu cầu. Lưu ý rằng bạn bước vào pha INVOKE_APPLICATION và phương thức persist() được gọi: ContactController.persist().

Thêm một khung công tác duyệt tính hợp lệ ở mức đối tượng

Bạn có thể sử dụng một PhaseListener để bắt giữ các sự kiện nghe pha và thay đổi cách JSF xử lý các yêu cầu. Giả sử bạn muốn thêm vào JSF một khung công tác duyệt tính hợp lệ ở mức-đối tượng riêng của bạn. Bạn có thể sử dụng một giao diện giống như giao diện được hiển thị trong Listing 48:

Listing 48. Giao diện trình duyệt tính hợp lệ (Validator)
package com.arcmind.contact.model;

public interface Validateable {
   public void validate() throws ValidationException;
}

Sau đó, bạn thay đổi đối tượng Contact của mình để thực hiện giao diện này, như hiển thị trong Listing 49:

Listing 49. Thực hiện giao diện trình duyệt tính hợp lệ của Contact
package com.arcmind.contact.model;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@SuppressWarnings("serial")
public class Contact implements Serializable, Validateable {
    ...
    public void validate() throws ValidationException {
        if (
             homePhoneNumber == null  &&
             workPhoneNumber == null  &&
             mobilePhoneNumber == null             
           ) {
            throw new ValidationException("At least one phone number " +
            "must be set", "");
            
        }
    }
...

Bạn có thể tạo ra một PhaseListener để tìm kiếm các đối tượng có khả năng duyệt tính hợp lệ (Validateable) và duyệt tính hợp lệ của chúng. PhaseListener có thể nhận các thông báo ngoại lệ và biến đổi chúng thành các FacesMessage, nhưng làm thế nào để bạn biết khi nào đối tượng cần phải được duyệt tính hợp lệ? Có vẻ như là kiến thức đó sẽ chỉ có trong lớp của trình điều khiển. Vì vậy, để xử lý điều này theo một cách tổng quát, bạn có thể tạo ra một trình điều khiển lớp mẹ (superclass) để tạo một lớp con PhaseListener không tên bên trong mà bạn có thể liên kết tới nó, như hiển thị trong Listing 50:

Listing 50. Lớp cơ sở với PhaseListener
package com.arcmind.contact.controller;

import java.io.Serializable;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import com.arcmind.contact.model.Validateable;
import com.arcmind.contact.model.ValidationException;

@SuppressWarnings("serial")
public abstract class AbstractCrudController implements Serializable {

   private boolean edit;

   public PhaseListener phaseListener = new PhaseListener() {
      
      public void afterPhase(PhaseEvent event) {
         validate();
      }

      public void beforePhase(PhaseEvent event) {

      }

      public PhaseId getPhaseId() {
         return PhaseId.UPDATE_MODEL_VALUES;
      }
      
   };
   
   abstract Object getFormObject(); //subclass defines this

   private void validate() {
      Object form = getFormObject();
      if (! (form instanceof Validateable) || form == null) {
         return;
      }
      Validateable validateable = (Validateable) form;
      try {
         validateable.validate(); //validate object
      } catch (ValidationException validationException) {

         FacesContext.getCurrentInstance().renderResponse(); //Do not invoke application.
         addErrorMessage(validationException.getMessage());

      }
   }

   public PhaseListener getPhaseListener() {
      return phaseListener;
   }


    protected void addErrorMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                FacesMessage.SEVERITY_ERROR, message, null));
    }

    public boolean isEdit() {
        return edit;
    }

    public void setEdit(boolean edit) {
        this.edit = edit;
    }

}

Lưu ý rằng PhaseListener chỉ lắng nghe UPDATE_MODEL_VALUES. Sau đó nó gọi phương thức validate() để kiểm tra xem đối tượng biểu mẫu có mặt không và có là một cá thể của Validator trước khi nó gọi validate() trên đối tượng miền. Sau đó nó gọi validate() và cho phép mô hình thực hiện việc duyệt tính hợp lệ. Cũng lưu ý rằng, nếu việc duyệt tính hợp lệ của mô hình thất bại, phương thức validate() của AbstractCrudCOntroller gọi FacesContext.getCurrentInstance().renderResponse(), để buộc JSF bỏ qua pha INVOKE_APPLICATION.

Bây giờ bạn thay đổi ContactController thành lớp con của AbstractCrudController, như hiển thị trong Listing 51:

Listing 51. ContactController mở rộng AbstractCrudController để cung cấp hỗ trợ duyệt tính hợp lệ của PhaseListener
public class ContactController extends AbstractCrudController{
   ...
   public void addNew() {
       ... same as before except sets the edit mode.
      super.setEdit(true);
   }
   public void persist() {
      ... same as before except sets the edit mode.
      super.setEdit(false);
   }

   public void read() {
      ... same as before except sets the edit mode.
      super.setEdit(true);
   }

    @Override
    Object getFormObject() {
        if (super.isEdit()) {
            return this.contact;
        } else {
            return null;
        }
    }

}

Lưu ý rằng Listing 51 thực hiện phương thức trừu tượng getFormObject của lớp AbstractCrudController. Nó sử dụng chế độ chỉnh sửa sao cho nó trả về đối tượng mối giao tiếp chỉ khi trình điều khiển ở trong chế độ chỉnh sửa. Bằng cách này, trình điều khiển quyết định khi nào đối tượng cần được duyệt tính hợp lệ.

Cuối cùng, bạn cần phải đăng ký trình nghe pha của bạn, như hiển thị trong Listing 52:

Listing 52. . Liên kết vào thuộc tính phaseListener
<f:view>
   <h3>Contacts (4th version)</h3>
   <f:phaseListener binding="#{contactController.phaseListener}" />

Với trình nghe pha, bạn có thể thay đổi cách JSF xử lý yêu cầu, do đó mở rộng khung công tác của JSF cho phù hợp với các nhu cầu của bạn.


Kết luận

Hướng dẫn này đã trình bày về vòng đời của quá trình xử lý yêu cầu của JSF và chứng tỏ một số các đặc tính cốt yếu của mô hình thành phần JSF. Nó đã trình bày khá nhiều về biến đổi và duyệt tính hợp lệ của JSF. Thực vậy, nó đã bao gồm hầu hết những gì bạn cần biết về các quá trình xử lý này để làm cho chúng hoạt động trong các ứng dụng của bạn (ít nhất là với phiên bản này của JSF)! Tất nhiên, hướng dẫn này không thể bao gồm tất cả mọi thứ. Ví dụ, bạn có thể muốn xem qua Tomahawk, Crank, Shale, và jsf-comp để biết về các thành phần duyệt tính hợp lệ không được cung cấp trong JSF hoặc được thảo luận ở đó (xem Tài nguyên). Và mặc dù hướng dẫn này đã thảo luận các kỹ thuật biến đổi và duyệt tính hợp lệ phổ biến nhất, vẫn còn có các kỹ thuật khác không được đưa ra trong hướng dẫn này.

Một điều cần ghi nhớ là việc biến đổi và duyệt tính hợp lệ không nhất thiết làm việc tốt với nhau. Phép biến đổi sẽ biến đổi các chuỗi ký tự thành các đối tượng, trong khi hầu hết các trình duyệt tính hợp lệ tiêu chuẩn làm việc trên các chuỗi ký tự. Vì vậy, bạn cần phải thận trọng khi sử dụng các trình biến đổi và các trình duyệt tính hợp lệ tùy biến cùng với nhau. Ví dụ, đối tượng PhoneNumber của hướng dẫn này sẽ không làm việc với một trình duyệt tính hợp lệ theo chiều dài. Trong trường hợp này, hoặc bạn sẽ phải viết một trình duyệt tính hợp lệ theo yêu cầu, hoặc đơn giản là bao gồm bất kỳ logic duyệt tính hợp lệ đặc biệt nào vào trong trình biến đổi tùy biến. Tôi thích lựa chọn sau bởi vì nó cho phép bạn chỉ đơn giản kết hợp một trình biến đổi tùy biến (có logic duyệt tính hợp lệ làm sẵn) với một kiểu đối tượng cụ thể và yêu cầu JSF xử lý kiểu đối tượng đó. JSF thực hiện điều này cho bạn một cách tự động mà không bắt bạn chỉ rõ các mã nhận dạng id của trình biến đổi nào trong JSP. (Tất nhiên, một số người có thể gọi đây là lập trình lười nhác, và nó không nhất thiết là giải pháp tốt nhất cho tất cả các trường hợp sử dụng).

JSF cung cấp một khung công tác linh hoạt, mạnh mẽ, và có khả năng cắm được cho việc phát triển ứng dụng web. Ngoài các trình biến đổi và trình duyệt tính hợp lệ tiêu chuẩn, JSF tạo thuận tiện cho việc triển khai thực hiện tùy biến phù hợp như nhau với cả nhà phát triển ứng dụng và cả nhà phát triển khung công tác. Cuối cùng, chiến lược biến đổi và duyệt tính hợp lệ mà bạn chọn là tùy thuộc vào bạn. JSF cho phép bạn dễ dàng và nhanh chóng bắt đầu (các trình biến đổi, các trình duyệt tính hợp lệ tiêu chuẩn, duyệt tính hợp lệ nội tuyến) trong khi làm bản mẫu đầu tiên, và dịch chuyển đến các giải pháp sản xuất tinh vi hơn (các đối tượng tùy biến, các thông báo tùy biến) trong các pha phát triển sau này. Và xuyên suốt tất cả, vòng đời của ứng dụng JSF cung cấp một cơ sở hạ tầng tin cậy cho việc bảo đảm trước sau như một tính toàn vẹn của dữ liệu-mô hình.


Tải về

Mô tảTênKích thước
Mã ví dụ của bài học nàyjsf12-Part2-sample_code.zip239KB

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Khi bạn đăng ký với trang developerWorks lần đầu tiên, một tiểu sử của của bạn được tạo ra. Chọn các thông tin về tiểu sử của bạn (tên, nước/vùng, và nơi làm việc) đã được hiện lên màn hình, thông tin này sẽ được hiện kèm với nội dung mà bạn đăng tải. Bạn có thể cập nhật thông tin này bất kỳ lúc nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Công nghệ Java, SOA và dịch vụ Web
ArticleID=383402
ArticleTitle=Khởi đầu với JavaServer Faces 1.2, Phần 2: Vòng đời, phép chuyển đổi, duyệt tính hợp lệ, và trình nghe pha của JSF
publish-date=05202009