在Spring Boot上创建JSF应用程序
在我们开发一个查询和新增“产品”的简单应用程序时,我们将首先创建Product实体。 对于初学者,请在com.auth0.samples.bootfaces包中创建Product.java文件。 该实体将具有以下代码:
package com.auth0.samples.bootfaces;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.math.BigDecimal;
@Data
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String name;
@Column
private BigDecimal price;
protected Product() {
}
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
}
这是一个非常简单的Product实体,只有三个属性:
- id, 实体的主键
- name,产品的名称
- price,产品的价格
您可能已经注意到IDE报错,无法识别@Data annotation。 这个注解来自lombok库,我们需要将它导入到我们的应用程序中。 Project Lombok旨在减少在Java应用程序的许多部分(如getter和setter)中重复的样板代码。 在上面的实体中,我们使用@Data来承担为实体属性定义许多访问器方法的负担。 Lombok还有许多其他功能,请查看其文档。
若要导入它,请将以下元素作为dependecies 的子元素添加到pom.xml文件中:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
现在,我们将创建Spring Boot使用的application.properties文件,配置HSQLDB连接String,以及Spring Data以禁用Hibernate的自动创建功能。
请注意,Hibernate是Spring Data的传递依赖项,默认情况下它会读取使用Entity注解的类并尝试为它们创建表。 如前所述,在我们的应用程序中,我们将使用Flyway。 我们需要在src / main / webapp /文件夹中创建application.properties文件,其中包含以下内容:
spring.datasource.url=jdbc:hsqldb:file:data/products
spring.jpa.hibernate.ddl-auto=none
第一个属性将HSQLDB配置为将数据持久保存到应用程序根目录的数据文件夹中,第二个属性是禁用Hibernate自动创建功能的属性。 由于我们已禁用此功能,因此我们现在需要添加一个Flyway脚本来创建产品表。 我们通过在src / main / resources / db / migration /文件夹中创建一个名为V1__products.sql的文件来实现。 该文件将包含以下脚本:
create table product (
id identity not null,
name varchar (255) not null,
price double not null
);
现在已经定义了Product实体类和一个在HSQLDB上持久化它的表,我们可以扩展JpaRepositorySpringBoot接口,以提供一个托管bean来与数据库通信。为此,我们在com.auth0.samples.bootFaces包中创建一个名为ProductRepository的接口,其内容如下:
package com.auth0.samples.bootfaces;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
JpaRepository接口附带了一些预定义的方法,允许开发人员findAll (查询所有实体实例),getOne (根据id获取单个实体),delete (删除实体),save (保存新实体)等。
接下来,我们需要准备好前端处理代码了。 为了使用户能够通过我们的应用程序创建“产品”,我们需要:
1.一个JSF应用程序基本布局的模板。
2.一个JSF接口(xhtml文件),包含用于创建新产品的表单。
3.一个Spring控制器,作为表单接口的辅助bean。
构建JSF接口来创建“产品”
首先,让我们创建应用程序的模板。 这个模板非常简单。 首先,在src / main / webapp /文件夹中创建一个名为layout.xhtml的文件,然后将以下代码添加到其中:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"><f:view>
<h:head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Product</title>
</h:head>
<h:body>
<div class="ui-g">
<div class="ui-g-12">
<p:toolbar>
<f:facet name="left">
<p:button href="/" value="List of Products" />
<p:button href="/product" value="New Product" />
</f:facet>
</p:toolbar>
</div>
<div class="ui-g-12">
<ui:insert name="content" />
</div>
</div>
</h:body>
</f:view>
</html>
在JSF上定义视图几乎就像定义常规HTML文件一样,但是有一些不同的元素,例如来自JSF和相关框架(如PrimeFaces)上定义的名称空间。 代码中关于布局的最重要的元素是p:toolbar和ui:insert。 第一个是PrimeFaces提供的组件,我们使用它在我们的模板中定义导航菜单。 此菜单将允许用户切换到新增“产品”的维护视图,或者显示“产品”列表的查询视图。
第二个元素ui:insert定义了允许子视图定义其内容的模板的确切位置。 模板可以有多个ui:insert元素,使用不同的名称进行区别即可。
注意:JSF使用一种名为Facelets的技术来定义模板。可以在Oracle网站的JavaEE 7教程中阅读有关它的所有内容(https://docs.oracle.com/javaee/7/tutorial/jsf-facelets001.htm)。
定义模板之后,我们创建对应的Spring控制器。在com.auth0.samples.bootFaces包中创建一个名为ProductController的类,并添加以下代码:
package com.auth0.samples.bootfaces;
import org.ocpsoft.rewrite.annotation.Join;
import org.ocpsoft.rewrite.el.ELBeanName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope(value = "session")
@Component(value = "productController")
@ELBeanName(value = "productController")
@Join(path = "/product", to = "/product-form.jsf")
public class ProductController {
@Autowired
private ProductRepository productRepository;
private Product product = new Product();
public String save() {
productRepository.save(product);
product = new Product();
return "/product-list.xhtml?faces-redirect=true";
}
public Product getProduct() {
return product;
}
}
这个类只有两个方法:
- save方法,用于触发JSF提交按钮,以此保存一个新“产品”;
- getProduct方法,接口使用它将表单上的输入绑定到Product的实例。 此实例与ProductController实例同时创建,并在用户保存新产品后立即创建新实例。
需要注意的是,save方法重定向到product-list.xhtml,该列表列出了我们数据库中已经存在的“产品”。
更重要的是,这个类有四个注解:
- @ Scope是一个Spring注解,它定义每个用户将存在此类的单个实例。
- @ Component将此类定义为Spring组件,并将其命名为将在表单接口中使用的productController-name。
- @ ELBeanName是Rewrite提供的注解,用于在其作用域上配置bean的名称。
- @ Join置-由Rewrite提供的另一个注解 - 配/ productURL以响应product-form.xhtml的内容。
最后,让我们创建使用上述控制器的表单。 我们将在src / main / webapp /文件夹中创建一个名为product-form.xhtml的文件,其中包含以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:p="http://primefaces.org/ui"><ui:composition template="layout.xhtml">
<ui:define name="content">
<h:form id="productForm">
<p:panel header="Product Details">
<h:panelGrid columns="1">
<p:outputLabel for="name" value="Name: " />
<p:inputText id="name" value="#{productController.product.name}" />
<p:outputLabel for="price" value="Price: " />
<p:inputNumber id="price" value="#{productController.product.price}" />
<h:commandButton value="Save" action="#{productController.save}" />
</h:panelGrid>
</p:panel>
</h:form>
</ui:define>
</ui:composition>
</html>
此文件使用ui:composition元素显式定义layout.xhtml作为此视图的模板,并使用ui:define来通知该视图必须在模板的内容区域中呈现。 然后定义表单来创建新“产品”。 此表单由一个p:inputText和一个p:inputNumber元素组成,用户可以定义产品的名称和产品的价格。 最后一个元素专门用于处理数字属性,不允许用户输入非数字型字符。
最后,定义了一个h:commandButton,即视图(页面)上的一个按钮,该按钮可以触发ProductController组件的save方法。 在这个视图中,可以看到我们将产品对象和定义在ProductController组件中的行为通过ProductController名称关联在一起,该名称通过该组件的@Component和@ELBeanName注解进行定义。
如果现在通过IDE或通过mvn spring-boot:run命令运行应用程序,我们将能够在浏览器中访问http:// localhost:8080 / product。通过该页面,我们可以创建新“产品”,但我们无法列出刚创建的“产品”——接下来,我们来解决这个问题。
为“产品列表”构建JSF接口
为了使我们的用户能够看到已创建产品的列表,我们将首先定义一个将处理接口背后逻辑的辅助bean。 这个支持bean将被称为ProductListController,我们将使用以下代码在com.auth0.samples.bootfaces包中创建它:
package com.auth0.samples.bootfaces;
import org.ocpsoft.rewrite.annotation.Join;
import org.ocpsoft.rewrite.annotation.RequestAction;
import org.ocpsoft.rewrite.el.ELBeanName;
import org.ocpsoft.rewrite.faces.annotation.Deferred;
import org.ocpsoft.rewrite.faces.annotation.IgnorePostback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.List;
@Scope (value = "session")
@Component (value = "productList")
@ELBeanName(value = "productList")
@Join(path = "/", to = "/product-list.jsf")
public class ProductListController {
@Autowired
private ProductRepository productRepository;
private List<Product> products;
@Deferred
@RequestAction
@IgnorePostback
public void loadData() {
products = productRepository.findAll();
}
public List<Product> getProducts() {
return products;
}
}
与ProductController类似,此类有四个注解:
- @ Scope(value =“session”)定义每个用户只有一个此类的实例。
- @ Component将此类定义为Spring组件,并将其命名为productList。
- @ ELBeanName在重写范围上配置Bean的名称。
- @ Join配置/ URL将使用/product-list.jsf进行响应。
请注意,此控制器有一个名为loadData的方法,该方法使用@ Deferred,@ RequestAction和@IgnorePostback进行注解。 在渲染界面之前,需要使用这些注解来加载“产品”集合。 我们也可以在getProducts中加载这个集合,但这会使渲染过程变慢,因为这个方法在JSF 生命周期(lifecycle)中会被调用很多次。
最后,我们将创建对应的“产品”列表界面——文件名叫product-list.xhtml,在src / main / webapp /文件夹中,代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui">
<ui:composition template="layout.xhtml">
<ui:define name="content">
<h:form id="form">
<p:panel header="Products List">
<p:dataTable id="table" var="product" value="#{productList.products}">
<p:column>
<f:facet name="header"># Id</f:facet>
<h:outputText value="#{product.id}" />
</p:column>
<p:column>
<f:facet name="header">Name</f:facet>
<h:outputText value="#{product.name}" />
</p:column>
<p:column>
<f:facet name="header">Price</f:facet>
<h:outputText value="#{product.price}">
<f:convertNumber type="currency" currencySymbol="$ " />
</h:outputText>
</p:column>
</p:dataTable>
</p:panel>
</h:form>
</ui:define>
</ui:composition>
</html>
我们借助PrimeFaces提供的p:dataTable组件呈现“产品”集合。 该组件通过value属性从支持bean(本例中为ProductListController)接收对象集合,并迭代它创建HTML表的行。 该表的列由p:column元素定义,也由PrimeFaces提供。 请注意,在此界面中,我们使用了一个名为f:convertNumber的元素来正确格式化“产品”的价格。
运行应用程序,并在浏览器中输入http:// localhost:8080 ,将显示以下内容:
译者注:
由于是基于Maven构建,所以使用install构建target目录中的内容。
构建内容如下:
(未完待续)
用SpringBoot开发JSF应用程序系列文章: