Пример контроллера, службы и 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:
К сожалению, при нажатии на кнопку возникает ошибка:
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.