JSFをSpring Bootに統合してアプリケーションを作成

はじめに

JSFとSpring Bootを組み合わせてアプリケーションを作成しました。 前回はThymeleafをテンプレートに使用していましたが、今回はJSFのFaceletを使用します。
すべてのコードは以下に載せてます。
github.com

どんなものを作ったか

ToDoリストを作りました。
最初の画面のボタンをクリックするとToDoリスト画面に遷移します。
f:id:xxyoko10:20201229215700p:plain f:id:xxyoko10:20201229215733p:plain

「Save」ボタンでリストを追加できます。 削除したいNoを入力して「Delete」ボタンを押下すると、リストの削除ができます。
f:id:xxyoko10:20201229220150p:plain

環境

Windows 10
Java 1.8
Spring Framework 5.3.2
STS 4.8.1
JSF 2.3.7

構成

f:id:xxyoko10:20201230153653p:plain

テンプレート

http:localhost:8080/index.jsf を入力して最初の画面が開くように設定します。

 <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>ToDoリストアプリケーションへようこそ</p>
            <p style="height:50px">
                <h:form>
                    <h:commandButton value="Todoリストを利用する" action="#{jsfController.loadTodoPage}" />
                </h:form>
            </p>
        </div>
    </h:body>
</f:view>

web.xmlJSFで使用する専用のサーブレットを登録しておきます。「Facesサーブレット」を登録しています。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

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

コントローラークラス

最初の画面から、ToDoリスト画面への遷移はコントローラーが担当します。「Todoリストを利用する」ボタンを押すと処理が走ります。

package com.example.controller;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

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

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

遷移先のToDoリスト画面です。

<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>
                ToDoを追加しましょう
            </div>
            <h:dataTable value="#{todoService.allTodo}" var="item">
                <h:column>
                    <f:facet name="header">
                    <h:outputText value="No"/>
                    </f:facet>
                    <h:outputText value="#{item.id}"/>
                </h:column>
                <h:column>
                    <div style="display:block; float:left; width:60px"></div>
                </h:column>                 
                <h:column>
                    <f:facet name="header">
                    <h:outputText value="やること"/>
                    </f:facet>
                    <h:outputText value="#{item.message}"/>
                </h:column>
                <h:column>
                    <div style="display:block; float:left; width:60px"></div>
                </h:column>                                 
                <h:column>
                    <f:facet name="header">
                    <h:outputText value="期限" style="display:block; float:left"/>
                    </f:facet>
                    <h:outputText value="#{item.deadline}"><f:convertDateTime  pattern="yyyy年MM月dd日"/></h:outputText> 
                </h:column>
            </h:dataTable>
        </div>
        <div><br></br><br></br><br></br><br></br><br></br>
            <div>
                New:
            </div>
            <h:form>     
                <h:panelGrid columns="8">     
                    <h:outputLabel for="message" value="やること"/>
                    <h:inputText id="message" value="#{todoService.todo.message}"/>
                    <div style="display:block; float:left; width:60px"></div>
                    <h:outputLabel for="deadline" value="期限"/>
                    <h:inputText id="deadline" value="#{todoService.todo.deadline}" style="width:80px"><f:convertDateTime  pattern="yyyyMMdd"/> </h:inputText>(yyyyMMdd)
                    <h:commandButton value="Save" action="#{todoService.save}"/>
                </h:panelGrid>
            </h:form>
            <h:form>  
                <h:panelGrid columns="3">        
                    <h:outputLabel for="id" value="削除"/>
                    <h:inputText id="id" value="#{todoService.todo.id}" style="width:60px"/>
                    <h:commandButton value="Delete" action="#{todoService.delete}"/>
                </h:panelGrid>  
            </h:form>            
        </div>
    </h:body>
</f:view>

サービスクラス

todo.xhtmlにToDoの要素を表示します。

package com.example.service;

import java.util.Collection;
import java.util.Comparator;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.example.model.Dao;
import com.example.model.Todo;

@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 void delete() {
        todoDao.delete(todo);
        todo = new Todo();
    }

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

    public Collection<Todo> getAllTodoSortedByPriority() {
        return todoDao.getAll().stream().sorted(Comparator.comparingInt(Todo::getId)).collect(Collectors.toList());
    }

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

    public Todo getTodo() {
        return todo;
    }
}
package com.example.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.stereotype.Component;

@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();
        todo.setId(index);
        return index;
    }

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

}

TodoServiceクラスの getAllTodo() メソッドがはじめに呼ばれ、ToDoの要素をすべて表示します。@Scope(value = "session")で データをセッションスコープで保持しています。

つまずいたところ

  • cannot find the declaration of element 'faces-config' エラー発生
    アプリケーション起動時に『cannot find the declaration of element 'faces-config'.』というエラーが発生しました。
    エラーの原因は「faces-config.xml」にありました。
<?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>


4行目の【xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd】のURLの記述が間違っていました。

<?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
  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

xsdファイルのURLを変更することで、エラーを解決できました。
参考:jtdz-solenoids.com

  • java.lang.ClassNotFoundException: javax.enterprise.context.spi.Contextual エラー発生
    アプリケーション起動時に『java.lang.ClassNotFoundException: javax.enterprise.context.spi.Contextual』というエラーが発生しました。
    「pom.xml」に以下のライブラリーを追加することで治りました。
     <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <version>1.2</version>
        </dependency> 


参考: stackoverflow.com

参考

www.codeflow.site