Ein Controller-, Service- und DAO-Beispiel mit Spring Boot und JSF

1. Einleitung

JavaServer Faces ist ein serverseitiges komponentenbasiertes Benutzeroberflächen-Framework. Ursprünglich wurde es als Teil des Jakarta EE entwickelt. In diesem Tutorial untersuchen wir, wie JSF in eine Spring Boot-Anwendung integriert wird.

Als Beispiel implementieren wir eine einfache Anwendung, um eine TO-DO-Liste zu erstellen.

2. Maven-Abhängigkeiten

Wir müssen unsere pom.xml erweitern , um JSF-Technologien zu verwenden:

 org.apache.tomcat.embed tomcat-embed-jasper    org.glassfish javax.faces 2.3.7 

Das Artefakt javax.faces enthält auch die JSF-APIs und die Implementierungen. Detaillierte Informationen finden Sie hier.

3. Konfigurieren des JSF-Servlets

Das JSF-Framework verwendet XHTML-Dateien, um den Inhalt und die Struktur der Benutzeroberfläche zu beschreiben. Die Serverseite generiert die JSF-Dateien aus den XHTML-Beschreibungen.

Beginnen wir mit der Erstellung einer statischen Struktur in einer index.xhtml- Datei im Verzeichnis src / main / webapp :

    TO-DO application 

Welcome in the TO-DO application!

This is a static message rendered from xhtml.

Der Inhalt wird unter /index.jsf verfügbar sein . Auf der Clientseite wird jedoch eine Fehlermeldung angezeigt, wenn wir zu diesem Zeitpunkt versuchen, den Inhalt zu erreichen:

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

Es wird keine Backend-Fehlermeldung angezeigt. Trotzdem können wir herausfinden, dass wir ein JSF-Servlet benötigen, um die Anforderung zu verarbeiten, und die Servlet-Zuordnung, um die Anforderung mit dem Handler abzugleichen.

Da wir uns in Spring Boot befinden, können wir unsere Anwendungsklasse problemlos erweitern, um die erforderliche Konfiguration zu handhaben:

@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; } }

Das sieht toll und ziemlich vernünftig aus, ist aber leider immer noch nicht gut genug. Wenn wir jetzt versuchen, /index.jsf zu öffnen, erhalten wir einen weiteren Fehler:

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

Leider benötigen wir neben der Java-Konfiguration eine web.xml . Erstellen wir es in src / webapp / WEB-INF :

 Faces Servlet javax.faces.webapp.FacesServlet 1   Faces Servlet *.jsf 

Jetzt ist unsere Konfiguration einsatzbereit. Öffnen Sie /index.jsf :

Welcome in the TO-DO application! This is a static message rendered from xhtml.

Bevor wir unsere Benutzeroberfläche erstellen, erstellen wir das Backend der Anwendung.

4. Implementieren des DAO-Musters

DAO steht für Data Access Object. Normalerweise ist die DAO-Klasse für zwei Konzepte verantwortlich. Kapselung der Details der Persistenzschicht und Bereitstellung einer CRUD-Schnittstelle für eine einzelne Entität. Eine ausführliche Beschreibung finden Sie in diesem Tutorial.

Um das DAO-Muster zu implementieren, definieren wir zunächst eine generische Schnittstelle :

public interface Dao { Optional get(int id); Collection getAll(); int save(T t); void update(T t); void delete(T t); }

Jetzt erstellen wir unsere erste und einzige Domänenklasse in dieser Aufgabenanwendung:

public class Todo { private int id; private String message; private int priority; // standard getters and setters }

Die nächste Klasse wird die Implementierung von Dao sein . Das Schöne an diesem Muster ist, dass wir jederzeit eine neue Implementierung dieser Schnittstelle bereitstellen können.

Folglich können wir die Persistenzschicht ändern, ohne den Rest des Codes zu berühren.

In unserem Beispiel verwenden wir eine In-Memory-Speicherklasse :

@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. Die Service-Schicht

Das Hauptziel der DAO-Schicht besteht darin, die Details des Persistenzmechanismus zu behandeln. Während die Service-Schicht darüber steht, um die Geschäftsanforderungen zu erfüllen.

Beachten Sie, dass auf die DAO-Schnittstelle vom Dienst verwiesen wird:

@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; } }

