JSF的简单CRUD示例
这是一个使用 JSF 的简单 CRUD 示例。
引言
这是一个使用 JSF 的简单 CRUD 示例。
背景
JSF 是一个 MVC 框架,但它与 Spring MVC 和 ASP.NET MVC 有很大不同。 它实际上具有很强的 ASP.NET Web Form " 回发 (POSTBACK)" 风格。 此示例基于一个 Stack Overflow 示例。
附件是一个 Maven 项目。 我已使用 Maven 3.2.1、Java 1.8.0_45、Tomcat 7 和 Eclipse Java EE IDE for Web Developers Luna Service Release 2 测试过它。 它实际上有两个示例。
- “simplecrud.xhtml”是简单的 CRUD 示例;
- “freshsafecrud.xhtml”用于解决由于 JSF 的“回发 (POSTBACK)”特性导致的问题。
“welcome.xhtml”是这两个示例的索引页面。
如果您不熟悉如何将 Maven 项目导入 Eclipse,您可以查看 此链接。 当我尝试导入项目时,我注意到 Eclipse 向我显示了一些错误消息,告诉我一些验证错误。 您可以简单地忽略这些消息并从 Eclipse 中删除标记。
pom.xml 和 web.xml
“pom.xml”声明了创建 JSF 应用程序所需的依赖项。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.song.example</groupId>
<artifactId>JSF-Example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<tomcat.version>7.0.55</tomcat.version>
<jsf.version>2.1.7</jsf.version>
</properties>
<dependencies>
<!-- Sevlet jars for compilation, provided by Tomcat -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>${jsf.version}</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>${jsf.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>true</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
该示例应用程序的“web.xml”如下所示。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>JSF Example</display-name> <welcome-file-list> <welcome-file>welcome.xhtml</welcome-file> </welcome-file-list> <filter> <filter-name>nocachefilter</filter-name> <filter-class> com.song.web.filter.NocacheFilter </filter-class> </filter> <filter-mapping> <filter-name>nocachefilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>jsf-servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jsf-servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <session-config> <session-timeout>20</session-timeout> <tracking-mode>COOKIE</tracking-mode> </session-config> <context-param> <param-name>BaseUrl</param-name> <param-value> https://:8080/JSF-Example/ </param-value> </context-param> </web-app>
- 从 servlet 容器的角度来看,所有 JSF 页面都将被映射到由“javax.faces.webapp.FacesServlet”类实现的单个 servlet;
- 在此示例应用程序中,我们将所有对“xhtml”页面的请求映射到“javax.faces.webapp.FacesServlet”。
简单的 CRUD 示例
简单示例的 托管 bean 在“SimpleCrudBean”类中实现。
package com.song.jsf.example;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class SimpleCrudBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<Student> list;
private Student item = new Student();
private Student beforeEditItem = null;
private boolean edit;
@PostConstruct
public void init() {
list = new ArrayList<Student>();
}
public void add() {
// DAO save the add
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Student();
}
public void resetAdd() {
item = new Student();
}
public void edit(Student item) {
beforeEditItem = item.clone();
this.item = item;
edit = true;
}
public void cancelEdit() {
this.item.restore(beforeEditItem);
this.item = new Student();
edit = false;
}
public void saveEdit() {
// DAO save the edit
this.item = new Student();
edit = false;
}
public void delete(Student item) throws IOException {
// DAO save the delete
list.remove(item);
}
public List<Student> getList() {
return list;
}
public Student getItem() {
return this.item;
}
public boolean isEdit() {
return this.edit;
}
}
- “add”、“saveEdit”和“delete”方法需要将数据与持久存储(如数据库)同步;
- 在此示例中,为了简单起见,跳过了数据库操作。
“Student”类在“Student.java”文件中实现。
package com.song.jsf.example;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
public Student() {}
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public Student clone() {
return new Student(id, name);
}
public void restore(Student student) {
this.id = student.getId();
this.name = student.getName();
}
}
“SimpleCrudBean”类绑定到“simplecrud.xhtml”文件,使其完全可用。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> <head> <title>Simple CRUD</title> </head> <body> <h3>List students</h3> <h:form rendered="#{not empty simpleCrudBean.list}"> <h:dataTable value="#{simpleCrudBean.list}" var="item"> <h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column> <h:column><f:facet name="header">Name</f:facet>#{item.name}</h:column> <h:column> <h:commandButton value="edit" action="#{simpleCrudBean.edit(item)}" /> </h:column> <h:column> <h:commandButton value="delete" action="#{simpleCrudBean.delete(item)}" /> </h:column> </h:dataTable> </h:form> <h:panelGroup rendered="#{empty simpleCrudBean.list}"> <p>No students! Please add students.</p> </h:panelGroup> <h:panelGroup rendered="#{!simpleCrudBean.edit}"> <h3>Add student</h3> <h:form> <p>Name: <h:inputText value="#{simpleCrudBean.item.name}" /></p> <p> <h:commandButton value="add" action="#{simpleCrudBean.add}" /> <h:commandButton value="reset" action="#{simpleCrudBean.resetAdd}" /> </p> </h:form> </h:panelGroup> <h:panelGroup rendered="#{simpleCrudBean.edit}"> <h3>Edit student #{simpleCrudBean.item.id}</h3> <h:form> <p>Name: <h:inputText value="#{simpleCrudBean.item.name}" /></p> <p> <h:commandButton value="save" action="#{simpleCrudBean.saveEdit}" /> <h:commandButton value="cancel" action="#{simpleCrudBean.cancelEdit}" /> </p> </h:form> </h:panelGroup> <p> <a href="#{appUrlStore.baseUrl}">Go back to index</a> </p> </body> </html>
如果您现在加载“simplecrud.xhtml”页面,您会发现 CRUD 操作都运行良好。 但是,如果您在添加学生后单击浏览器上的刷新按钮,您将看到这个难看的弹出消息。
如果您单击“继续”按钮并继续刷新,您会注意到刚添加的学生又被添加了。 这绝对不是一个好的行为。 这种行为是由于 JSF 的“回发 (POSTBACK)”特性造成的。 在下一个示例中,我们将尝试解决这个问题。
刷新安全的 CRUD 示例
为了解决简单 CRUD 示例中的问题,我创建了“CommonUtils”类。
package com.song.jsf.example.util; import java.io.IOException; import java.io.Serializable; import javax.faces.bean.ApplicationScoped; import javax.faces.bean.ManagedBean; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletRequest; @ManagedBean(name="commonUtils") @ApplicationScoped public class CommonUtils implements Serializable { private static final long serialVersionUID = 1L; public void redirectWithGet() { FacesContext facesContext = FacesContext.getCurrentInstance(); ExternalContext externalContext = facesContext.getExternalContext(); HttpServletRequest request = (HttpServletRequest)externalContext.getRequest(); StringBuffer requestURL = request.getRequestURL(); String queryString = request.getQueryString(); if (queryString != null) { requestURL.append('?').append(queryString).toString(); } String url = requestURL.toString(); try { externalContext.redirect(requestURL.toString()); } catch (IOException e) { throw new RuntimeException("Unable to rerirect to " + url); } facesContext.responseComplete(); } }
“redirectWithGet”方法用于简单地向浏览器发送重定向请求,以使用 GET 请求刷新浏览器。“CommonUtils”对象被注入到“FreshsafeCrudBean”类中,并且每当执行“回发 (POSTBACK)”时都会调用“redirectWithGet”方法。
package com.song.jsf.example; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; import javax.faces.bean.ManagedProperty; import javax.faces.bean.SessionScoped; import com.song.jsf.example.util.CommonUtils; @ManagedBean @SessionScoped public class FreshsafeCrudBean implements Serializable { private static final long serialVersionUID = 1L; private List<Student> list; private Student item = new Student(); private Student beforeEditItem = null; private boolean edit; @ManagedProperty(value="#{commonUtils}") private CommonUtils util; public void setUtil(CommonUtils util) { this.util = util; } @PostConstruct public void init() { list = new ArrayList<Student>(); } public void add() { // DAO save the add item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1); list.add(item); item = new Student(); util.redirectWithGet(); } public void resetAdd() { item = new Student(); util.redirectWithGet(); } public void edit(Student item) { beforeEditItem = item.clone(); this.item = item; edit = true; util.redirectWithGet(); } public void cancelEdit() { this.item.restore(beforeEditItem); this.item = new Student(); edit = false; util.redirectWithGet(); } public void saveEdit() { // DAO save the edit this.item = new Student(); edit = false; util.redirectWithGet(); } public void delete(Student item) throws IOException { // DAO save the delete list.remove(item); util.redirectWithGet(); } public List<Student> getList() { return list; } public Student getItem() { return this.item; } public boolean isEdit() { return this.edit; } }
“freshsafecrud.xhtml”与“simplecrud.xhtml”文件完全相同,只是它绑定到“FreshsafeCrudBean”类。
如果您现在加载“simplecrud.xhtml”,您应该可以随意刷新页面,而不会看到“回发 (POSTBACK)”的副作用。 当然,代价是往返 Web 服务器一次。
关注点
- 这是一个使用 JSF 的简单 CRUD 示例;
- 真正的 CRUD 操作通常涉及数据库操作,但此示例为了简单起见省略了这些操作;
- JSF 具有很强的 ASP.NET Web Form 风格,它严重依赖“回发 (POSTBACK)”来实现其功能;
- 还提供了一个额外的示例,以解决“回发 (POSTBACK)”的副作用,代价是额外的服务器往返。
历史
第一次修订 - 2015 年 9 月 16 日