Перейти к основному содержимому

Пример контроллера, службы и DAO с Spring Boot и JSF

· 7 мин. чтения

1. Введение

JavaServer Faces — это инфраструктура пользовательского интерфейса на основе компонентов на стороне сервера. Первоначально он разрабатывался как часть Jakarta EE. В этом руководстве мы рассмотрим, как интегрировать JSF в приложение Spring Boot.

В качестве примера мы реализуем простое приложение для создания списка дел.

2. Зависимости Maven

Мы должны расширить наш pom.xml для использования технологий JSF:

<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--JSF-->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.faces</artifactId>
<version>2.3.7</version>
</dependency>

Артефакт javax.faces также содержит API-интерфейсы JSF и их реализации. Подробную информацию можно найти здесь .

3. Настройка сервлета JSF

Платформа JSF использует файлы XHTML для описания содержимого и структуры пользовательского интерфейса. На стороне сервера файлы JSF создаются из описаний XHTML.

Начнем с создания статической структуры в файле index.xhtml в каталоге src/main/webapp :

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

Контент будет доступен по адресу <your-url>/index.jsf . Хотя мы получим сообщение об ошибке на стороне клиента, если попытаемся получить доступ к содержимому на этом этапе:

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

Не будет сообщения об ошибке бэкэнда. Тем не менее, мы можем понять, что нам нужен сервлет JSF для обработки запроса и сопоставление сервлета для сопоставления запроса с обработчиком.

Поскольку мы находимся в 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 javax.faces.context.FacesContextFactory.

К сожалению, помимо конфигурации Java нам нужен файл web.xml . Давайте создадим его в src/webapp/WEB-INF :

<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>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>

Теперь наша конфигурация готова к работе. Откройте <ваш-url>/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.

Кроме того, этот класс имеет область сеанса, которая будет удовлетворять этому простому приложению.

Для получения дополнительной информации об областях 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 Bean

Давайте посмотрим, как мы можем ссылаться на наши компоненты из контекста 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 (например, формы).

На этом этапе мы можем запустить наше приложение и изучить <your-url>/index.jsf :

./66da1f02c3de3ba6fafce845b91cb6db.png

К сожалению, при нажатии на кнопку мы получим ошибку:

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

В сообщении четко указывается проблема: jsfController разрешается в null. Соответствующий компонент либо не создан, либо, по крайней мере, невидим из контекста JSF.

В данной ситуации верно последнее.

Нам нужно связать контекст Spring с контекстом JSF в файле webapp/WEB-INF/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>

Теперь, когда наш контроллер готов к работе, нам понадобится файл 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.

Как всегда, код доступен на GitHub .