知識庫 / Spring RSS 訂閱

Spring Boot 和 JSF 中的控制器、服務和 DAO 示例

Jakarta EE,Spring
HongKong
3
01:39 PM · Dec 06 ,2025

1. 引言

JavaServer Faces 是一種服務器端、基於組件的用户界面框架。它最初是作為 Jakarta EE 的一部分開發的。

在本教程中,我們將學習如何將 JSF 集成到 Spring Boot 應用程序中。作為示例,我們將實現一個簡單的應用程序,創建一個待辦事項列表。

2. Maven 依賴

為了使用 JSF 技術,我們需要擴展我們的 <em pom.xml</em>

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--JSF-->
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.faces</artifactId>
    <version>4.1.0-M1</version>
</dependency>

jakarta.faces 構件包含 JSF API 及其實現。 詳細信息請參考 這裏

3. 配置 JSF Servlet

JSF 框架使用 XHTML 文件來描述用户界面的內容和結構。服務器端會根據 XHTML 描述生成 JSF 文件。

讓我們從在 <em src="main/webapp</em> 目錄下的 <em index.xhtml</em> 文件中創建靜態結構開始:

<f:view xmlns="http://www.w3c.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <title>TO-DO application</title>
    </h:head>
    <h:body>
        <div>
            <p>Welcome in the TO-DO application!</p>
            <p style="height:50px">
                This is a static message rendered from xhtml.
            </p>
        </div>
    </h:body>
</f:view>

內容將在 <em>your-url</em>/index.jsf 處提供。請注意,我們嘗試在此時訪問內容時,客户端會收到錯誤提示。

There was an unexpected error (type=Not Found, status=404).
No message available

不會出現任何後端錯誤消息。即便如此,我們仍然可以確定,我們需要一個 JSF servlet 來處理請求,以及 servlet 映射來匹配請求與處理器。

由於我們使用的是 Spring Boot,我們可以輕鬆地擴展我們的應用程序類來處理所需的配置:

@SpringBootApplication
public class JsfApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(JsfApplication.class, args);
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        FacesServlet servlet = new FacesServlet();
        ServletRegistrationBean servletRegistrationBean = 
          new ServletRegistrationBean(servlet, "*.jsf");
        return servletRegistrationBean;
    }
}

這看起來不錯,而且相當合理,但不幸的是,它仍然不夠好。當我們現在嘗試打開 <your-url>/index.jsf 時,我們會收到另一個錯誤:

java.lang.IllegalStateException: Could not find backup for factory jakarta.faces.context.FacesContextFactory.

不幸的是,我們需要除了Java配置之外的web.xml 讓我們在 src/webapp/WEB-INF 中創建它:

<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
</servlet-mapping>

現在我們的配置已就緒,可以打開 <em>/index.jsf:

Welcome in the TO-DO application!

This is a static message rendered from xhtml.

在創建用户界面之前,我們首先將創建應用程序的後端。

4. 實現 DAO 模式

DAO 代表數據訪問對象。通常,DAO 類負責兩個概念:封裝持久化層的細節以及為單個實體提供 CRUD 接口。 詳細描述請參考本教程。

為了實現 DAO 模式,我們首先將定義一個通用的接口:

public interface Dao<T> {

    Optional<T> get(int id);
    Collection<T> getAll();
    int save(T t);
    void update(T t);
    void delete(T t);
}

現在我們將創建這個待辦事項應用程序中的第一個,也是唯一的,領域類:

public class Todo {

    private int id;
    private String message;
    private int priority;

    // standard getters and setters

}

下一次課程將是實現 Dao<Todo>。這種模式的優點在於,我們可以隨時提供對該接口的新實現。

因此,我們可以無需修改其他代碼的情況下更改持久層。

對於我們的示例,我們將使用內存存儲類

@Component
public class TodoDao implements Dao<Todo> {

    private List<Todo> todoList = new ArrayList<>();
    
    @Override
    public Optional<Todo> get(int id) {
        return Optional.ofNullable(todoList.get(id));
    }

    @Override
    public Collection<Todo> getAll() {
        return todoList.stream()
          .filter(Objects::nonNull)
          .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
    }

    @Override
    public int save(Todo todo) {
        todoList.add(todo);
        int index = todoList.size() - 1;
        todo.setId(index);
        return index;
    }

    @Override
    public void update(Todo todo) {
        todoList.set(todo.getId(), todo);
    }

    @Override
    public void delete(Todo todo) {
        todoList.set(todo.getId(), null);
    }
}

5. 服務層

DAO 層的主要目標是處理持久化機制的細節,而服務層則在其之上處理業務需求。

請注意,服務層將引用 DAO 接口。

@Scope(value = "session")
@Component(value = "todoService")
public class TodoService {

    @Autowired
    private Dao<Todo> todoDao;
    private Todo todo = new Todo();

