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

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

1. Вступление

JavaServer Faces - это основанная на сервере компонентная структура пользовательского интерфейса. Первоначально он был разработан как часть Java EE. В этом руководствеwe’ll investigate how to integrate JSF into a Spring Boot application.

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

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

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


    org.apache.tomcat.embed
    tomcat-embed-jasper



    org.glassfish
    javax.faces
    2.3.7

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

3. Конфигурирование сервлета JSF

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

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


    
        
        
        TO-DO application
    
    
        

Welcome in the TO-DO application!

This is a static message rendered from xhtml.

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

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

Там не будет никакого сообщения об ошибке бэкэнда. Даже в этом случае мы можем вычислитьwe need a JSF servlet to handle the request и отображение сервлета, чтобы сопоставить запрос с обработчиком.

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

Unfortunately, we need a web.xml beside the Java configuration. Давайте создадим его вsrc/webapp/WEB-INF:


    Faces Servlet
    javax.faces.webapp.FacesServlet
    1


    Faces Servlet
    *.jsf

Теперь наша конфигурация готова к работе. Открыть<your-url>/index.jsf:

Welcome in the TO-DO application!

This is a static message rendered from xhtml.

Прежде чем мы создадим наш пользовательский интерфейс, давайте создадим серверную часть приложения.

4. Реализация паттерна DAO

DAO обозначает объект доступа к данным. Обычно класс DAO отвечает за две концепции. Инкапсуляция деталей персистентного уровня и предоставление интерфейса CRUD для одного объекта. Вы можете найти подробное описание в руководствеthis.

Чтобы реализовать шаблон DAO,we’ll first define a generic interface:

public interface Dao {

    Optional get(int id);
    Collection 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>. Прелесть этого паттерна в том, что мы можем предоставить новую реализацию этого интерфейса в любое время.

Следовательно, мы можем изменить уровень постоянства, не затрагивая остальную часть кода.

В нашем примереwe’ll use an in-memory storage class:

@Component
public class TodoDao implements Dao {

    private List todoList = new ArrayList<>();

    @Override
    public Optional get(int id) {
        return Optional.ofNullable(todoList.get(id));
    }

    @Override
    public Collection 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 todoDao;
    private Todo todo = new Todo();

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

    public Collection 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 ознакомьтесь с руководством поthis. Since Spring’s built-in scopes have a different model than JSF, it’s worth considering defining a custom scope.

Дополнительные инструкции по этому поводу доступны в руководствеthis.

6. Контроллер

Как и в приложении JSP, контроллер будет обрабатывать переходы между различными представлениями.

Далее мы реализуем минималистичный контроллер. Он перейдет от начальной страницы к странице списка дел:

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

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

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

The navigation is based on the returned name. Следовательно,loadTodoPage отправит нас на страницуtodo.xhtml, которую мы реализуем дальше.

7. Соединение JSF и Spring Beans

Давайте посмотрим, как мы можем ссылаться на наши компоненты из контекста JSF. Сначала расширимindex.xthml:


    
       // same code as before
    
    
        
// same code as before

Здесь мы ввелиcommandButton внутри элемента формы. This is important since every UICommand element (e.g. commandButton)has to be placed inside of a UIForm element (e.g. form).

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

image

К сожалению, при нажатии на кнопку возникает ошибка:

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:



    
        org.springframework.web.jsf.el.SpringBeanFacesELResolver
    

Теперь, когда наш контроллер готов к работе, нам понадобитсяtodo.xhtml!

8. Взаимодействие с сервисом из JSF

Наша страницаtodo.xhtml будет иметь две цели. Сначала отобразятся все элементы списка дел.

Во-вторых, предложите возможность добавлять новые элементы в список.

Для этого компонент пользовательского интерфейса будет напрямую взаимодействовать с сервисом, объявленным ранее:


    
        
        
        TO-DO application
    
    
        
List of TO-DO items
Message #{item.message} Priority #{item.priority}
Add new to-do item:

Вышеупомянутые две цели реализованы в двух отдельных элементахdiv.

В первом мы использовали элементdataTable для представления всех значений изtodoService.AllTodo.

Второйdiv содержит форму, в которой мы можем изменить состояние объектаTodo вTodoService.

We use the inputText element to accept user input, where the second input is automatically converted into an int.  С помощьюcommandButton, пользователь может сохранить (сейчас в памяти) объектTodo с помощьюtodoService.save.

9. Заключение

Каркас JSF может быть интегрирован в каркас Spring. Вы должны выбрать, какой фреймворк будет управлять компонентами. В этом уроке мы использовали среду Spring.

Однако модель области действия немного отличается от структуры JSF. Так что вы можете рассмотреть возможность определения пользовательских областей в контексте Spring.

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