Hier ist der Dienst eine benannte Komponente. Wir werden den Namen verwenden, um auf die Bean aus dem JSF-Kontext zu verweisen.

Diese Klasse hat auch einen Sitzungsbereich, der für diese einfache Anwendung zufriedenstellend ist.

Weitere Informationen zu Spring Scopes finden Sie in diesem Tutorial. Da die in Spring integrierten Bereiche ein anderes Modell als JSF haben, sollten Sie einen benutzerdefinierten Bereich definieren.

Weitere Anleitungen hierzu finden Sie in diesem Tutorial.

6. Der Controller

Genau wie in einer JSP-Anwendung übernimmt der Controller die Navigation zwischen den verschiedenen Ansichten.

Als nächstes implementieren wir einen minimalistischen Controller. Es wird von der Eröffnungsseite zur Seite mit der Aufgabenliste navigiert:

@Scope(value = "session") @Component(value = "jsfController") public class JsfController { public String loadTodoPage() { checkPermission(); return "/todo.xhtml"; } private void checkPermission() { // Details omitted } }

Die Navigation basiert auf dem zurückgegebenen Namen. Daraus ergibt sich die loadTodoPage wird uns an die schicken todo.xhtml Seite , die wir als nächstes umsetzen werde.

7. JSF und Spring Beans verbinden

Mal sehen, wie wir unsere Komponenten aus dem JSF-Kontext referenzieren können. Zuerst erweitern wir die index.xthml :

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

Hier haben wir einen commandButton innerhalb eines Formularelements eingeführt. Dies ist wichtig, da jedes UICommand- Element (z. B. commandButton) in einem UIForm- Element (z. B. form) platziert werden muss.

In diesem Stadium können wir unsere Anwendung starten und /index.jsf untersuchen :

Leider erhalten wir eine Fehlermeldung, wenn wir auf die Schaltfläche klicken:

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

In der Nachricht wird das Problem eindeutig angegeben: Der jsfController wurde auf null aufgelöst . Die entsprechende Komponente wurde entweder nicht erstellt oder ist im JSF-Kontext zumindest nicht sichtbar.

In dieser Situation ist letzteres wahr.

Wir müssen den Spring-Kontext mit dem JSF- Kontext in der webapp / WEB-INF / faces-config.xml verbinden :

   org.springframework.web.jsf.el.SpringBeanFacesELResolver  

Jetzt, da unser Controller betriebsbereit ist, benötigen wir die Datei todo.xhtml !

8. Interaktion mit einem Dienst von JSF

Unsere todo.xhtml- Seite hat zwei Zwecke. Zunächst werden alle Aufgabenelemente angezeigt.

Zweitens bieten Sie die Möglichkeit, der Liste neue Elemente hinzuzufügen.

Zu diesem Zweck interagiert die UI-Komponente direkt mit dem zuvor deklarierten Dienst:

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

The above mentioned two purposes are implemented in two separate div elements.

In the first, we used a dataTable element to represent all the values from todoService.AllTodo.

The second div contains a form where we can modify the state of the Todo object in the TodoService.

We use the inputText element to accept user input, where the second input is automatically converted into an int. With the commandButton, the user can persist (into the memory now) the Todo object with the todoService.save.

9. Conclusion

Das JSF-Framework kann in das Spring-Framework integriert werden. Sie müssen auswählen, welches Framework die Beans verwalten soll. In diesem Tutorial haben wir das Spring-Framework verwendet.

Das Bereichsmodell unterscheidet sich jedoch etwas vom JSF-Framework. Daher können Sie benutzerdefinierte Bereiche im Spring-Kontext definieren.

Wie immer ist der Code auf GitHub verfügbar.