    public void save() {
        todoDao.save(todo);
        todo = new Todo();
    }

    public Collection<Todo> getAllTodo() {
        return todoDao.getAll();
    }

    public int saveTodo(Todo todo) {
        validate(todo);
        return todoDao.save(todo);
    }

    private void validate(Todo todo) {
        // Details omitted
    }

    public Todo getTodo() {
        return todo;
    }
}

在這裏,該服務是一個命名組件。我們將使用這個名稱從JSF上下文引用該Bean。

該類也具有會話範圍,這對於這個簡單的應用程序來説是合適的。

有關Spring範圍的更多信息,可以參考這個教程。 由於Spring的內置範圍與JSF的範圍模型不同,因此考慮定義一個自定義範圍是值得的。

關於此問題的更多指導可以在此教程中找到。

6. 控制器

就像在 JSP 應用程序中一樣,控制器將處理不同視圖之間的導航。

接下來,我們將實現一個極簡的控制器。它將從主頁導航到待辦事項列表頁面:

@Scope(value = "session")
@Component(value = "jsfController")
public class JsfController {

    public String loadTodoPage() {
        checkPermission();
        return "/todo.xhtml";
    }

    private void checkPermission() {
        // Details omitted
    }
}

導航基於返回的名稱。因此,loadTodoPage 將會帶我們到 todo.xhtml 頁面,接下來我們將對其進行實現。

7. 連接 JSF 和 Spring Beans

現在讓我們看看如何從 JSF 上下文中引用我們的組件。首先,我們將擴展 index.xthml

<f:view 
  xmlns="http://www.w3c.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
       // same code as before
    </h:head>
    <h:body>
        <div>
           // same code as before
           <h:form>
             <h:commandButton value="Load To-do page!" action="#{jsfController.loadTodoPage}" />
           </h:form>
        </div>
    </h:body>
</f:view>

在這裏,我們引入了位於表單元素內的 commandButton這很重要,因為每一個 UICommand 元素(例如 commandButton)都必須位於 UIForm 元素(例如 form)內。

此時,我們可以啓動我們的應用程序並檢查 <your-url>/index.jsf

不幸的是,當我們點擊按鈕時,我們會收到一個錯誤。

There was an unexpected error (type=Internal Server Error, status=500).
jakarta.el.PropertyNotFoundException:
/index.xhtml @11,104 action="#{jsfController.loadTodoPage}":
Target Unreachable, identifier [jsfController] resolved to null

消息明確指出問題:jsfController 解決為 null。 對應的組件要麼未創建,要麼在 JSF 上下文中不可見。

在這種情況下,後一種情況為真。

我們需要在 webapp/WEB-INF/faces-config.xml 中將 Spring 上下文與 JSF 上下文連接

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
  version="2.2">
    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
</faces-config>

既然我們的控制器已準備好工作,我們需要 todo.xhtml

8. 通過 JSF 與服務交互

我們的 todo.xhtml 頁面將具有兩個目的。首先,它將顯示所有待辦事項元素。

其次,它將提供添加新元素到列表的機會。

為此,UI 組件將直接與前面聲明的服務進行交互:

<f:view xmlns="http://www.w3c.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <title>TO-DO application</title>
    </h:head>
    <h:body>
        <div>
            <div>
                List of TO-DO items
            </div>
            <h:dataTable value="#{todoService.allTodo}" var="item">
                <h:column>
                    <f:facet name="header"> Message</f:facet>
                    #{item.message}
                </h:column>
                <h:column>
                    <f:facet name="header"> Priority</f:facet>
                    #{item.priority}
                </h:column>
            </h:dataTable>
        </div>
        <div>
            <div>
                Add new to-do item:
            </div>
            <h:form>
                <h:outputLabel for="message" value="Message: "/>
                <h:inputText id="message" value="#{todoService.todo.message}"/>
                <h:outputLabel for="priority" value="Priority: "/>
                <h:inputText id="priority" value="#{todoService.todo.priority}" converterMessage="Please enter digits only."/>
                <h:commandButton value="Save" action="#{todoService.save}"/>
            </h:form>
        </div>
    </h:body>
</f:view>

上述兩個目的分別在兩個獨立的 div 元素中實現。

在第一個中,我們使用了 dataTable 元素來表示 todoService.AllTodo 中的所有值。

第二個 div 包含一個表單,用於修改 Todo 對象的狀態,該狀態位於 TodoService 中。

我們使用 inputText 元素來接受用户輸入,而第二個輸入則會自動轉換為 int 通過 commandButton,用户可以將 Todo 對象持久化(現在存儲在內存中)到 todoService.save

9. 結論

JSF 框架可以集成到 Spring 框架中。我們需要選擇哪個框架來管理 Bean,在本文中,我們使用了 Spring 框架。

但是,範圍模型與 JSF 框架略有不同,因此我們可能會考慮在 Spring 上下文中定義自定義範圍。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.