View
216
Download
0
Category
Preview:
Citation preview
Prof. Dr. Stephan Kleuker
310Komponentenbasierte Software-Entwicklung
4.5 JSF mit EJB und JPA
Ziel: einfache Persistierung von Daten (Create, Read, Update, Delete) mit Transaktionssteuerung
• DB einbinden
• Kurzer Überblick EJB
• Controller zur Seitenverwaltung
• Ausgabe dynamischer Tabellen
Prof. Dr. Stephan Kleuker
311Komponentenbasierte Software-Entwicklung
Datenbank in Server einbinden (1/2)
Einschub: Nutzung von JPA bei DB im Server
• Datenbank einrichten
• Data Source einrichten
Prof. Dr. Stephan Kleuker
312Komponentenbasierte Software-Entwicklung
Datenbank in Server einbinden (2/2)
• Datenbank wird unter JNDI-Namen im Server registriert
• JSF-Applikation kann über JNDI auf DB zugreifen
• JSF-Container regelt DB-Zugriff (nicht Nutzer!), typische container-managed Persistance [geht auch individuell]
Prof. Dr. Stephan Kleuker
313
Erinnerung: beans.xml
• Für die Nutzung von CDI (Contexts and DependencyInjection) wird Datei beans.xml benötigt, die sich im Ordner WEB-INF befinden muss
• Wenn nicht da, erzeugen
• vorletzte Zeile ändern (statt annotated in all)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
314
EJB-SessionBeans
Architekturvariante
• bisher einfache direkte Software-Architektur (nutzbar für kleinere und mittlere Projekte)
• Problem: Skalierung nicht unbedingt sichergestellt, da Server nicht im Detail eingreifen kann
• Problem: direkte Nutzung von JPA wäre sehr bastelig
• Lösung: Datenbankzugriff in eigene Klasse (Enterprise JavaBean, EJB) auslagern
• EJB werden vom Server verwaltet (Tempo, Skalierung)
• EJB werden in Verwaltungsklassen injiziert
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
315
EJB - Konzept
• Standardisierte Komponenten eines JEE-Servers• Klassen zur Steuerung von Prozessen und Verwaltung von
Daten, die leicht in anderen EJB-Applikationen nutzbar sind• Unterstützt mit wenig Aufwand
– Transaktionssteuerung (Transaktion pro Methodenaufruf)– Sicherheitseinstellungen– Identifikation von Objekten zur Zusammenarbeit
• Drei Arten (local und remote Varianten)– Entity Bean (POJO, @Entity wie bereits bekannt)– Session Bean: Kommunikation mit (End-)Anwendungen
• @Stateless: bei jeder Nutzung neu erstellt• @Stateful: in einer Session nutzbar
– Message Driven Bean: asynchroner Nachrichtenablauf
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
316
Systematische Architektur
• Ansatz: mehrere Schichten, jede Schicht redet nur mit den umgebenden Schichten
• GUI konsequent vom Rest trennen, um Schicht einfach auszutauschen
• GUI kennt keine Fachklassen, nur Strings und Zahlen
• GUI erhält View-Controller (View-Model), der Zustand des GUIs kennt und Verbindung zur Geschäftslogik enthält
• Geschäftslogik hat keine Information über GUI
• Persistenz-Schicht sollte austauschbar sein
• Hier: konsequenter Einsatz von Injection
• Literatur: M. Yener, A. Theedom, Java EE Design Patterns, Wrox, John Wiley & Sons, Indianapolis (USA), 2015
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
317
Mini-Beispiel (1/9)
• Nicht editierbare Aufgabenliste
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
318
Mini-Beispiel (2/9): Architektur
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
319
Mini-Beispiel (3/9): Projektaufbau
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
320
Mini-Beispiel (4/9): Datenmodell@Entity
public class Aufgabe implements Serializable{
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String aufgabe;
public int getId() { return id; }
public void setId(int id) {
this.id = id;
}
public String getAufgabe() { return aufgabe;}
public void setAufgabe(String aufgabe) {
this.aufgabe = aufgabe;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
321
Mini-Beispiel (5/9): Persistenzschicht (1/2)
public class MyProducers {
@Produces // evtl. mit Qualifier kennzeichnen
@PersistenceContext(unitName = "ListeJSFEJBPU")
private EntityManager em;
}
• Injection passiert mit CDI (Contexts and Dependency Injection, später mehr)
• CDI und EJB getrennte Konzepte
• so werden EJBs dem CDI-Lebenszyklus untergeordnet
• @Produces: angelegtes Objekt über CDI injizierbar
• Muss! bei vorgeschlagener Architektur
• gibt Alternativen zu EJB über Annotationen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
322
Mini-Beispiel (6/9): Persistenzschicht (2/2)@Stateless
public class Persistence {
@Inject
private EntityManager em;
public void persist(Object object) {
em.persist(object); //Exception weiterreichen
}
public Object merge(Object object) {
return em.merge(object); //Exception weiterreichen
}
public List<Aufgabe> findAllAufgaben() {
return em.createQuery("SELECT a FROM Aufgabe a", Aufgabe.class)
.getResultList();
}
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
323
Mini-Beispiel (7/9): Geschäftslogik@Dependent // hier wären fachliche Berechnungen
public class Modell implements Serializable{
@Inject
private Persistence db;
public List<String> aufgaben(){ // könnte auch Aufgaben liefern
List<String> erg = new ArrayList<>();
for(Aufgabe a:this.db.findAllAufgaben()){
erg.add(a.getAufgabe());
}
return erg;
}
public void aufgabeHinzu(String aufgabe){
Aufgabe a = new Aufgabe();
a.setAufgabe(aufgabe);
this.db.persist(a);
}
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
324
Mini-Beispiel (8/9): ViewModel@Named(value = "vm")
@RequestScoped
public class ViewModel implements Serializable{
private String aufgabe;
@Inject
private Modell model;
public List<String> aufgaben(){
System.out.println("aufgaben");
return this.model.aufgaben();
}
public void aufgabeHinzu(){
this.model.aufgabeHinzu(this.aufgabe);
this.aufgabe = "";
}
public String getAufgabe() { return aufgabe; }
public void setAufgabe(String aufgabe) {
this.aufgabe = aufgabe;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
325
Mini-Beispiel (9/9): View
<h:head>
<title>Aufgaben</title>
</h:head>
<h:body>
<h:form id ="form">
<h:outputText value="Aufgabe: "/>
<h:inputText id="aufgabe" value="#{vm.aufgabe}"/>
<h:commandButton id="hinzu" value="OK"
action="#{vm.aufgabeHinzu()}"/>
<br/>
<ui:repeat value="#{vm.aufgaben()}" var="a">
#{a} <br/>
</ui:repeat>
</h:form>
</h:body>
</html>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
326
@Dependent
@Dependent
public class Modell implements Serializable{ … }
• Annotation gehört zu CDI (später)
• Injiziertes Objekt übernimmt Scope des nutzenden Objekts
• es werden dann ausschließlich Objekte dieser Klasse vom CDI-Framework erzeugt; nie selbst den Konstruktor aufgerufen
… // in anderer Klasse@Inject
private Modell model;
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
327
Angedeutetes Problem (1/3): Ausgaben
• man erkennt, dass aufgaben() sehr häufig aufgerufen wird
• Problem, da jedes Mal Datenbank genutzt wird
Komponentenbasierte Software-Entwicklung
Ausgaben nach letzter Eingabe
Information: aufgaben
Information: aufgaben
Information: aufgaben
Information: aufgaben
Information: aufgaben
Information: aufgaben
Prof. Dr. Stephan Kleuker
328
Angedeutetes Problem (2/3): Lösung
public class ViewModel implements Serializable{
private String aufgabe;
// neu: lokale Variable fuer darzustellende Objekte
// hier sehr verinfacht nur Strings
private List<String> aufgaben; // neu
@Inject
private Modell model;
// neu: nach Objekterzeugung (und Injects)
@PostConstruct
public void init(){
this.aufgaben = this.model.aufgaben();
}
public List<String> aufgaben(){
return this.aufgaben; // neu
// return this.model.aufgaben();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
329
Angedeutetes Problem (3/3): Lösung
• letzte Folie zeigt Lösung mit lokaler Datenhaltung der anzuzeigenden Daten
• JSF-Framework fragt darzustellende Daten mehrfach ab
• bei SessionScope
– rufe nach eigener Änderung (CREATE, UPDATE, DELETE) die darzustellenden Daten erneut ab
– Erinnerung: Session-kontext wird gespeichert und bei nächster Seitennutzung wieder hergestellt
– mit @PostConstruct annotierte Methode kann auch an anderen Stellen aufgerufen werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
330
4.6 Detaillierteres Beispiel: Mitarbeiterverwaltung
• Zu entwickeln ist eine Komponente zur Verwaltung von Mitarbeitern
• generelle Attribute: Mitarbeiternummer (minr), Vorname, Nachname, Gehalt, Geburtsdatum
• Beispiel zeigt Varianten bei der Umsetzung; ob sinnvoll hängt von individuellem Projekt ab
• Besonderheit: zu entwickelnde Komponente darf nur Teile der Attribute füllen (aus juristischen oder … Gründen, erfolgt die Vervollständigung der Daten mit Gehalt und Geburtsdatum in einer anderen Komponente)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
331
Ergebnis
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
332
Beispiel: Klasse Mitarbeiter
@Entity
public class Mitarbeiter implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int minr;
private String vorname;
private String nachname;
private int gehalt;
@Temporal(javax.persistence.TemporalType.DATE)
private Date geburtsdatum;
@Version
private int version;
public Mitarbeiter(){}
// weitere Konstruktoren denkbar
// getter und setter; equals(), HashCode() basierend auf minr
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
333
Erinnerung: MitarbeiterBuilder (1/2)public class MitarbeiterBuilder implements Serializable{
private int minr;
private String vorname;
private String nachname;
private int gehalt;
private Date geburtsdatum;
private MitarbeiterBuilder(){};
public static MitarbeiterBuilder create(){
return new MitarbeiterBuilder();
}
public MitarbeiterBuilder minr(int minr){
this.minr = minr;
return this;
} // analog fuer vorname und nachname
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
334
Erinnerung: MitarbeiterBuilder (2/2)public MitarbeiterBuilder gehalt(int gehalt){
this.gehalt = gehalt;
return this;
}
public MitarbeiterBuilder geburtsdatum(Date geburtsdatum){
this.geburtsdatum = geburtsdatum;
return this;
}
public Mitarbeiter build(){
Mitarbeiter ergebnis = new Mitarbeiter();
ergebnis.setVorname(this.vorname);
ergebnis.setNachname(this.nachname);
ergebnis.setGehalt(this.gehalt);
ergebnis.setGeburtsdatum(this.geburtsdatum);
return ergebnis;
}
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
335
Informationsweitergabe zwischen Schichten (1/3)
• Alle Schichten nutzen Objekte aus der Datenbank
• einfach realisierbar
• wenn nur Teilinformationen benötigt ist es langsam, speicheraufwändig; soll jeder DB-Objekte kennen?
Komponentenbasierte Software-Entwicklung
Oberfläche
Oberflächensteuerung
Persistenz
Geschäftslogik
Datenbank<<Entity>>
<<nutzt>>
Prof. Dr. Stephan Kleuker
336
Informationsweitergabe zwischen Schichten (2/3)
• Informationen werden in Data-Transfer-Objekte (DTO) verpackt
• Information können passenden Typ haben, in DTO validieren
• DTO auch Value Object genannt
• einmalig einpacken (marshalling) und dann auspacken (unmarshalling) von Informationen
Komponentenbasierte Software-Entwicklung
Oberfläche
Oberflächensteuerung
Persistenz
Geschäftslogik
Datenbank
<<DTOs>>
<<nutzt>>
<<erzeugt>>
Prof. Dr. Stephan Kleuker
337
Informationsweitergabe zwischen Schichten (3/3)
• DTO pro Kommunikation von Schichten
• Information genau an nutzende Schicht angepasst, z. B. Oberfläche benötigt nur elementare Datentypen
• seht häufiges einpacken und auspackenKomponentenbasierte Software-
Entwicklung
Oberfläche
Oberflächensteuerung
Persistenz
Geschäftslogik
Datenbank
<<DTOgp>><<nutzt>>
<<erzeugt>>
<<DTOog>><<nutzt>>
<<erzeugt>>
<<DTOoo>><<nutzt>>
<<erzeugt>>
Prof. Dr. Stephan Kleuker
338
Data Transfer Object (1/4)
public class MitarbeiterDTO implements Serializable{
private int minr; // gehalt soll nicht sichtbar sein
private String vorname;
private String nachname;
private String alter; // fuer Ausgabe vorbereitet
private String befehl; // fuer Steuerung
public MitarbeiterDTO(){ }
public MitarbeiterDTO(int minr, String vorname
, String nachname, String alter, String befehl) {
this.minr = minr;
this.vorname = vorname;
this.nachname = nachname;
this.alter = alter;
this.befehl = befehl;
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
339
Data Transfer Object (2/4)
public static MitarbeiterDTO toMitarbeiterDTO(Mitarbeiter m){
if (m == null){
return null;
}
LocalDate jetzt = LocalDate.now();
Date tmp = m.getGeburtsdatum();
String alter = "unbekannt";
if (tmp != null){
LocalDate geburt = tmp.toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate();
alter = "" + ChronoUnit.YEARS.between(geburt, jetzt);
}
return new MitarbeiterDTO(m.getMinr(), m.getVorname()
, m.getNachname(), alter,"");
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
340
Data Transfer Object (3/4)public Mitarbeiter toMitarbeiter(){
return MitarbeiterBuilder
.create() .minr(this.minr) .vorname(this.vorname)
.nachname(this.nachname).build();
}
public void aktualisieren(Mitarbeiter m){
m.setVorname(this.vorname);
m.setNachname(this.nachname);
}
public String validieren(){
String ergebnis = "";
if(this.vorname.trim().length() < 2
|| this.nachname.trim().length() <2){
ergebnis = "Vor- und Nachname müssen mind. zwei Zeichen";
}
return ergebnis;
}Komponentenbasierte Software-Entwicklung
Projekt benötigt ein Konzept zum Umgang mit Validierungen (nur eine pro Client und Server)
Codierung: leerer String ok, sonst Fehlermeldung (schöner
mit eigener Klasse)
Prof. Dr. Stephan Kleuker
341
Data Transfer Object (4/4)@Override
public int hashCode() {
int hash = 5;
hash = 53 * hash + this.minr;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
return this.minr != ((MitarbeiterDTO) obj).minr;
}
// getter und setter für: minr, vorname, nachname, alter
Komponentenbasierte Software-Entwicklung
hashCode und equals basieren nur auf minr
Prof. Dr. Stephan Kleuker
342
Architektur
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
343
Persistenz
• Persistenz wie im ersten Beispiel behandelt
• hier könnten u. a. Transaktionen scheitern -> Methoden werden Exception
• sind RuntimeExceptions, kein throws oder catch benötigt
• benötigt wird einheitliches Konzept, wie mit Exceptions umgegangen werden soll; hier direkte Weitergabe und Behandlung auf Controller-Ebene
• Hinweis: diese Exceptions sind ineinander geschachtelt, so dass Grund schwer feststellbar: e. getCausedByException()
• EJBException, RollbackException, PersistenceException, SQLIntegrityConstraintViolationException, …
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
344
Persistenz (1/3)
public class EJBinCDIIntegration {
@Produces
@PersistenceContext(unitName = "kbseMitarbeiterverwaltungPU")
private EntityManager em;
}
• Erinnerung: EJB wird CDI untergeordnet (bei Stateless recht einfach machbar)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
345
Persistenz (2/3)
@Stateless
public class Persistence {
@Inject
private EntityManager em;
public void save(MitarbeiterDTO m) {
this.em.persist(m.toMitarbeiter());
}
public void update(MitarbeiterDTO m) {
Mitarbeiter mit = this.em.find(Mitarbeiter.class, m.getMinr());
m.aktualisieren(mit);
this.em.persist(mit);
}
public MitarbeiterDTO mitarbeiter(int minr) {
return MitarbeiterDTO
.toMitarbeiterDTO(this.em.find(Mitarbeiter.class, minr));
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
346
Persistenz (3/3)
public void delete(MitarbeiterDTO m) {
Mitarbeiter mit = this.em.find(Mitarbeiter.class, m.getMinr());
this.em.remove(mit);
}
public List<MitarbeiterDTO> alleMitarbeiter() {
List<Mitarbeiter> tmp = this.em
.createQuery("SELECT m FROM Mitarbeiter m",Mitarbeiter.class)
.getResultList();
List<MitarbeiterDTO> ergebnis = new ArrayList<>();
tmp.forEach(m ->
ergebnis.add(MitarbeiterDTO.toMitarbeiterDTO(m)));
return ergebnis;
}
}
Komponentenbasierte Software-Entwicklung
TODO: als @NamedQueryauslagern
Prof. Dr. Stephan Kleuker
347
Controller
• auf dieser Ebene gibt es die Klassen zur Verwaltung der Entitäten
• diese Klasse sollte nichts/wenig über die Oberfläche, generell über ihre Nutzer, wissen
• Klasse kann völlig unabhängig von JSF (aber auch JPA) gestaltet werden (würde sich Interface anbieten)
• um unabhängig von JSF zu sein, gibt es neutrale Klasse Resultat, die informiert, ob ein Aktion erfolgreich war und beim Scheitern einen Grund angeben kann [oftmals ist genauerGrund für Endnutzer uninteressant]
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
348
Resultatpublic class Resultat implements Serializable{
private boolean ergebnis = true;
private String kommentar ="";
public Resultat(){}
public Resultat(boolean ergebnis, String kommentar) {
this.ergebnis = ergebnis;
this.kommentar = kommentar;
}
public boolean isErgebnis() { return ergebnis;}
public void setErgebnis(boolean ergebnis) {
this.ergebnis = ergebnis;
}
public String getKommentar() { return kommentar; }
public void setKommentar(String kommentar) {
this.kommentar = kommentar;
}Komponentenbasierte Software-
Entwicklung
in POJOs bei boolean ist isstatt get erlaubt
Prof. Dr. Stephan Kleuker
349
Controller (1/4)@Dependent
public class Controller implements Serializable {
@Inject
private Persistence persist;
public Resultat save(MitarbeiterDTO m) {
String ergebnis = m.validieren();
if (!ergebnis.equals("")) {
return new Resultat(false, ergebnis);
}
try {
this.persist.save(m);
return new Resultat(true, "erfolgreich gespeichert");
} catch (Throwable e) { // egal was, immer fangen
return new Resultat(false
,"Aktion gescheitert: " + e.getMessage());
}
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
350
Controller (2/4)
public List<MitarbeiterDTO> alleMitarbeiter() {
return this.persist.alleMitarbeiter();
}
public Resultat update(MitarbeiterDTO m) {
String ergebnis = m.validieren();
if (!ergebnis.equals("")) {
return new Resultat(false, ergebnis);
}
try {
this.persist.update(m);
return new Resultat(true, "erfolgreich aktualisiert");
} catch (Throwable e) {
return new Resultat(false,"Aktion gescheitert: "
+ "Mitarbeiter gelöscht? Detail: " + e.getMessage());
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
351
Controller (3/4)public Resultat delete(MitarbeiterDTO m) {
String ergebnis ="";
try {
this.persist.delete(m);
return new Resultat(true, "erfolgreich gelöscht");
} catch (Throwable e) {
try {
if (this.mitarbeiter(m.getMinr()) != null) {
ergebnis = " löschen gescheitert " + e;
}
else {
ergebnis = "erfolgreich gelöscht (war vorher schon)";
}
} catch(Exception e2) {
ergebnis = "Aktion gescheitert: " + e2;
}
return new Resultat(false, ergebnis);
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
352
Controller (4/4)public MitarbeiterDTO mitarbeiter(int minr) {
return this.persist.mitarbeiter(minr);
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
353
ViewModel
• erst ab hier JSF-spezifisch
• hier kann Aufteilung in ViewModelController (z. B. SessionScoped) und ViewModel (RequestScoped) sinnvoll sein
• leider fliegen in der Literatur die Namen für diese Modellebene oft durcheinander; teilweise aus „politischen“ Überlegungen, da MVVM nach Microsoft und birektionalemBinding klingt
• ViewModel enthält spezielle Daten, um Oberfläche zu steuern (hier welcher Bearbeitungsmodus)
• hier neu: JSF unterstützt auch klassische HTTP-get-Aufrufe mit Parametern [keine Architekturbesonderheit]
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
354
ViewModel (1/4)
@Named("mvm")
@RequestScoped
public class MitarbeiterViewModel implements Serializable {
private MitarbeiterDTO mitarbeiter; // zur Bearbeitung
private List<MitarbeiterDTO> alle;
private String ausgabe = ""; //Verzicht auf FacesContext
private boolean editmodus;
private boolean loeschenmodus;
@Inject
private Controller controller;
public MitarbeiterViewModel() {
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
355
ViewModel (2/4)@PostConstruct
public void initialisieren() {
this.alle = this.controller.alleMitarbeiter();
this.mitarbeiter = new MitarbeiterDTO();
this.editmodus = false;
this.loeschenmodus = false;
}
public void uebernehmen() {
if (this.editmodus) {
this.ausgabe = this.controller
.update(this.mitarbeiter).getKommentar();
} else {
this.ausgabe = this.controller
.save(this.mitarbeiter).getKommentar();
}
this.initialisieren();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
356
ViewModel (3/4)
public void aufrufVerarbeiten() { // nachdem Parameter gelesen
if (this.editmodus) {
this.mitarbeiter = this.controller
.mitarbeiter(this.mitarbeiter.getMinr());
if (this.mitarbeiter != null) {
return;
} else {
this.editmodus = false;
this.ausgabe = "Mitarbeiter existiert nicht (mehr)";
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
357
ViewModel (4/4)
if (this.loeschenmodus) {
this.mitarbeiter = this.controller
.mitarbeiter(this.mitarbeiter.getMinr());
if (this.mitarbeiter != null) {
this.ausgabe = this.controller
.delete(this.mitarbeiter).getKommentar();
this.initialisieren();
} else {
this.loeschenmodus = false;
this.ausgabe = " Mitarbeiter geloescht (vorher schon)";
}
}
this.mitarbeiter = new MitarbeiterDTO();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
358
JSF-View
• hier neu: Nutzung von get-Parametern
• aktuelle Seiten nicht für Bookmarks oder Mails geeignet
• sinnvoll wäre GET-Nutzung (ab JSF 2.0 möglich, wird erweitert, mindestens 2.2.6 von mojarra notwendig)
• Ansatz: In Seite können GET-Parameter spezifiziert werden
• aufgerufene Seite erhält Parameter und kann diese in speziellen f:metadata-Tag lesen und mit Variablen des Controllers verknüpfen
• Steuerung passiert durch neue Elemente, h:button und h:link ersetzen h:commandButton und h:commandLink
• http://localhost:8080/kbseMitarbeiterverwaltung/faces/index.xhtml;jsessionid=4d39f162427f80bd9370f6db120f?minr=1552&edit=true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
359
Konzept von get
<h:button value="Editieren" outcome="mitarbeiterParam">
<f:param name="mid" value="#{m.id}" />
<f:param name="actionId" value="2" />
</h:button>
• outcome nennt aufzurufende Seite
• Parameter per GET übergeben
• aufgerufene Seite, in <f:metadata>
– Einlesen mit<f:viewParam name="mid" value="#{controller.id}" />
– zentrale Aktion, die nach dem Einlesen passieren soll <f:viewAction action="#{controller.reagiereAufGet}"/>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
360
View (1/6)
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<f:metadata>
<f:viewParam name="minr" required="false"
value="#{mvm.mitarbeiter.minr}"/>
<f:viewParam name="edit" required="false"
value="#{mvm.editmodus}"/>
<f:viewParam name="loeschen" required="false"
value="#{mvm.loeschenmodus}"/>
<f:viewAction action="#{mvm.aufrufVerarbeiten()}"/>
</f:metadata>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
361
View (2/6)<h:head>
<title>Mitarbeiterverwaltung</title>
</h:head>
<h:body>
<h:form id="mitarbeiter">
<h:panelGrid id ="eingabe" columns="3" >
<h:outputText value="Mitarbeiternummer: "/>
<h:outputText value="#{mvm.mitarbeiter.minr}"
rendered="#{mvm.mitarbeiter.minr != 0}"/>
<h:outputText value="vom System vergeben"
rendered="#{mvm.mitarbeiter.minr == 0}"/>
<br/>
<h:outputText value="Vorname: "/>
<h:inputText id="vorname"
value="#{mvm.mitarbeiter.vorname}"/>
<h:message for="vorname" style="color:red"/>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
362
View (3/6)
<h:outputText value="Nachname: "/>
<h:inputText id="nachname"
value="#{mvm.mitarbeiter.nachname}"/>
<h:message for="nachname" style="color:red"/>
<h:commandButton value="übernehmen"
action="#{mvm.uebernehmen}"/>
<h:button value="abbrechen"
outcome="index"
rendered="#{mvm.editmodus}">
<f:param name="edit" value="false" />
</h:button>
</h:panelGrid>
<h:outputText value="#{mvm.ausgabe}"/>
<br/>
Komponentenbasierte Software-Entwicklung
nur im Edit-Modus diesen Knopf einblenden
rufe outcome-Seite mit diesem get-Parameter auf:
index.xhtml?edit=false
Prof. Dr. Stephan Kleuker
363
View (4/6)
<h:panelGrid rendered="#{!empty mvm.alle}">
<h:dataTable value="#{mvm.alle}" var="m" border="0" >
<h:column>
<f:facet name="header">
<h:outputText value="Minr" />
</f:facet>
<h:outputLabel value="#{m.minr}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Vorname" />
</f:facet>
<h:outputLabel value="#{m.vorname}"/>
</h:column>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
364
View (5/6)<h:column>
<f:facet name="header">
<h:outputText value="Nachname" />
</f:facet>
<h:outputLabel value="#{m.nachname}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Alter" />
</f:facet>
<h:outputLabel value="#{m.alter}"/>
</h:column>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
365
View (6/6)
<h:column>
<h:button value="editieren" outcome="index">
<f:param name="minr" value="#{m.minr}" />
<f:param name="edit" value="true" />
</h:button>
</h:column>
<h:column>
<h:button value="löschen" outcome="index">
<f:param name="minr" value="#{m.minr}" />
<f:param name="loeschen" value="true" />
</h:button>
</h:column>
</h:dataTable>
</h:panelGrid>
</h:form>
</h:body>
</html>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
366
4.7 Testen von JEE-Programmen
Erinnerung an Testarten
• Unit-Tests: durch Entwickler, Methoden einzelner Klassen, Zusammenspiel zusammengehöriger Klassen
• Komponententests: Test von ein oder mehr Packages, Fokus auf die dazwischenliegenden Schnittstellen
• Systemtests, Abnahmetests: Tests des Gesamtsystems als Blackbox, Fokus auf zentrale Use Cases, Endabnahme
• technisches Problem: JUnit kann nicht direkt @Inject(allgemein CDI) nutzen, bzw. Objekte zur Verfügung stellen
• Ergänzung von JUnit durch Arquillian, http://arquillian.org/
• Installation und Start kann sehr ätzend sein, danach absolut intuitiv nutzbar
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
367
Gutes Testen
• generell muss Software testbar geschrieben werden (get-, set-Methoden, kurze Methoden mit wenig Verzweigungen, …)
• Testentwickler muss erfahrener Software-Entwickler sein
– kennt die typischen Fallen in einer Software
– muss eine sinnvolle Testarchitektur (wart- und erweiterbar) entwickeln
• Testentwickler muss typische Use Cases der SW kennen
• Ziel: kleine Änderungen in der zu testenden Software (SuT = System under Test) führt nur zu kleinen Änderungen in Tests
• Tests so entwickeln, dass Entwickler der SuT weiterentwickelt, diesen Tests trauen kann (keine Abhängigkeiten von der Zeit)
• Ziel: Testautomatisierung, Continous Integration / Development
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
368
Arquillian
• unterstützt CDI, dadurch @Inject in Tests nutzbar
• benötigt Server auf dem zu testende App installiert wird, hierzu gibt es folgende Varianten:
– embedded Container: basiert auf GlassFish; erlaubt normales Deployment, kann unabhängig vom realen System konfiguriert werden; nutzt gleiche JVM wie Tests; ist nah (aber nicht gleich) dem Zielsystem
– managed Container: läuft auf Ziel-Server; Arquillian muss sich damit verbinden (-> z. B. für Komponententests)
• hier: embedded Container, realen Server zum Testen stoppen, DB muss laufen
• Tests auch ohne Arquillian möglich, dazu von Hand set-Injection nutzen, Mocks erstellen, (Workarounds basteln)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
369
Arquillian - Start
• unsere Web-Applikationen bestehen aus WebArchive (war-Datei, gepackt), die festen Aufbau hat und u. a. jar-Dateien enthält
• nur war-Datei benötigt, wenn alle benutzten Bibliotheken im Server vorhanden (zentrales lib-Verzeichnis)
• Arquillian unterstützt den Bau solcher WebArchives für Applikationen (Shrinkwrap)
• wir nutzen gleiche Version des embedded GlassFish und des realen Servers (-> war-Datei kann direkt genutzt werden)
• folgende Folien zeigen nur Beispiele, die systematische aufbereitet werden müssten (z. B. Hilfsmethoden auslagern)
• Literatur: J. D. Ament, Arquillian Testing Guide, Packt Publishing, Birmingham, UK, 2013
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
370
Arquillian – Beispielnutzung (1/7)
@RunWith(Arquillian.class)
public class PersistenceTest {
@Inject
Persistence persist;
MitarbeiterDTO zuLoeschen;
@After
public void after(){
if (this.zuLoeschen != null){
this.persist.delete(this.zuLoeschen);
this.zuLoeschen = null;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
371
Arquillian – Beispielnutzung (2/7)
@Deployment
public static WebArchive createDeployment() {
WebArchive war = ShrinkWrap.createFromZipFile(
WebArchive.class
, new File("dist/kbseMitarbeiterverwaltung.war"));
System.out.println("war: " + war + " " + war.getContent());
return war;
}
Ausgabe: war: kbseMitarbeiterverwaltung.war: 17 assets
{BasicPath [context=/META-INF]=/META-INF, BasicPath
[context=/META-INF/MANIFEST.MF]=/META-INF/MANIFEST.MF,
BasicPath [context=/WEB-INF]=/WEB-INF, BasicPath
[context=/WEB-INF/classes]=/WEB-INF/classes, BasicPath
[context=/WEB-INF/classes/META-INF]=/WEB-INF/classes/META-
INF, BasicPath [context=/WEB-INF/classes/access]=/WEB-
INF/classes/access, BasicPath [context=/WEB-
INF/classes/controller]=/WEB-INF/classes/controller, Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
372
Arquillian – Beispielnutzung (3/7)@Test
public void ejbBeispielTest() {
List<MitarbeiterDTO> alt = this.persist.alleMitarbeiter();
String name = new Date().toString();
// minr wird ignoriert, da sie von JPA generiert wird
MitarbeiterDTO mi = new MitarbeiterDTO(42, name + "1"
, name + "2", "42", "");
this.persist.save(mi);
List<MitarbeiterDTO> neu = this.persist.alleMitarbeiter();
neu.removeAll(alt); // geht nur mit passendem equals
Assert.assertEquals("ein neues Objekt", 1, neu.size());
this.zuLoeschen = neu.get(0);
Assert.assertEquals("falscher Vorname"
, name + "1", neu.get(0).getVorname());
Assert.assertEquals("falscher Nachname"
, name + "2", neu.get(0).getNachname());
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
373
Arquillian – Beispielnutzung (4/7) - Hilfsmethode
// Idee von letzter Folie auslagern (gibt noch sicherere Wege
// nicht existierende Daten zu erzeugen)
public MitarbeiterDTO neuerMitarbeiter(){
List<MitarbeiterDTO> alt = this.persist.alleMitarbeiter();
String name = new Date().toString();
// minr wird ignoriert, da sie von JPA generiert wird
MitarbeiterDTO mi = new MitarbeiterDTO(42, name + "1"
, name + "2", "42", "");
this.persist.save(mi);
List<MitarbeiterDTO> neu = this.persist.alleMitarbeiter();
neu.removeAll(alt);
return neu.get(0);
}
// weitere Erkenntnis: wuerde persist zumindest die erzeugte
// minr zurueckliefern, waere Testerstellung einfacher
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
374
Arquillian – Beispielnutzung (5/7) – mehrere Nutzer@Inject
Persistence pers2;
@Test
public void testVerschiedenePersistenceObjekte(){
Assert.assertNotEquals(this.pers2, this.persist);
}
@Test
public void testZweiterLoeschtObjektDasErsterDannAktualisiert(){
MitarbeiterDTO mi = this.neuerMitarbeiter();
this.pers2.delete(mi);
try{
this.persist.update(mi);
Assert.fail("geloeschtes Element aktualisiert");
} catch (EJBException e){
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
375
Arquillian – Beispielnutzung (6/7)@Inject
Controller control;
@Test
public void testbeispielControllernutzung(){
List<MitarbeiterDTO> alt = this.control.alleMitarbeiter();
String name = new Date().toString();
MitarbeiterDTO mi = new MitarbeiterDTO(42, name + "1"
, name + "2", "42", "");
Resultat res = this.control.save(mi);
Assert.assertTrue(mi + " nicht persistiert",res.isErgebnis());
List<MitarbeiterDTO> neu = this.control.alleMitarbeiter();
neu.removeAll(alt);
Assert.assertEquals("ein neues Objekt", 1, neu.size());
this.zuLoeschen = neu.get(0);
Assert.assertEquals(name + "1", neu.get(0).getVorname());
Assert.assertEquals(name + "2", neu.get(0).getNachname());
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
376
Arquillian – Beispielnutzung (7/7)@Inject
MitarbeiterViewModel mv;
@Test
public void testbeispielViewnutzung(){
List<MitarbeiterDTO> alt = this.mv.getAlle();
String name = new Date().toString();
// minr wird ignoriert, da sie von JPA generiert wird
MitarbeiterDTO mi = new MitarbeiterDTO(42, name + "1"
, name + "2", "42", "");
this.mv.setMitarbeiter(mi); // wuerde der View fuellen
this.mv.uebernehmen();
List<MitarbeiterDTO> neu = this.mv.getAlle();
neu.removeAll(alt);
Assert.assertEquals("ein neues Objekt", 1, neu.size());
this.zuLoeschen = neu.get(0);
Assert.assertEquals(name + "1", neu.get(0).getVorname());
Assert.assertEquals(name + "2", neu.get(0).getNachname());
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
377
Anmerkung
• Generell wird mit jedem @Inject neues Objekt erzeugt
• Ausnahme bei der Sessionsteuerung
• über arquillian.xml konfigurierbar (außerhalb des Scopes dieser Einführungsveranstaltung)
• folgender Test scheitert deshalb:@Inject
MitarbeiterViewModel mv2;
@Test
public void testVerschiedeneViewModelObjekte(){
Assert.assertNotEquals(this.mv, this.mv2);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
378
Überdeckungsmessung (1/2)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
379
Überdeckungsmessung (2/2)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
380
Abschlussbeispiel: Traue keinen Überdeckungen
public int max(int x,
int y,
int z){
int max = 0;
if (x>z)
max = x;
if (y>x)
max = y;
if (z>y)
max = z;
return max;
}
0
1
2
3
int max = 0
if (x>z)
max = x
return max
4
5
6
7
max = y
max = z
if (y>x)
if (z>y)
• Erinnerung: Suche Maximum von drei ganzen Zahlen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
381
Anweisungsüberdeckung - jeder Knoten einmal
0
1
2
3
max = 0
if (x>z)
max = x
return
max
4
5
6
7
max = y
max = z
if (y>x)
if (z>y)
5 4
7 5
4 7
5
7
0
1
2
3
max = 0
if (x>z)
max = x
return
max
4
5
6
7
max = y
max = z
if (y>x)
if (z>y)
4 7
5 4
7 5
5
7
7 = max (5, 7, 4)
7
7 = max (4, 5, 7)
7
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
382
Zweigüberdeckung - jede Kante einmal
0
1
2
3
max = 0
if (x>z)
max = x
return
max
4
5
6
7
max = y
max = z
if (y>x)
if (z>y)
7 4
5 7
4 5
7
7
0
1
2
3
max = 0
if (x>z)
max = x
return
max
4
5
6
7
max = y
max = z
if (y>x)
if (z>y)
5 4
7 5
4 7
5
7
0
1
2
3
max = 0
if (x>z)
max = x
return
max
4
5
6
7
max = y
max = z
if (y>x)
if (z>y)
4 7
5 4
7 5
5
7
7 = max (7, 5, 4) 7 = max (5, 7, 4)
7
7 = max (4, 5, 7)
7
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
383
Fehler kann gefunden werden
• Kenntnisse über Äquivalenzklassen !
x>y=z y=z>x y>x=z x=z>y z>y=x y=x>z
z>y>x z>x>y y>z>x y>x>z x>z>y x>y>z x=y=z
• Kenntnisse über Datenflussanalyse finden Fehler!
• benötigt große Programmiererfahrung
• benötigt sehr strukturiertes Denken
• benötigt Forderung nach intensiven Tests (auch DO-178B)
• benötigt Werkzeuge zur Automatisierung
• benötigt Zeit und Geld
• benötigt Management mit Software-Verstand
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
384
Fazit: Äquivalenzklassen und Überdeckungen
• letztes Beispiel zeigt deutlich, dass man trotz systematischer Analyse Fehler übersehen kann !!!
• sinnvolles Vorgehen:
1. Testfälle aus typischen Verhalten ableiten
2. Testfälle aus Ausnahmesituationen ableiten
3. Äquivalenzklassen überlegen und kritische Fälle systematisch analysieren
4. Überdeckung messen, bei niedrigen Prozentzahlen zunächst das „warum“ ergründen, dann ggfls. Testfälle für Überdeckungen konstruieren
Nie, nie nur als zentrales Testziel 90 % - Überdeckung angeben, da dann Orientierung an Nutzerprozessen verloren geht
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
385
5. (RESTful) Web Services
• JavaScript Object Notation
• JSON-P
• Idee: Web-Services
• Idee: RESTful
• erste Services
• GET, POST
• Clients
• Response
• Path-Parameter
• Aufrufparameter
• Architektur von REST-Applikationen
• Fallstudie (GET, POST, PUT, DELETE)
• Ausblick
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
386
Ausblick auf weitere Themen
Komponentenbasierte Software-Entwicklung
Browser
Datenbank
JPA
EJBBean
Validation
Scope
JSFRESTful
WebService
Web Sockets
1
CDI
2
3
Prof. Dr. Stephan Kleuker
387
Einstieg JSON
• JavaScript Object Notation (http://json.org/)
• textuelles Austauschformat, abgeleitet aus JavaScript{ "name": "Tony Stark",
"alter": 42,
"firma": { "name": "Stark Industries",
"ort": "New York, N.Y"
},
"freunde":["Steve Rogers", "Bruce Banner"]
}
• Sammlung von
– (Name: Wert)-Paaren
– Arrays von Werten
• Werte können wieder aus beiden Elementen bestehen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
388
Vereinheitlichung von JSON in Java
in JEE 7 ergänzt:
• JSR 353: JavaTM API for JSON Processing (23.5.2013)
• JSR 374: JavaTM API for JSON Processing 1.1 (24.5.2017)
https://jcp.org/en/jsr/detail?id=374
• Referenzimplementierung JSON-P https://jsonp.java.net/
• in Glassfish seit 4.0 enthalten
zwei zentrale APIs
• Object Model API; sehr analog zum DOM API für XML parsing
• Streaming API; sehr analog zum StAX API
• unabhängig von Programmiersprachen nutzbar
• kompakter als XML (ähnlich gut/schlecht menschenlesbar)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
389
Beispiel: JSON-Object lesen (1/2)
public static void main(String[] args) {
String daten =
"{ \"name\": \"Tony Stark\","
+ " \"alter\": 42,"
+ " \"firma\": { \"name\": \"Stark Industries\","
+ " \"ort\": \"New York, N.Y\""
+ "},"
+ "\"freunde\":[\"Steve Rogers\", \"Bruce Banner\", 42]"
+ "}";
JsonReader reader = Json.createReader(new StringReader(daten));
JsonObject tony = reader.readObject();
reader.close();
//Set<String> namen = tony.keySet(); // geht auch
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
390
Beispiel: JSON-Objekt lesen (2/2)
System.out.println("Name : " + tony.getString("name"));
System.out.println("Alter : " + tony.getInt("alter"));
JsonObject firma = tony.getJsonObject("firma");
System.out.println("Firmenname : " + firma.getString("name"));
System.out.println("Umsatz : " + firma.getInt("umsatz", 20));
JsonArray freunde = tony.getJsonArray("freunde");
for (JsonValue freund : freunde) {
System.out.println(freund + " * " + freund.getValueType());
}
}
Name : Tony Stark
Alter : 42
Firmenname : Stark Industries
Umsatz : 20
Steve Rogers * STRING
Bruce Banner * STRING
42 * NUMBER
Default, wenn nicht da
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
391
Beispiel: JSON-Objekt von Hand erstellen
public static void main(String[] args) {
JsonObject personObject = Json.createObjectBuilder()
.add("name", "Bruce Banner")
.add("alter", 44)
.add("firma",
Json.createObjectBuilder()
.add("name", "Shield")
.add("ort", "unbekannt")
.build())
.add("freunde",
Json.createArrayBuilder()
.add("James Howlett")
.add("Ben Grimm")
.build())
.build();
System.out.println("Object: " + personObject);
}
Object:
{"name":"Bruce
Banner","alter":44,"f
irma":{"name":"Shield
","ort":"unbekannt"},
"freunde":["James
Howlett","Ben
Grimm"]}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
392
Ausschnitt Klassendiagramm
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
393
Beispiel: Stream-Bearbeitung von JSON
// daten: siehe JSON lesen
JsonParser parser = Json
.createParser(new StringReader(daten));
while (parser.hasNext()) {
Event event = parser.next();
System.out.print(event + ": ");
switch (event) {
case KEY_NAME:
System.out.print(parser.getString());
break;
case VALUE_NUMBER:
System.out.print(parser.getInt());
break;
}
System.out.println("");
}
START_OBJECT:
KEY_NAME: name
VALUE_STRING:
KEY_NAME: alter
VALUE_NUMBER: 42
KEY_NAME: firma
START_OBJECT:
KEY_NAME: name
VALUE_STRING:
KEY_NAME: ort
VALUE_STRING:
END_OBJECT:
KEY_NAME: freunde
START_ARRAY:
VALUE_STRING:
VALUE_STRING:
VALUE_NUMBER: 42
END_ARRAY:
END_OBJECT:Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
394
Binding
• Binding schafft automatische Umwandlungsmöglichkeit von A nach B und von B nach A
• ohne Binding muss die Umwandlung (marshalling) manuell erfolgen, bei Netztransport ggfls. Rückumwandlung notwendig (unmarshalling)
• Java-Objekt von und nach XML löst JAXB
• JSR 222: JavaTM Architecture for XML Binding (JAXB) 2.0, https://jcp.org/en/jsr/detail?id=222
• wichtig Umwandlungsprozess konfigurierbar
• Java-Objekt von und nach JSON noch nicht standardisiert (für JEE 8 angekündigt)
• Referenzimplementierung für Glassfish (Stand Ende 2013) ist MOXy (übersetzt JAXB-Annotationen nach JSON)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
395
Beispiel: Vorbereitung einer Entitäts-Klasse für JSON
@XmlRootElement
public class Punkt implements Serializable {
private int x;
private int y;
public Punkt() {} // wichtig
public Punkt(int x, int y) {this.x = x; this.y = y;}
public int getX() {return x;}
public int getY() {return y;}
public void setX(int x) {this.x = x;}
public void setY(int y) {this.y = y;}
@Override
public String toString() {return "[" + x + "," + y + "]";}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
396
Annotationen zur Steuerung der Übersetzung
@XmlElement(name=“rufname") // Key-Umbenennung
public String name;
@XmlTransient // nicht übertragen
public int alter;
• man beachte, dass man erhaltenes Objekt auch noch mit vorherigen Methoden modifizieren kann
• Übersetzung noch nicht standardisiert (aktuell MOXy, Teil von EclipseLink)
• da manuelle JsonObject-Erzeugung nicht sehr aufwändig und sehr flexibel, wird es gute Alternative bleiben
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
397
Hintergrund Web Services
• zentraler Wunsch: einfache Nutzung von Software über das Netz
• unabhängig wo sich ein Rechner befindet
• unabhängig von der Programmiersprache
SOAP-basierte WebServices
• jeder Service hat eindeutige Kennung (URI, Uniform Resource Identifier)
• Schnittstellenbeschreibung WSDL
• typisch: XML-basierte Kommunikationsprotokolle
• typisch: Verbindung mit SOA
• hier nicht wichtig, aber SOA ≠ SOAP ≠ Web Service
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
398
Hintergrund: Service Oriented Architecture
Service-
Verzeichnis
Service-
Anbieter
Service-
Nutzer
3. Anfragen
4. Antworten
SOAP
WSDL
HTTP
UDDI
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
399
Zwischenfazit SOA
• Vision: auf Grundlage von Geschäftsprozessmodellierungen kann individuelle Software für ein Unternehmen entstehen
• Realität: machbar, wenn alles auf einem Hersteller basiert
• Realität: UDDI hat in fast allen Projekten nicht stattgefunden (SOA ist auch Super Overhyped Acronym)
• aber: WebServices basierend auf SOAP haben als Kommunikationskonzept zentrale Bedeutung bekommen
• gilt als relativ langsam
• aber: Unternehmen nutzen es um MS-basierte Oberfläche mit JEE-realisiertem Server zu verbinden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
400
RESTful (Representational State Transfer)
• Idee von der Interaktion zwischen Rechnern bleibt
• REST ist ein Architekturstil für verteilte Hypermedia-Systeme
• Protokoll: nutze Möglichkeiten von HTTP
– GET: lese Information (SELECT)
– POST: neue Information (INSERT)
– PUT: ändere Information (UPDATE)
– DELETE: lösche Information (DELETE)
• Klammern deuten Ähnlichkeit zu Datenbankoperationen an
• Grundlage: Dissertation Roy Fielding „Architectural Styles and the Design of Network-based Software Architectures “
• http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
401
(An)forderungen zur REST-Nutzung
• Adressierbarkeit: Jede Ressource eindeutig über URL identifizierbar
• Ressourcenorientierung: alle Anfragen beziehen sich auf Ressourcen, URL kann verarbeitenden Dienst und Ress-Id enthalten
• flexible Darstellung: maschinell lesbar, z. B: JSON, XML, HTML
• Zustandsbasiert: Interaktion entspricht endlichen Automaten (Interaktion führt zu Folgezustand, weitere Schritte klar definiert)
• Statuslos: Alle Infos zur Bearbeitung in Anfrage enthalten
• Standardisiert: Zugriff über standardisierte Operatoren (hier: Get, Post, Put, Delete, Patch, Head, Options)
• Literatur: D. Abts, Masterkurs Client/Server-Programmierung in Java, 4. Auflage, Springer Vieweg, 2015
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
402
Woher kommt „ Representational State Transfer“
Client fordert Information mit Hilfe einer URL (URI) an.
Eine Repräsentation der Information zusammen mit möglichen Bearbeitungsschritten, wird als Ergebnis zurückgegeben (z. B. in Form eines JSON-Objekts), Client hat Informationszustand.
Client nutzt Hyperlinks in Ergebnis um weitere Informationen anzufordern.
Neues Ergebnis versetzt Client in einen neuen Informationszustand.
ResourceClient
http://www.scrumsprinter.de/sprint/42
{ “id”: 42,
“name”: “Prototyp”,
“elemente”: [ …
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
403
HATEOAS – saubere REST-Architektur
„Hypermedia as the Engine of Application State“
• Client kennt nur die Basis-URI des Dienstes
• Server leitet durch Informationszustände der Anwendung durch Bekanntgabe von Wahlmöglichkeiten (Hyperlinks), genauer: welche Folgeaufrufe sind wie möglich
• HTTP-Kommunikationsprotokoll selbst bleibt zustandslos
• Grundregel: GET, PUT, DELETE (auch PATCH, HEAD, OPTIONS) sind idempotent; führen zum gleichen Ergebnis, egal wie oft sie im gleichen Informationszustand aufgerufen werden
• häufig genutzter Trick: POST auch zur partiellen Aktualisierung
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
404
Richardson Maturity Model
• Bewertung inwiefern WebService REST-Bedingungen erfüllt
• Stufe 0: RESTless, nutzt URI, HTTP als Transportprotokoll für RPC-Aufrufe
• Stufe 1: Ressourcen, jede Ressource hat URI, diese wird zur direkten Ansprache genommen
• Stufe 2: HTTP-Verben, GET, POST, … werden in der von REST geforderten Form genutzt, Antworten mit HTTP-Statuscodes
• Stufe 3: RESTful - Hypermedia, zustandsbasiert, klare Information in welchem Zustand sich Bearbeitung befindet und welche nächsten Aktionen (mit Parameterbereichen) möglich sind
• Anmerkung: Diskutabel, aber oft liegt die Praxis zwischen Level 1 und 3, oft 2 (RESTbasiert)
• Literatur: z. B. https://martinfowler.com/articles/richardsonMaturityModel.html
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
405
HATEOAS: Beispiel
Komponentenbasierte Software-Entwicklung
z1
z2
b b
a
a
Prof. Dr. Stephan Kleuker
406
Wer nutzt es (Beispiele)?
• Hinweis: Öfter wird gegen die geforderte Reinform von RESTful WebServices verstoßen, und normale Anfragemöglichkeit mit GET als RESTful oder REST-basiert bezeichnet
• Google Maps
• Google AJAX Search API
• Yahoo Search API
• Amazon WebServices
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
407
Standardisierung in Java
viele Implementierungen
• Restlet http://www.restlet.org/
• Apache CXF http://cxf.apache.org/
• Project Zero http://www.projectzero.org
• GlassFish Jersey https://jersey.dev.java.net/ (Referenz)
• JBoss RESTeasy http://www.jboss.org/resteasy/
Standardisierung für Java:
• JSR 311: JAX-RS (10.10.2008)
• JSR 339: JAX-RS 2.0 (24.5.2013)
• JSR 370: JavaTM API for RESTful Web Services (JAX-RS 2.1), (22.8.2017), https://www.jcp.org/en/jsr/detail?id=370
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
408
JAX-RS aktivieren
• in JEE-aware Servern reicht folgendes ausimport javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("resources")
public class ApplicationConfig extends Application {
}
• statt „resources“ auch „“ möglich, wenn nicht gleichzeitig JSF genutzt wird („Pfade beißen sich“)
• ist generell im .war-File
• sonst Konfiguration als Servlet nötig
• Beschreibung in web.xml
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
409
JAX-RS aktivieren (Alternative)
@ApplicationPath("resources")
public class ApplicationConfig extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
try { // customize Jersey 2.0 JSON provider:
Class jsonProvider = Class
.forName("org.glassfish.jersey.moxy.json.MoxyJsonFeature");
resources.add(jsonProvider);
} catch (ClassNotFoundException ex) {}
addRestResourceClasses(resources);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(hello.HelloWorld.class);
}
}
Anbieter von Services
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
410
erste REST-basierte WebServices
@Path("/helloworld")
public class HelloWorld {
public HelloWorld() { }
@GET
@Produces("text/html")
public String getHtml() {
return "<html><body><h1>Hello, World!!</h1></body></html>";
}
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getText() {
return "Tach Welt";
}
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
411
detaillierte Analyse@Path("/helloworld")
• Gibt Aufrufpfad an, hier resources/helloworld
• Pfad wird an Projektpfad, z. B. /vlRESTAnfang, angehängt
• könnte auch nur an einzelnen Methoden stehen
• kann auch zusätzlich an Methoden stehen, so dass sich der Pfad verlängert
@GET
@Produces("text/html")
• Annotationen aus javax.ws.rs
• HTTP-Befehl und Ergebnistyp (mögliche Ergebnistypen, mehrere MIME-Typen [Multipurpose Internet Mail Extension])
• nachfolgender Methodenname spielt keine Rolle!
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
412
direkter Aufruf
• bei GET ist direkter Aufruf im Browser möglich
• aber, das ist ein sehr sehr untypisches Szenario
• typisch:
– Aufruf direkt aus einer Web-Seite, meist mit JavaScript
– Aufruf aus anderer Software heraus mit Mitteln der jeweiligen Programmiersprache (z. B. java.net.URL)
• NetBeans: kein Haken bei „Display Browser on Run“
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
413
Detaillierte Analyse mit cURL
• generell jedes Programm zur Erzeugung von HTTP-Aufrufen und Analyse der Ergebnisse geeignet
• Kommando-Zeile mit cURLhttp://curl.haxx.se/download.html
• Für etwaige Parameter muss auch URL in Anführungsstrichen stehen
• viele Browser unterstützen direkt bei solchen Tests
• weiteres Tool: Postman https://www.getpostman.com/Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
414
Nutzung automatischen Marshallings - GET
• verschiedene Rückgabetypen bedienbar (praktisch sinnvoll?)@GET
@Produces({MediaType.TEXT_XML, MediaType.APPLICATION_JSON})
public Punkt getJSon2() {
return new Punkt(42,43); // war @XMLRootElement annotiert
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
415
Nutzung automatischen Unmarshallings - POST
@POST
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_JSON)
public String postit(Punkt p){
System.out.println(p);
return "ok";
}
• weitere Parameter im JSON-Objekt führen zu Fehlern
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
416
zentrale Klassen Client und Response
• RESTful Web Services werden typischerweise aus anderer Software aufgerufen
• dies ist natürlich auch in Java möglich; vor JAX-RS 2.0 aber proprietäre Lösungen der Anbieter
• https://jersey.java.net/download.html
• jetzt Klasse javax.ws.rs.client.Client
• Bei der Nutzung von RESTful Web Services können verschiedene Klassen als Typen für Parameter und Rückgabe genutzt werden
• Hilfreich ist Klasse javax.ws.rs.core.Response
• Server erzeugt Response-Objekt
• Client kann problemlos Response-Objekt lesen
• Response ist ein Stream, muss auch geschlossen werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
417
Hilfsmethode zur genaueren Analyse von Response
private void details(Response res) {
System.out.println("-----------------\n"
+ "AllowedMethods : " + res.getAllowedMethods() + "\n"
+ "Entity Class: " + res.getEntity().getClass() + "\n"
+ "Language : " + res.getLanguage() + "\n"
+ "Location : " + res.getLocation() + "\n"
+ "Mediatype : " + res.getMediaType() + "\n"
+ "Links : " + res.getLinks() + "\n"
+ "Status : " + res.getStatus() + "\n"
+ "Date : " + res.getDate() + "\n"
+ "Class : " + res.getClass() + "\n"
+ "Inhalt : " + res.readEntity(String.class));
res.close();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
418
Kleine Beispiele (1/7)
• Anmerkung: Zeigt Service-Nutzung, zeigt nichts von RESTpublic class ClientAnalyse {
private Client client;
private WebTarget userTarget;
public ClientAnalyse() {
Client client = ClientBuilder.newClient();
userTarget = client
.target("http://localhost:8080/vlRESTAnfang"
+ "/resources/helloworld");
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
419
Kleine Beispiele (2/7)
public void analyse1() {
Response res = userTarget.request("text/html").get();
details(res);
}
AllowedMethods : []
Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$2
Language : null
Location : null
Mediatype : text/html
Links : []
Status : 200
Date : Fri Dec 18 15:39:22 CET 2015
Class : class org.glassfish.jersey.client.InboundJaxrsResponse
Inhalt : <html lang="en"><body><h1>Hello, World!!</h1></body></html>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
420
Kleine Beispiele (3/7)
public void analyse1() {
Response res = userTarget.request(MediaType.TEXT_PLAIN).get();
details(res);
}
AllowedMethods : []
Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$2
Language : null
Location : null
Mediatype : text/plain
Links : []
Status : 200
Date : Wed May 14 18:55:35 CEST 2014
Class : class org.glassfish.jersey.client.ScopedJaxrsResponse
Inhalt : <html lang="en"><body><h1>Hello, World!!</h1></body></html>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
421
Kleine Beispiele (4/7)
public void analyse3() {
Response res = userTarget
.request(MediaType.APPLICATION_JSON).get();
details(res);
}
AllowedMethods : []
Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$1
Language : null
Location : null
Mediatype : application/json
Links : []
Status : 200
Date : Wed May 14 18:55:35 CEST 2014
Class : class org.glassfish.jersey.client.ScopedJaxrsResponse
Inhalt : {"x":42,"y":43}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
422
Kleine Beispiele (5/7)
public void analyse4() {
Response res = userTarget.request(MediaType.TEXT_XML).get();
details(res);
}
AllowedMethods : []
Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$1
Language : null
Location : null
Mediatype : text/xml
Links : []
Status : 200
Date : Wed May 14 19:08:13 CEST 2014
Class : class org.glassfish.jersey.client.ScopedJaxrsResponse
Inhalt : <?xml version="1.0" encoding="UTF-8"
standalone="yes"?><punkt><x>42</x><y>43</y></punkt>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
423
Kleine Beispiele (6/7)
public void analyse5() {
Builder buil = this.userTarget.request(MediaType.TEXT_PLAIN);
Entity e = Entity.entity(new Punk(3, 4)
, MediaType.APPLICATION_JSON);
System.out.println(e + " : " + e.getEntity());
String res = buil.post(e, String.class);
System.out.println(res);
}
javax.ws.rs.client.Entity@52aa911c : [3,4]
ok
• Anmerkung: Klasse Punk wie Punkt, sogar ohne XMLRootElement-Annotation , aber Serializable
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
424
Kleine Beispiele (7/7)
public void analyse6() {
Builder buil = this.userTarget.request(MediaType.TEXT_PLAIN);
Entity e = Entity.json(new Punk(2,3));
System.out.println(e + " : " + e.getEntity());
String res = buil.post(e, String.class);
System.out.println(res);
}
Entity{entity=[2,3], variant=Variant[mediaType=application/json,
language=null, encoding=null], annotations=[]} : [2,3]
ok
• Klasse Entity bietet einige Marshalling-Methoden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
425
flexible Dienststrukturen
• generell soll man aus Antworten auf weitere Abfragemöglichkeiten schließen können
• /helloworld/kunden/
Frage nach Kunden: Sammlung der Namen aller Kunden
• /helloworld/kunden/Hoeness/
Frage nach Kunden mit Namen: alle Eigenschaften des Kunden
• /helloworld/kunden/Hoeness/konten
Frage nach Konten eines benannten Kunden: Sammlung aller Konten des Kunden
• /helloworld/kunden/Hoeness/konten/42
Frage nach Kontonummer eines benannten Kunden: alle Eigenschaften des Kontos dieses Kunden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
426
Beispiel: Umsetzung von Pfaden (1/4)
@Path("helloworld")
public class HelloWorld {
// Kundenname, Sammlung von Konten (Nummer, Betrag)
private Map<String, Map<Integer, Long> > kunden;
public HelloWorld() {
// zufaellige Beispieldaten
Map<Integer,Long> tmp = new HashMap<>();
tmp.put(42,32000000L);
kunden = new HashMap<>();
kunden.put("Hoeness", tmp);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
427
Beispiel: Umsetzung von Pfaden (2/4)
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/kunden/{user}/konten/{id}")
public JsonObject getKontostand(
@PathParam("user") String user
, @PathParam("id") int id) {
JsonObjectBuilder erg = Json.createObjectBuilder();
Map<Integer,Long> kunde = kunden.get(user);
if(kunde == null){
return erg.add("fehler", "kein Kunde").build();
}
Long summe = kunde.get(id);
if(summe == null){
return erg.add("fehler", "kein Konto").build();
}
return erg.add("summe", summe).build();
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
428
Beispiel: Umsetzung von Pfaden (3/4)
public static void main(String[] a){
String[] verdaechtig = {"Rummenigge", "Hoeness"};
int[] nummern = {42,43};
Client client = ClientBuilder.newClient();
for(String v:verdaechtig){
for (int n:nummern){
WebTarget target = client.target("http://localhost:8080"
+ "/vlRESTAnfang/resources/helloworld/kunden/"
+ v + "/konten/" + n);
JsonObject erg = target
.request(MediaType.APPLICATION_JSON)
.get(JsonObject.class);
System.out.println(erg);
}
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
429
Beispiel: Umsetzung von Pfaden (4/4)
{"fehler":"kein Kunde"}
{"fehler":"kein Kunde"}
{"summe":32000000}
{"fehler":"kein Konto"}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
430
Umsetzung von Pfaden
@Path("/kunden/{user}/konten/{id}")
• Einbau von Pfadvariablen, auf die in Parameterliste mit @PathParam("user") zugegriffen werden kann
• einfache Java-Typen, typischerweise int, long, String nutzbar; Konvertierung automatisch
• Pfadvariablen in der Klassenannotation können dann in jedem Methodenkopf genutzt werden
• Pfadvariablen können in @Path doppelt vorkommen und müssen dann gleichen Wert bei Nutzung haben
• im Hinterkopf: wenn HTTPS, dann auch User-Token so übertrag- und später prüfbar (Sicherheit)
• im Hinterkopf: individueller Wert für jeden Nutzer, der E-Mail mit so einem Link erhält
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
431
Externer Service zur Analyse von IPs (1/4)
private static void zeigeJsonObjekt(JsonObject js){
for(String key:js.keySet()){
System.out.println(key+ ": " + js.get(key));
}
}
public static void main(String[] s){
String SERVICE = "http://freegeoip.net/json";
Client client = ClientBuilder.newClient();
WebTarget wt = client.target(SERVICE);
Invocation.Builder invoc = wt.request();
JsonObject ergebnis = invoc.get(JsonObject.class);
zeigeJsonObjekt(ergebnis);
zeigeJsonObjekt(client.target(SERVICE+"/www.bild.de")
.request().get(JsonObject.class));
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
432
Externer Service zur Analyse von IPs (2/4)
ip: "84.155.86.93"
country_code: "DE"
country_name: "Germany"
region_code: "NI"
region_name: "Lower Saxony"
city: "Neuenkirchen"
zip_code: "49586"
time_zone: "Europe/Berlin"
latitude: 52.4167
longitude: 7.85
metro_code: 0
ip: "72.247.9.43"
country_code: "US"
country_name: "United States"
region_code: "MA"
region_name: "Massachusetts"
city: "Cambridge"
zip_code: "02142"
time_zone: "America/New_York"
latitude: 42.3626
longitude: -71.0843
metro_code: 506
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
433
Externer Service zur Analyse von IPs (3/4)
public static void main(String[] st){
Client client = ClientBuilder.newClient();
WebTarget wt = client.target("http://freegeoip.net/json");
Invocation.Builder invoc = wt.request();
Response ergebnis = invoc.get();
System.out.println(ergebnis);
ergebnis.bufferEntity(); // sonst Fehler bei 42
System.out.println(ergebnis.getEntity());
for(String s:ergebnis.getHeaders().keySet()){
System.out.println(s +": " + ergebnis.getHeaders().get(s));
}
System.out.println(ergebnis.readEntity(JsonObject.class));
System.out.println(ergebnis.getEntity().getClass()); //42
ergebnis.close();
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
434
Externer Service zur Analyse von IPs (4/4)
ScopedJaxrsResponse{ClientResponse{method=GET,
uri=http://freegeoip.net/json, status=200, reason=OK}}
java.io.ByteArrayInputStream@6d420a24
Date: [Wed, 14 May 2014 17:48:10 GMT]
Access-Control-Allow-Origin: [*]
Content-Length: [222]
Content-Type: [application/json]
{"ip":"93.196.192.46","country_code":"DE","country_name":"Germany","
region_code":"07","region_name":"Nordrhein-
Westfalen","city":"Hopsten","zipcode":"","latitude":52.3833,"longitu
de":7.6167,"metro_code":"","area_code":""}
class java.io.ByteArrayInputStream
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
435
Übergabe von Aufrufparametern (1/2)
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/rechnen")
public JsonObject machMathe(
@QueryParam("op1") int op1,
@QueryParam("op2") int op2,
@DefaultValue("plus")
@QueryParam("operator") String operator) {
JsonObjectBuilder erg = Json.createObjectBuilder();
if(operator.equals("minus")){
return erg.add("operator", operator)
.add("ergebnis", (op1-op2)).build();
}
return erg.add("operator", "plus")
.add("ergebnis", (op1+op2)).build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
436
Übergabe von Aufrufparametern (2/2)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
437
Dienstnutzung mit Aufrufparametern (1/2)
public static void main(String[] s) {
String SERVICE
= "http://maps.googleapis.com/maps/api/geocode/json";
Client client = ClientBuilder.newClient();
WebTarget wt = client.target(SERVICE +
"?address=Quakenbrueck&sensor=false");
Invocation.Builder invoc = wt.request();
JsonObject ergebnis = invoc.get(JsonObject.class);
System.out.println(ergebnis);
JsonObject details = ((JsonArray)ergebnis.get("results"))
.getJsonObject(0);
JsonObject position= (JsonObject)
((JsonObject)details.get("geometry")).get("location");
System.out.println(position);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
438
Dienstnutzung mit Aufrufparametern (2/2)
{"results":[{"address_components":[{"long_name":"Quakenbrück","
short_name":"Quakenbrück","types":["locality","political"]},{"l
ong_name":"Lower
Saxony","short_name":"NDS","types":["administrative_area_level_
1","political"]},{"long_name":"Germany","short_name":"DE","type
s":["country","political"]}],"formatted_address":"Quakenbrück,
Germany","geometry":{"bounds":{"northeast":{"lat":52.6967289,"l
ng":8.0344312},"southwest":{"lat":52.65917049999999,"lng":7.903
767999999999}},"location":{"lat":52.675599,"lng":7.950777699999
999},"location_type":"APPROXIMATE","viewport":{"northeast":{"la
t":52.6967289,"lng":8.0344312},"southwest":{"lat":52.6591704999
9999,"lng":7.903767999999999}}},"place_id":"ChIJqfqve3Zpt0cRIqf
jZXu8LGw","types":["locality","political"]}],"status":"OK"
{"lat":52.675599,"lng":7.950777699999999}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
439
UriInfo (1/2)
@Path("ana")
@Stateless
public class Analyse {
@Context
private UriInfo uriInfo;
private final static Logger LOGGER = Logger
.getLogger(Analyse.class.getSimpleName());
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getText() {
LOGGER.info("in getText");
LOGGER.info(this.uriInfo.getAbsolutePath().toString());
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
440
UriInfo (2/2)
LOGGER.info(this.uriInfo.getPath());
LOGGER.info(this.uriInfo.getRequestUri().toString());
for (String s:this.uriInfo.getQueryParameters().keySet()){
LOGGER.info(s+ ": "
+ this.uriInfo.getQueryParameters().get(s));
}
return "hai";
}
INFO: in getText
INFO: http://localhost:8080/resources/ana
INFO: /ana
INFO: http://localhost:8080/resources/ana?x=Hai&text=42
INFO: text: [42]
INFO: x: [Hai]
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
441
Aufgabe
• Ergänze Mitarbeiterverwaltung um REST-Schnittstelle
• setze auf Basis-Architektur auf
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
442
Ergänzungen (1/3)
• Klassen zum Ergebnisaustausch JSON-fähig machen (Annotation oder von Hand)
• typischerweise toJsonObject(): JSsonObject, ggfls. auch Rückwandlung
• z. B. in MitarbeiterDTO:
public JsonObject toJSonObject() {
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("minr", this.getMinr())
.add("vorname", this.getVorname())
.add("nachname", this.getNachname())
.add("alter", this.getAlter())
.add("befehl", this.befehl); // hier ignorieren
return js.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
443
Ergänzungen (2/3)
• in Resultatpublic JsonObject toJSonObject() {
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("ergebnis", this.isErgebnis())
.add("kommentar", this.getKommentar());
return js.build();
}
• REST einrichten (Pfad „r“ nur, da er hier nicht leer sein soll, da zusammen mit JSF Applikation installiert, die bei Nutzung des einfachen Pfads aufgerufen wird)
@ApplicationPath("r")
public class ApplicationConfig extends Application { }
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
444
Ergänzungen (3/3)
• Methode wäre auch in MitarbeiterDTO sinnvoll
• hier in MitarbeiterRESTBoundary Null-Prüfung möglichprivate JsonObject toJSonObject(MitarbeiterDTO m) {
if (m == null) {
return null;
}
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("minr", m.getMinr())
.add("vorname", m.getVorname())
.add("nachname", m.getNachname())
.add("alter", m.getAlter())
.add("link", uriInfo.getAbsolutePathBuilder()
.build().getPath());
return js.build();
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
445
REST-Schnittstelle (1/5)
@Stateless // oder @Singleton
@Path("")
public class MitarbeiterRESTBoundary {
@Inject
private Controller controller;
@Context
private UriInfo uriInfo;
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/mitarbeiter/{minr}")
public JsonObject mitarbeiter(@PathParam("minr") int minr) {
return this.toJSonObject(this.controller.mitarbeiter(minr));
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
446
REST-Schnittstelle (2/5)
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/mitarbeiter")
public JsonObject alleMitarbeiter() {
List<MitarbeiterDTO> alle = this.controller.alleMitarbeiter();
JsonArrayBuilder elemente = Json.createArrayBuilder();
alle.forEach(m -> elemente.add(this.toJSonObject(m)));
return Json.createObjectBuilder()
.add("mitarbeiter", elemente)
.add("self", uriInfo.getAbsolutePathBuilder()
.path("/").build().getPath())
.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
447
REST-Schnittstelle (3/5)
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/mitarbeiter")
public JsonObject hinzufuegen(JsonObject jo) {
MitarbeiterDTO ma = new MitarbeiterDTO(0
,jo.getString("vorname")
, jo.getString("nachname"), "","");
return this.controller.save(ma).toJSonObject();
}
// hier wird das Protokoll fuer den Nutzer festgelegt,
// es wird JSON-Objekt mit den angegebenen Attributen gefordert
// (Exception, wenn nicht vorhanden; besser behandeln)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
448
REST-Schnittstelle (4/5)
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/mitarbeiter/{minr}")
public JsonObject aktualisieren(@PathParam("minr") int minr
, JsonObject jo) {
MitarbeiterDTO ma = this.controller.mitarbeiter(minr);
if (ma == null){
return (new Resultat(false, "kein Mitarbeiter mit minr "
+ minr)).toJSonObject();
}
ma.setVorname(jo.getString("vorname"));
ma.setNachname(jo.getString("nachname"));
return this.controller.update(ma).toJSonObject();
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
449
REST-Schnittstelle (5/5)
@DELETE
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/mitarbeiter/{minr}")
public JsonObject loeschen(@PathParam("minr") int minr) {
MitarbeiterDTO ma = this.controller.mitarbeiter(minr);
if (ma == null){
return (new Resultat(false
, "Mitarbeiter gelöscht (nicht existent)" + minr))
.toJSonObject();
}
return this.controller.delete(ma).toJSonObject();
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
450
Beispiel-Client (1/7)
• Client kann in beliebiger Sprache geschrieben sein
• Hier Beispiel mit Wiederverwendung, Frontend aus Mitarbeiterprojekt wird wieder genutzt, neue Controllerschicht, die REST nutzt
• Client schreibt eigene Mitarbeiterklasse (hier vereinfachend übernommen)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
451
Beispiel-Client (2/7)
@Dependent
public class ControllerRESTFrontend implements Serializable {
private final static String MITARBEITER
= "http://localhost:8080/kbseMitarbeiterverwaltung"
+ "/r/mitarbeiter";
private Client client;
private WebTarget wt;
@PostConstruct
public void init() {
this.client = ClientBuilder.newClient();
this.wt = client.target(MITARBEITER);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
452
Beispiel-Client (2/7)
@Dependent
public class ControllerRESTFrontend implements Serializable {
private final static String MITARBEITER
= "http://localhost:8080/kbseMitarbeiterverwaltung"
+ "/r/mitarbeiter";
private Client client;
private WebTarget wt;
@PostConstruct
public void init() {
this.client = ClientBuilder.newClient();
this.wt = client.target(MITARBEITER);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
453
Beispiel-Client (3/7)public List<MitarbeiterDTO> alleMitarbeiter() {
Invocation.Builder build = this.wt
.request(MediaType.APPLICATION_JSON);
JsonObject ergebnis = build.get(JsonObject.class);
JsonArray array = ergebnis.getJsonArray("mitarbeiter");
List<MitarbeiterDTO> alle = new ArrayList<>();
for (JsonValue val : array) {
JsonObject js = (JsonObject) val;
MitarbeiterDTO tmp = new MitarbeiterDTO();
tmp.setAlter(js.getString("alter"));
tmp.setMinr(js.getInt("minr"));
tmp.setNachname(js.getString("nachname"));
tmp.setVorname(js.getString("vorname"));
alle.add(tmp);
}
return alle;
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
454
Beispiel-Client (4/7)
public String save(MitarbeiterDTO m) {
Invocation.Builder build = this.wt
.request(MediaType.APPLICATION_JSON);
Entity entity = Entity.entity(this.toJSonObject(m),
MediaType.APPLICATION_JSON);
try {
JsonObject ergebnis = build.post(entity
, JsonObject.class);
return ergebnis.getString("kommentar");
} catch (Exception e) {
return e.toString();
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
455
Beispiel-Client (5/7)
public String update(MitarbeiterDTO m) {
WebTarget wt = client.target(MITARBEITER + "/" + m.getMinr());
Invocation.Builder build = wt
.request(MediaType.APPLICATION_JSON);
Entity entity = Entity.entity(this.toJSonObject(m)
, MediaType.APPLICATION_JSON);
try {
JsonObject ergebnis = build.put(entity, JsonObject.class);
return ergebnis.getString("kommentar");
} catch (Exception e) {
return e.toString();
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
456
Beispiel-Client (6/7)
public String delete(MitarbeiterDTO m) {
WebTarget wt = client.target(MITARBEITER + "/" + m.getMinr());
Invocation.Builder build = wt
.request(MediaType.APPLICATION_JSON);
try {
JsonObject ergebnis = build.delete(JsonObject.class);
return ergebnis.getString("kommentar");
} catch (Exception e) {
return e.toString();
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
457
Beispiel-Client (7/7)
public MitarbeiterDTO mitarbeiter(int minr) {
WebTarget wt = client.target(MITARBEITER + "/" + minr);
Invocation.Builder build = wt
.request(MediaType.APPLICATION_JSON);
try {
JsonObject jo = build.get(JsonObject.class);
return MitarbeiterDTO.toObject(jo);
} catch (Exception e) {
return null;
}
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
458
Variante JavaScript-Client (1/10)
• JavaScript zentrale Sprache in den Browsern
• ursprünglich nur für kleine Aufgaben konzipiert (was man immer spürt)
• Standardisiert (ECMA-Script)
• Standard von keinem Browser konsequent eingehalten; es werden Browser-Switches benötigt
• Bibliotheken können Browser-Abhängigkeit teilweise wegkapseln
• viele sehr kreative, oft inkompatible (oft auch mit sich selbst in Versionssprüngen) Bibliotheken
• fast saubere Architektur möglich, leider selten
• hier nur sehr naiver Code ohne Tricks als Fallstudie
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
459
Variante JavaScript-Client (2/10)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
460
Variante JavaScript-Client (3/10) – index.html (1/2)<!DOCTYPE html>
<html>
<head>
<title>REST Client für Mitarbeiter</title>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<script type="text/javascript" src="js/ws.js"></script>
</head>
<body>
<form name="felder">
<span id="pminr">Mitarbeiternummer wird vom
System vergeben</span>
<input id="minr" type="hidden">
<table border='0'>
<tr>
<td>Vorname:</td>
<td><input type="text" id="vorname"></td>
</tr>Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
461
Variante JavaScript-Client (4/10) – index.html (2/2)<tr>
<td>Nachname:</td>
<td><input type="text" id="nachname"></td>
</tr>
<tr>
<td><input type="button" value="uebernehmen"
onClick="sende();"> </td>
<td><input type="button" id="abbrechen"
value="abbrechen" onClick="abbruch();"
style=" display: none;"></td>
</tr>
</table>
<span id="kommentar"></span><br>
<div id="mitarbeiter" style="background-color: white;
margin:5px;">
</div>
</form>
</body>
</html>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
462
Variante JavaScript-Client (5/10) window.onload = function () {
laden();
}
PFAD =
"http://localhost:8080/kbseMitarbeiterverwaltung/r/mitarbeiter/";
function laden(){
var xhttp = new XMLHttpRequest();
xhttp.open("GET", PFAD, false);
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.send();
var response = JSON.parse(xhttp.responseText);
document.getElementById("mitarbeiter").innerHTML
= table(response.mitarbeiter);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
463
Variante JavaScript-Client (6/10) function table(daten) {
data = daten;
var erg = "<table border='0'>";
erg += "<tr><th>Minr</th><th>Vorname</th><th>Nachname</th>";
erg += "<th>Alter</th><th></th></tr>";
for (var i = 0; i < data.length; i++) {
erg += "<tr>";
erg += "<td>" + data[i].minr + "</td>";
erg += "<td>" + data[i].vorname + "</td>";
erg += "<td>" + data[i].nachname + "</td>";
erg += "<td>" + data[i].alter + "</td>";
erg += "<td><input type='button' value='editieren' "
+ "onClick='edit(" + i + ");'></td>";
erg += "<td><input type='button' value='loeschen' "
+ "onClick='loeschen(" + i + ");'></td>";
erg += "</tr>";
}
erg += "</table>";
return erg;
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
464
Variante JavaScript-Client (7/10) function sende() {
var xhttp = new XMLHttpRequest();
var ma = {
vorname: document.getElementById('vorname').value,
nachname: document.getElementById('nachname').value,
minr: 0
};
// wenn „abbrechen“ sichtbar, dann ist es Aktualisierung
if (document.getElementById("abbrechen").style.display
=='block'){
ma.minr = parseInt(document.getElementById('minr').value, 10);
xhttp.open("PUT"
, PFAD + ma.minr
, false);
} else {
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
465
Variante JavaScript-Client (8/10) } else {
xhttp.open("POST"
, PFAD
, false);
}
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.send(JSON.stringify(ma));
var response = JSON.parse(xhttp.responseText);
document.getElementById("kommentar").innerHTML
= response.kommentar;
zuruecksetzen();
laden();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
466
Variante JavaScript-Client (9/10) function abbruch() {
document.getElementById("abbrechen").style.display ='none';
zuruecksetzen();
}
function loeschen(i){
var xhttp = new XMLHttpRequest();
xhttp.open("DELETE"
, PFAD+data[i].minr
, false);
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.send();
var response = JSON.parse(xhttp.responseText);
laden();
document.getElementById("mitarbeiter").innerHTML
= table(response.mitarbeiter);
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
467
Variante JavaScript-Client (10/10) function zuruecksetzen(){
document.getElementById('vorname').value = '';
document.getElementById('nachname').value = '';
document.getElementById("abbrechen").style.display ='none';
document.getElementById("pminr").innerHTML
='Mitarbeiternummer wird vom System vergeben';
}
function edit(i) {
document.getElementById("minr").value = data[i].minr;
document.getElementById("pminr").innerHTML
= "Mitarbeiternummer: " + data[i].minr;
document.getElementById("vorname").value = data[i].vorname;
document.getElementById("nachname").value = data[i].nachname;
document.getElementById("abbrechen").style.display ='block';
document.getElementById("kommentar").innerHTML ='';
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
468
Vorherige Lösung ist schlechtes JavaScript
• Klasse XMLHttpRequest ist eine elementarer Baustein von Single-Page-Applications
• Nutzung erfolgt aktuell synchron, d. h. warten auf Ergebnis
• besser asynchron (asynchrones JavaScript und XML JSON)
• Projektaufbau davon unabhängig
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
469
Skizze – Umstellung auf asynchron (CallBack)
function laden(){
var xhttp = new XMLHttpRequest();
xhttp.open("GET"
, PFAD
, true); // true fuer asynchron
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var response = JSON.parse(this.responseText);
document.getElementById("mitarbeiter").innerHTML
= table(response.mitarbeiter);
} // Fehlerbehandlung fuer anderen this.status sinnvoll
};
xhttp.send();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
470
WADL (1/3)
• Web Application Description Language
• XML-basierte Beschreibung angebotener Dienste
• generell soll HTTP-Befehl OPTIONS genutzt werden, um Übersicht zu erhalten
• Alle möglichen Dienste mit Parametern werden aufgeführt
• Dienstbeschreibungen können aus Annotation generiert werden
• Alternativ kann @OPTIONS-annotierte Methode realisiert werden (z. B. um Ausgabe zu verhindern)
• Bedeutung eher gering, für Werkzeuge basierend auf WADL-Services interessant; erkennen so Aktualisierungen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
471
WADL (2/3) - Beispielmethode
@GET
@Produces("text/html")
public String getHtml() {
return "<html><body>Hello, World!!</body></html>";
}
<resources base="http://localhost:8080/resources/">
<resource path="helloworld">
<method id="getHtml" name="GET">
<response>
<representation mediaType="text/html"/>
</response>
</method>
...
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
472
WADL (3/3) – Beispiel aus Sprinter
<resources base="http://localhost:8080/Sprinter/resources/">
<resource path="sprints">
<method id="getSprints" name="GET">
<request>
<param xmlns:xs="http://www.w3.org/2001/XMLSchema"
name="von"
style="query" type="xs:int" default="-1"/>
<param xmlns:xs="http://www.w3.org/2001/XMLSchema"
name="bis"
style="query" type="xs:int" default="-1"/>
</request>
<response>
<representation mediaType="application/json"/>
</response>
</method>
<method id="hinzufuegen" name="POST">
<request>
<representation mediaType="application/json"/>
</request>
<response>
<representation mediaType="application/json"/>
</response>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
473
@FormParam
<form action="http://vfl.de/mitglieder" method="post">
<p>
Vorname: <input type="text" name="vorname"><br>
Nachname: <input type="text" name="nachname"><br>
<input type="submit" value="Send">
</p>
</form>
@Path("/mitglieder")
@Consumes(Mediatype.APPLICATION_FORM_URLENCODED)
public class CustomerResource {
@POST
public void createCustomer(
@FormParam(“vorname") String vorname
, @FormParam(“nachname") String nachname) {
...
}
ermöglicht die Übernahme von Parametern einer POST-Anfrage eines HTML-Formulars
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
474
Response.Status (gibt evtl. passende Exceptions)public enum Status {
OK(200, "OK"), CREATED(201, "Created"),
ACCEPTED(202, "Accepted"),
NO_CONTENT(204, "No Content"),
MOVED_PERMANENTLY(301, "Moved Permanently"),
SEE_OTHER(303, "See Other"),
NOT_MODIFIED(304, "Not Modified"),
TEMPORARY_REDIRECT(307, "Temporary Redirect"),
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
NOT_ACCEPTABLE(406, "Not Acceptable"),
CONFLICT(409, "Conflict"), GONE(410, "Gone"),
PRECONDITION_FAILED(412, "Precondition Failed"),
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
SERVICE_UNAVAILABLE(503, "Service Unavailable");
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
475
Weiterführend (1/2)
• asynchron@POST
@Asynchronous public void bearbeite(
@Suspended AsyncResponse ar, Daten daten)
• reguläre Ausdrücke in Path, @Path("{id : .+}")
komplexe Auswertungsregeln, was, wenn mehrere Möglichkeiten an Pfaden existieren
• HEAD: nimmt typischerweise erste GET und gibt statt Ergebnis nur Header und Response-Code zurück
• MIME-Types können sehr detailliert sein, generelltype/subtype;name=value;name=value...
@Consumes("application/xml;charset=utf-8")
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
476
Weiterführend (2/2)
• JAX-RS-Annotationen können auch nur in Interfaces ausgelagert werden
• Matrix-Parameter (Attribute) behandelbarhttp://beispiel.spieler.de/vfl;typ=Sturm/2015
• Nutzung von Header-Parametern @HeaderParampublic String get(@HeaderParam("Referrer") String
aufrufer) {
public String get(@Context HttpHeaders headers) {
• Cookie-Nutzung public String get(@CookieParam(“minr") int minr)
• genauere Analyse vom ResponseBuilder.status(.)
• Einbindung von Bean Validation
• …
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
477
Literatur
• (Standard-Links sind im Text)
• [Bur14] B. Burke, RESTful Java with JAX-RS 2.0, O‘Reilly, Sebastopol (CA), USA, 2014
• http://www.oracle.com/technetwork/articles/java/jaxrs20-1929352.html
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
478Komponentenbasierte Software-Entwicklung
6. Advanced JSF
6.1 Templates und Komponenten
6.2 Nutzung von Ajax
6.3 Testen von Web-Applikationen - Selenium
6.4 JSF-Erweiterungen
Prof. Dr. Stephan Kleuker
479Komponentenbasierte Software-Entwicklung
6.1 Templates und Komponenten
Facelets - Motivation
• entwickelt, um JSP als View Declaration Language zu ersetzen
• Template-Ansatz um Wiederverwendung zu ermöglichen und Redundanzen zu vermeiden
• ein Ziel: Zusammensetzung einer logischen Seite aus verschiedenen Dateien unter Nutzung von Parametern
• JSF-View in XHTML-Syntax [nutzen wir immer]
• Erweiterungsmöglichkeit mit tag-Libraries (vgl. JSTL für JSP)
– Anmerkung: Vermeiden Sie möglichst die Nutzung der JSTL
• MVC-Ansatz, kein Java-Code in XHTML möglich (und nötig)
Hinweis: Chrome ab und zu bemerkenswert langsam
Prof. Dr. Stephan Kleuker
480Komponentenbasierte Software-Entwicklung
Erstes Template-Beispiel (1/5) - Template rahmen.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<h:head>
<h:graphicImage library="bilder" name="boah.jpg" width="200"
height="40" alt="boah"/>
<title>
<ui:insert name="titel"/>
</title>
</h:head>
<h:body>
<h:form>
<ui:insert name="rumpf"/>
<br/> <br/>
<h:commandLink value="Zurück" action="#{bsp.zurueck}"/>
</h:form>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
481Komponentenbasierte Software-Entwicklung
Erstes Template-Beispiel (2/5) - Bean
@Named
@RequestScoped
public class Bsp {
public String getErgebnis(){
return "Du solltest Dich ändern";
}
public String bad(){
return "/analyse.xhtml";
}
public String good(){
return "/analyse.xhtml";
}
public String zurueck(){
return "/index.xhtml";
}
}
Prof. Dr. Stephan Kleuker
482Komponentenbasierte Software-Entwicklung
Erstes Template-Beispiel (3/5) - index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<h:body>
<ui:composition template="./templates/rahmen.xhtml">
<ui:define name="titel">
Wesenstest
</ui:define>
<ui:define name="rumpf">
<h:outputText value="Was bist Du?"/>
<br/>
<h:commandButton value="Bad Guy" action="#{bsp.bad}"/>
<h:commandButton value="Good Boy" action="#{bsp.good}"/>
</ui:define>
</ui:composition>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
483Komponentenbasierte Software-Entwicklung
Erstes Template-Beispiel (4/5) - analyse.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<h:body>
<ui:composition template="./templates/rahmen.xhtml">
<ui:define name="titel">
Ergebnis
</ui:define>
<ui:define name="rumpf">
<h:outputText value="#{bsp.ergebnis}"/>
</ui:define>
</ui:composition>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
484Komponentenbasierte Software-Entwicklung
Erstes Template-Beispiel (5/5) - Nutzung
Prof. Dr. Stephan Kleuker
485Komponentenbasierte Software-Entwicklung
Aufbau genauer analysiert
• in index.xhtml<h:body>
<ui:composition template="./templates/rahmen.xhtml">
• im Template wird <h:head> gesetzt
• generell werden mit ui:composition Kinder des Wurzelknotens UIViewRoot erzeugt
• soll umgebender Text und Struktur berücksichtigt werden, ist ui:decorate zu nutzen; immer bei mehreren Templates
• Beispiel: folgender Text nur bei ui:decorate sichtbar
<h:body>
<h:outputText value="Hallo"/>
<ui:decorate template="./templates/rahmen.xhtml">
Prof. Dr. Stephan Kleuker
486Komponentenbasierte Software-Entwicklung
Nutzung von Parametern
• in rahmen.xhtml<h:commandLink value="Zurück" action="#{bsp.zurueck}"
rendered="#{!start}"/>
• in index.xhtml<ui:composition template="./templates/rahmen.xhtml">
<ui:param name="start" value="true"/>
<ui:define name="titel">
• in analyse.xhtml<ui:composition template="./templates/rahmen.xhtml">
<ui:param name="start" value="false"/>
• Parameter auch bei anderen Elementen nutzbar
Prof. Dr. Stephan Kleuker
487Komponentenbasierte Software-Entwicklung
Nutzung von ui:include (1/2) - index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:include src="./templates/head.xhtml">
<ui:param name="titel" value="Hallodele"/>
</ui:include>
<ui:include src="./templates/body.xhtml">
<ui:param name="text" value="rischtisch"/>
<ui:param name="obj" value="#{bsp}"/>
</ui:include>
</html>
Prof. Dr. Stephan Kleuker
488Komponentenbasierte Software-Entwicklung
Nutzung von ui:include (2/2) - head/body.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title> <h:outputText value="#{titel}"/> </title>
</h:head>
</html>
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:body>
<h3> <h:outputText value="#{text}"/> </h3>
<h:outputText value="#{obj.ergebnis}"/>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
489
Nutzung von h:graphicImage
• Verwaltung von Hilfsdateien und Bildern im Ordner WEB-INF/resources
• Nutzung
<h:graphicImage library="bilder" name="boah.jpg"
width="200" height="40" alt="boah"/>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
490Komponentenbasierte Software-Entwicklung
Nutzung von ui:debug
• Im Development- oder Debug-Modus erhält man durch Drücken von CTRL+SHIFT+d Komponentenbaum angezeigt<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:debug/>
<ui:include src="./templates/head.xhtml">
Prof. Dr. Stephan Kleuker
491Komponentenbasierte Software-Entwicklung
Entwicklung eigener Komponenten
• Möglichkeit Komponenten basierend auf anderen Komponenten in XHTML zu realisieren
• konfigurierbar über Interface (hier nur Mini-Beispiel)
• vor eigener Entwicklung nachdenken:
– Realisierung mit Templates oder/und include genauso gut?
– gibt es gewünschte Komponente schon? (z. B. nachschauen unter http://jsfcentral.com/)
– Reichen nicht Validatoren, Konvertierer und evtl. PhaseListener aus?
Prof. Dr. Stephan Kleuker
492Komponentenbasierte Software-Entwicklung
Komponente (1/4) - Schnittstelle glossar.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:composite="http://xmlns.jcp.org/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="schlagwort" required="true"/>
<composite:attribute name="detail" required="true"/>
</composite:interface>
Prof. Dr. Stephan Kleuker
493Komponentenbasierte Software-Entwicklung
Komponente (2/4) - Implementierung
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:panelGrid columns="2" rules="cols" frame="box">
<h:outputLabel value="#{cc.attrs.schlagwort}"/>
<h:panelGroup>
<p>
<h:outputText value="#{cc.attrs.detail}"/>
</p>
<composite:insertChildren/>
</h:panelGroup>
</h:panelGrid>
</composite:implementation>
</html>
Prof. Dr. Stephan Kleuker
494Komponentenbasierte Software-Entwicklung
Komponente (3/4) - Beispielnutzung index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:bsp="http://xmlns.jcp.org/jsf/composite/beispiel">
<h:head>
<title>Glossar</title>
</h:head>
<h:body>
<bsp:glossar schlagwort="Adam"
detail="der, wo nich allein sein konnte"/>
<bsp:glossar schlagwort="Eva"
detail="die, wo den Apfel nich lassen konnte">
Wo ist Missing Link?
</bsp:glossar>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
495Komponentenbasierte Software-Entwicklung
Komponente (4/4) - Ausgabe, Projektstruktur
Ordner muss so heißen
Prof. Dr. Stephan Kleuker
496
Return-Taste soll zur Datenübernahme führen <h:body>
<ui:remove>
Die zentralen Knoepfe der Web-Seiten muessen den Namen
"schicken" haben, damit sie angesprungen werden
</ui:remove>
<h:form id="main" style="font-family:Calibri"
onkeypress="if (event.keyCode === 13) {
document.getElementById('main:schicken')
.click();
return false;
}">
• Detailliertere Bedingung im if:if (event.keyCode === 13)
and (event.target.nodeName == 'INPUT')
and (event.target.getAttribute('type') == 'text') {
document.getElementById(‚main:lokal').click();
return false;
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
497Komponentenbasierte Software-Entwicklung
Ausblick: Komponentenmöglichkeiten
• Im Interface wesentlich mehr als nur einfache Parameter (Attribute) definierbar
• Attribute können Verknüpfungen zu Action-Methoden bzw. Event-Listenern enthalten method-signature="java.lang.String f()"
• Übergabe weiterer Funktionalität (Methoden) an Komponente möglich für Event-Listener, Konvertierer und Validatoren
<composite:actionSource>
<composite:valueHolder>
<composite:editableValueHolder>
• Funktionalität kann dann bei inneren Komponenten der Composite-Komponente genutzt werden
Prof. Dr. Stephan Kleuker
498Komponentenbasierte Software-Entwicklung
6.2 Nutzung von Ajax
• Ajax = Asynchronous JavaScript and XML(HTTPRequest)
– klassische Webapplikation:
• Nutzer macht Eingaben
• Nutzer beendet Eingabe (Knopf, Link, <Return>-Taste)
• Informationen an Server, der wertet aus
• neu berechnete Seite wird Nutzer zur Verfügung gestellt
• interaktive Applikation (Jesse James Garret, 2004)
– Nutzer macht Eingaben
– Eingaben führen auch während der Eingabe zu Reaktionen
– Informationen werden zwischenzeitlich auf Server ausgewertet
– berechnete Teilergebnisse werden in aktuelle Seite eingeblendet
Prof. Dr. Stephan Kleuker
499Komponentenbasierte Software-Entwicklung
Ajax-Beispiel (1/3) - Bean
@Named("user")
@SessionScoped
public class Nutzer implements Serializable{
private String name = "";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Prof. Dr. Stephan Kleuker
500Komponentenbasierte Software-Entwicklung
Ajax-Beispiel (2/3) - index.xhtml mit f:ajax
<h:form>
<h:outputText value="Your name: " />
<h:inputText id="in" value="#{user.name}" >
<f:ajax render="greeting" event="keyup"/>
<f:validateLength minimum="0" maximum="10" />
</h:inputText>
<h:message for="in"/>
<br/>
<h:panelGroup id="greeting" >
<h:outputText value="Hello, "
rendered="#{not empty user.name}" />
<h:outputText value="x " rendered="#{empty user.name}" />
<h:outputText value="#{user.name}" />
<h:outputText value="!" rendered="#{not empty user.name}"/>
</h:panelGroup>
</h:form>
Prof. Dr. Stephan Kleuker
501Komponentenbasierte Software-Entwicklung
Ajax-Beispiel (3/3) - Ausgabe
• Start
• Nutzer tippt, dann unmittelbare Aktion
• (so) keine direkte Validierung
• Nutzer drückt Return-Taste
Prof. Dr. Stephan Kleuker
502Komponentenbasierte Software-Entwicklung
Granulare Nutzung von f:ajax
• f:ajax kann um Komponenten gelegt werden
• f:ajax kann in Komponenten ergänzt werden (z. B. h:commandButton)
• f:ajax kann in und um Komponenten genutzt werden, dann ist Effekt die Summe aller spezifizierten Effekte
• Mehrere f:ajax-Einträge für eine Komponente ergänzen sich generell
• render: IDs der Komponenten, die neu gerendert werden sollen (auch @this = dieses Element, @form = umgebende Form, @all = alle Elemente, @none = kein Element)
• execute: IDs der Komponenten, die vollständig JSF-Lebenszyklus durchlaufen sollen (auch ... s. o.)
Prof. Dr. Stephan Kleuker
503
Garantierte Aktualisierung
• Nicht alle Elemente werden sofort aktualisiert, z. B. h:commandButton
• Standardtrick: Zu verändernde Komponenten in h:Panelgroup oder h:panelgrid einordnen
<h:inputText id="nutzername" value="#{steuerung.nutzer}" >
<f:ajax render="edit" event="keyup" listener="#{…}"/>
</h:inputText>
…
<h:panelGroup id="edit">
<h:commandButton id ="editButton" value="Editieren"
action="#{…}" rendered="#{!empty steuerung.nutzer}"/>
</h:panelGroup>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
504
Texteingabe und Knopf über Ajax
<h:inputText id ="nutzer" size="12"
value ="#{vc.nutzereingabe}"
onkeypress ="return event.keyCode !== 13;"/>
<h:commandButton value ="anmelden" >
<f:ajax event ="click"
execute = "nutzer"
render = "@form"
listener = "#{vc.nutzerEingegeben}"/>
</h:commandButton>
Komponentenbasierte Software-Entwicklung
Variable im Controller Verhindere
„Enter“-Taste
wichtig, damit Feld validiert wird
zu aktualisieren (ab und zu Holzhammer notwendig)
Methode im Controller
Prof. Dr. Stephan Kleuker
505Komponentenbasierte Software-Entwicklung
Interaktionsspielerei (1/4) - index.xhtml
<h:form>
<h:outputLabel id="be" value="#{control.beruehrt}">
<f:ajax event="mouseover" render="be pg"
listener="#{control.drueber}"/>
<f:ajax event="mouseout" render="be pg"
listener="#{control.weg}"/>
</h:outputLabel>
<h:panelGrid id="pg" columns="2">
<h:outputText value="drueber:"/>
<h:outputText value="#{control.dzahl}"/>
<h:outputText value="weg:"/>
<h:outputText value="#{control.wzahl}"/>
</h:panelGrid>
</h:form>
Prof. Dr. Stephan Kleuker
506Komponentenbasierte Software-Entwicklung
Interaktionsspielerei (2/4) - Verwaltungsklasse
@Named
@SessionScoped
public class Control implements Serializable{
private String beruehrt = "beruehr mich";
private int dzahl;
private int wzahl;
public String getBeruehrt() {return beruehrt;}
public int getDzahl() {return dzahl;}
public int getWzahl() {return wzahl;}
public void drueber() {
beruehrt = "beruehr mich nicht";
dzahl++;
}
public void weg() {
beruehrt = "beruehr mich";
wzahl++;
}
}
Prof. Dr. Stephan Kleuker
507Komponentenbasierte Software-Entwicklung
Interaktionsspielerei (3/4) – Ausgabe Firefox
Prof. Dr. Stephan Kleuker
508
Interaktionsspielerei (4/4) – Ausgabe Chrome
• Zähler laufen bei Bewegung auf dem Text los, maximaler Unterschied: eins
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
509Komponentenbasierte Software-Entwicklung
<h: Komponenten> und Events (Ausschnitt)
body X X X X X X X X X X X X
commandButton X X X X X X X X X X X X X X
commandLink X X X X X X X X X X X X X
dataTable X X X X X X X X X X
inputText X X X X X X X X X X X X X X X
inputTextArea X X X X X X X X X X X X X X X
outputLabel X X X X X X X X X X X X
panelGrid X X X X X X X X X X
selectManyMenu X X X X X X X X X X X X X X X
selectOneRadio X X X X X X X X X X X X X X X
actio
n
blur
chan
ge
clic
k
dblc
lick
focu
s
keyd
own
keyp
ress
keyu
p
load
mou
sedo
wn
mou
sem
ove
mou
seou
t
mou
seov
er
mou
seup
sele
ct
unlo
ad
valu
eCha
nge
Prof. Dr. Stephan Kleuker
510
Polling mit Primefaces (1/2)
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Poller</title>
</h:head>
<h:body>
<h:form>
<h:outputText id="o1" value="#{zeit.datum}"/><br/>
<h:outputText id="o2" value="#{zeit.anzahl}"/>
<p:poll interval="3" update="o1,o2"/>
</h:form>
</h:body>
</html>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
511
Polling mit Primefaces (2/2)
@Named
@SessionScoped
public class Zeit
implements Serializable{
private int anzahl;
public String getDatum() {
anzahl++;
return new Date().toString();
}
public int getAnzahl() {
return anzahl;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
512Komponentenbasierte Software-Entwicklung
Einschub: JSF und CSS (1/3)
• CSS-Dateien nutzbar
• fast allen JSF-Elementen können Style-Klassen (genauer Style-Spezifikationen) und konkrete Styles zugeordnet werden
Prof. Dr. Stephan Kleuker
513Komponentenbasierte Software-Entwicklung
JSF und CSS (2/3) – Ausschnitt aus Anwendung
<h:head>
<title>Modulübersicht</title>
<h:outputStylesheet library="css" name="stil.css"/>
</h:head>
<h:form id="f2">
<h:messages globalOnly="true" styleClass="infoClass"/>
<h:panelGrid rendered="#{!empty module.module}">
<h:dataTable value="#{module.module}" var="m"
styleClass="resultGrid" rowClasses="oddRow,evenRow">
<h:column >
<f:facet name="header">
<h:outputText value="Nummer" />
</f:facet>
...
Prof. Dr. Stephan Kleuker
514Komponentenbasierte Software-Entwicklung
JSF und CSS (3/3) - Beispielausgabe
Prof. Dr. Stephan Kleuker
515
6.3 Testen von Web-Applikationen - Selenium
• Web-Browser nutzen schwerpunktmäßig HTML zur Darstellung
• Capture & Replay-Werkzeuge, die hardgecoded Pixel und Klicks verarbeiten, eignen sich meist auch für diese Programme
• Einfaches Werkzeug für Web-Applikationen und Firefox ist Selenium IDE [hier nicht gezeigt] (http://seleniumhq.org/)
– erlaubt Capture & Replay von Nutzereingaben
– ermöglicht Tests von Elementen, Identifizierung über Idsaber auch Beschriftungen
– erlaubt den Export der aufgezeichneten Tests u. a. in JUnit (Variante: Browsersteuerung aus JUnit heraus [nächste Folien], ähnlich FEST)
– basiert auf JavaScript und Iframes
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
516
Hinweise zum Testen
• Erinnerung: White Box – Grey Box – Black Box
• Basisfunktionalität mit JUnit testen
• einige Funktionalität ohne Server testbar
• gibt einfache Server nur zur Testausführung
• Selenium WebDriver ermöglicht Test der (Web-)Oberfläche
• niemals alle Tests durch Oberflächentests ersetzen
• es gibt nie das ultimative Werkzeug; aber Familie von Werkzeugen hilft oft
• nur automatisieren, wenn sinnvoll
• Ziel: Continuous Integration
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
517
Selenium WebDriver
• Selenium steuert Browser von Java (C#, Python, Ruby) aus
• Installation als jar-Dateien
• flexible Möglichkeiten zum Finden von GUI-Komponenten
• ideal für Regressionstests, bei wenig sich ändernden GUIs
• in fast allen Unternehmen genutzt, die Web-Applikationen herstellen
• kontinuierliche Weiterentwicklung (nicht immer alles übertragbar, Selenium -> Selenium 2)
• Grundregel: nur automatisieren, was sinnvoll und machbar ist, Rest manuell
• http://docs.seleniumhq.org/docs/
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
518
Selenium WebDriver - Konfiguration
• unterstützt alle gängigen Browser
• bei neuen Browsern muss eventuell auf Aktualisierung von WebDriver gewartet werden
• Oft kleine zusätzliche Programme pro Browser benötigt
– Firefox: geckodriver (https://github.com/mozilla/geckodriver/releases) Proxy für Marionette
• über Nutzer-Profile nachdenken
• generell Browser-Version nutzen, der vor der genutzten Selenium-Version fertig war
• generell notwendig: eigene Testumgebung
• typisch: Testserver mit fester Konfiguration; nicht notwendigerweise neuestem Browser
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
519
Beispielprogramm (1/3)
• Spezifikation: Zu entwickeln ist eine Applikation mit der geheime Nachrichten an einen Server übergeben und dort wieder abgefragt werden können. Genauer gibt der Nutzer eine Nachricht zusammen mit zwei Codewörtern und der Anzahl, wie oft die Nachricht abgerufen werden kann, ein. Weiterhin kann ein Nutzer über die Eingabe zweier Codewörter an gespeicherte Nachrichten kommen. Ist die Codewortkombination nicht vorhanden, wird ein Standardtext ausgegeben.
• Realisierung: Glassfish, JSF (Nutzung des Application Scope)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
520
Beispielprogramm (2/3)
Server starten
Applikation starten
http://localhost:8080/SecretSafe/
Komponentenbasierte Software-Entwicklung
vergebene Ids:
main:verfassen
main:lesen
Prof. Dr. Stephan Kleuker
521
Beispielprogramm (3/3)Nachricht verfassen Nachricht lesen
Komponentenbasierte Software-Entwicklung
eingabe:c1
eingabe:c2
eingabe:geheim
eingabe:ab
eingabe:verschicken
abfrage:c1
abfrage:c2
abfrage:abfragen
antwort:Nachricht
Prof. Dr. Stephan Kleuker
522
Einblick in Nutzungsmöglichkeiten (1/14)public class SafeMoeglichkeitenTest {
private WebDriver driver;
private int linie = 0; // nur Ausgabespielerei
@BeforeClass
public static void setUpOnce() {
System.setProperty("webdriver.gecko.driver"
, "F:\\Programme\\geckodriver\\geckodriver.exe");
}
@Before
public void setUp() {
DesiredCapabilities capabilities = DesiredCapabilities.firefox();
capabilities.setCapability("marionette", true);
driver = new FirefoxDriver(capabilities);
// driver = new HtmlUnitDriver();
// driver = new ChromeDriver();
// driver = new InternetExplorerDriver(ieCapabilities);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
523
Einblick in Nutzungsmöglichkeiten (2/14)
• Klasse WebDriver als zentrale Steuerungsmöglichkeit
• Erzeugt neue Browser-Instanz
• Browser muss auf dem System installiert sein, nutzt keine weiteren Einstellungen des aktuellen Nutzers (leeres Profil)
• Proxy zur Verbindung benötigt, FireFox: geckodriver.exe
• werden kontinuierlich weiterentwickelt
• (früher reichte driver = new InternetExplorerDriver(); )
• bisheriges Angebot (unterschiedliche Qualität): HtmlUnitDriver(), FirefoxDriver(), InternetExplorerDriver(), ChromeDriver(),
• OperaDriver durch andere Entwickler, IPhoneDriver nur zusammen mit Apple-XCode-Umgebung, AndroidDriver mit Android-Entwicklungsunterstützung
• Hintergrund: Selenium lange Zeit nur mit Firefox nutzbar
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
524
Einblick in Nutzungsmöglichkeiten (3/14)
• Zentrale Hilfsklasse für GUI-Komponenten: WebElement• nur zur Veranschaulichung: Ausgabemöglichkeit
private void zeigeElemente(List<WebElement> liste){
System.out.println("----"+(++linie));
if (liste != null) {
for (WebElement w : liste) {
System.out.println(" " + w.getTagName()
+ "::" + w.getAttribute("type")
+ "::" + w.getAttribute("name")
+ "::" + w.getAttribute("value")
+ "::" + w.getText()
+ "::" + w.getLocation() + "::" +w.isEnabled());
}
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
525
Einblick in Nutzungsmöglichkeiten (4/14)
• Überblick über generierte HTML-Seite
• In Entwicklung sinnvolle Ids/Namen vergeben
• JSF: Ids eindeutig
• Zugriff auch ohne Ids machbar („drittes Input-Element“)Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
526
Einblick in Nutzungsmöglichkeiten (5/14)
@Test
public void testBeispielvarianten()
throws InterruptedException, IOException {
// Seite aufrufen
driver.get("http://localhost:8080/SecretSafe/");
List<WebElement> liste =
driver.findElements(By.tagName("input"));
zeigeElemente(liste);
----1
input::hidden::main::main::::(0, 0)::true
input::submit::main:verfassen::Nachricht verfassen::::(8, 129)::true
input::submit::main:lesen::Nachricht lesen::::(8, 153)::true
input::hidden::javax.faces.ViewState::2158484851038199978:-
1608245938470041174::::(0, 0)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
527
Einblick in Nutzungsmöglichkeiten (6/14)
List<WebElement> inp = driver.findElements(
By.xpath("//input"));
zeigeElemente(inp);
----2
input::hidden::main::main::::(0, 0)::true
input::submit::main:verfassen::Nachricht verfassen::::(8, 129)::true
input::submit::main:lesen::Nachricht lesen::::(8, 153)::true
input::hidden::javax.faces.ViewState::2158484851038199978:-
1608245938470041174::::(0, 0)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
528
Einblick in Nutzungsmöglichkeiten (7/14)
• Viele weitere LokalisierungsmöglichkeitenMethod Summary
static By className(java.lang.String className)
static By cssSelector(java.lang.String selector)
WebElement findElement(SearchContext context)
List<WebElement> findElements(SearchContext context)
static By id(java.lang.String id)
static By linkText(java.lang.String linkText)
static By name(java.lang.String name)
static By partialLinkText(java.lang.String linkText)
static By tagName(java.lang.String name)
static By xpath(java.lang.String xpathExpression)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
529
Einblick in Nutzungsmöglichkeiten (8/14)
• Steuerungsmöglichkeiten mit submit(), click(), weiteren Eingabemöglichkeiten
WebElement element =
driver.findElement(By.name("main:verfassen"));
System.out.println(element.getTagName()
+ "::" + element.getAttribute("type")
+ "::" + element.getAttribute("name")
+ "::" + element.getAttribute("value"));
element.click();
input::submit::main:verfassen::Nachricht verfassen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
530
Einblick in Nutzungsmöglichkeiten (9/14)(new WebDriverWait(driver, 10)).until(
new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.id("eingabe"));
}
});
System.out.println(driver.findElement(By.tagName("body")).getText());
Codewort 1:
Codewort 2:
geheime Nachricht:
wie oft abrufbar:
Zur Startseite
// Hilfsvariable für folgende Berechnung
List<WebElement> labels =
driver.findElements(By.tagName("input"));
Komponentenbasierte Software-Entwicklung
Warten bis Element erscheint
Prof. Dr. Stephan Kleuker
531
Einblick in Nutzungsmöglichkeiten (10/14)
• es besteht die Möglichkeit JavaScript direkt auszuführen
• Mächtige Möglichkeiten, z. B. um Skripte zu starten oder Seite zu analysieren
• hier mit Übergabe und Rückgabe
@SuppressWarnings("unchecked")
List<WebElement> inputs2 = (List<WebElement>)
((JavascriptExecutor)driver).executeScript(
"var lbls = arguments[0]; "
+ "var inputs = []; "
+ "for (var i=0; i < lbls.length; i++){"
+ " inputs.push(document.getElementById("
+ " lbls[i].getAttribute('name'))); "
+ "} "
+ "return inputs;„ , labels);
zeigeElemente(inputs2);
Komponentenbasierte Software-Entwicklung
Aufrufparameter
Parameter für Aufruf
Prof. Dr. Stephan Kleuker
532
Einblick in Nutzungsmöglichkeiten (11/14)
• Ausgabe zur letzten Folie
----3form::null::eingabe::null::Codewort 1:
Codewort 2:
geheime Nachricht:
wie oft abrufbar:
Zur Startseite::(8, 109)::true
input::text::eingabe:c1::::::(92, 109)::true
input::text::eingabe:c2::::::(92, 131)::true
input::text::eingabe:geheim::::::(138, 153)::true
input::text::eingabe:ab::0::::(120, 175)::true
input::submit::eingabe:verschicken::Verschicken::::(8,
197)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
533
Einblick in Nutzungsmöglichkeiten (12/14)Object[] werte = {"input", "text"};
@SuppressWarnings("unchecked")
List<WebElement> inputs3 = (List<WebElement>)
((JavascriptExecutor) driver).executeScript(
"var tmp = document.getElementsByTagName(arguments[0]); "
+ "var erg = []; "
+ "for (var i=0; i<tmp.length; i++){"
+ " if(tmp[i].type==arguments[1]){"
+ " erg.push(tmp[i])"
+ " }"
+ "}; "
+ "return erg;", werte);
input::text::eingabe:c1::::::(92, 109)::true
input::text::eingabe:c2::::::(92, 131)::true
input::text::eingabe:geheim::::::(138, 153)::true
input::text::eingabe:ab::0::::(120, 175)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
534
Einblick in Nutzungsmöglichkeiten (13/14)
// Gibt Seitentitel auf Konsole aus
System.out.println("Titel der Seite ist: "
+ driver.getTitle());
// Bildschirmfoto
File screenshot = ((TakesScreenshot)driver)
.getScreenshotAs(OutputType.FILE);
// FileUtils aus org.apache,commons,io
// http://commons.apache.org/proper/commons-io/download_io.cgi
FileUtils.copyFile(screenshot,
new File("bild"+new Date().getTime()+".png"));
Assert.assertTrue(driver.getTitle().contains("Pssst"));
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
535
Einblick in Nutzungsmöglichkeiten (14/14)
• nach mehren Testläufen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
536
Projektaufbau
• zentral benötigte Bibliotheken im Selenium-Download
Komponentenbasierte Software-Entwicklung
alle benötigt
benötigt (aktuelle
Variante)
Prof. Dr. Stephan Kleuker
537
Weitere Funktionalität
• Wechsel zwischen Fenstern und zwischen Frames
• Möglichkeit vorwärts und zurück zu navigieren
• Nutzung von Cookies
• (Versuch der) Unterstützung von Drag und Drop
• Proxy-Nutzung
• Einstellung von Wartezeiten
• Warten auf das Erscheinen von HTML-Elementen (wichtig in Richtung AJAX und HTML5)
• Zusammenspiel mit Selenium IDE zur Testaufzeichnung
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
538
Achtung: Viele Einstiegsfallen
• generell gute Einarbeitungsmöglichkeit durch gute Dokumentation
• trotzdem viele kleine Fehlerquellen, die Entwicklungsprozess bremsen können
• Beispiel: Tests ziehen auf anderen Rechner um
• wichtiges Detail aus der Doku "The browser zoom level must be set to 100% so that the native mouse events can be set to the correct coordinates." (nicht erster Google-Treffer)
• teilweise weitere Browser-Einstellungen beachten
• Browser-Updates benötigen teilweise Selenium-Updates
• Fazit: Testrechner nie zu anderen Zwecken nutzen, Konfiguration sichern
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
539
Weitere Steuerungsvarianten
• Warten, bis Element vorhanden ist(new WebDriverWait(driver, 10)).until(
new ExpectedCondition<WebElement>(){
@Override
public WebElement apply(WebDriver d) {
return d.findElement( By.name("j_idt8:j_idt10"));
}});
• Steuerungsvariante mit JavaScriptWebElement but =
driver.findElement(By.name("j_idt8:j_idt10"));
((IJavaScriptExecutor)driver)
.executeScript("arguments[0].fireEvent('onclick');", but);
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
540
Test des Beispiels (1/7)
public class SecretSafeTest {
private WebDriver driver;
private static String text1;
private static String text2;
private File f = new File("C:\\Users\\x\\AppData\\"
+ "Roaming\\Mozilla\\Firefox\\Profiles\\zm12egmo.default");
private FirefoxProfile profile = new FirefoxProfile(f);
@BeforeClass
public static void setupClass() {
text1 = "" + Math.random(); // nicht ganz QS-sauber
text2 = "" + Math.random(); // zufaellige Texte
System.setProperty("webdriver.gecko.driver"
, "F:\\Programme\\geckodriver\\geckodriver.exe");
}
@Before
public void setUp() {
driver = new FirefoxDriver(profile);
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
541
Test des Beispiels (2/7)@After
public void tearDown() {
driver.quit(); // dauert, sonst nur am Ende aller Tests
}
// zur Erkennung, ob Seite bereits geladen ist
private void warteAufSeiteMitId(String id) {
(new WebDriverWait(driver, 10)).until(
new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.id(id));
}
});
}
private void startSeite(){
driver.get("http://localhost:8080/SecretSafe");
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
542
Test des Beispiels (3/7)
private void eingabeSeite(){
startSeite();
warteAufSeiteMitId("main");
driver.findElement(By.name("main:verfassen")).click();
warteAufSeiteMitId("eingabe");
}
private void ausgabeSeite(){
startSeite();
warteAufSeiteMitId("main");
driver.findElement(By.name("main:lesen")).click();
warteAufSeiteMitId("abfrage");
}
private void feldFuellen(String name, String wert){
driver.findElement(By.name(name)).clear();
driver.findElement(By.name(name)).sendKeys(wert);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
543
Test des Beispiels (4/7)private void textEingeben(String text1, String text2,
String geheim, int versuche){
eingabeSeite();
feldFuellen("eingabe:c1",text1);
feldFuellen("eingabe:c2",text2);
feldFuellen("eingabe:geheim",geheim);
feldFuellen("eingabe:ab",""+versuche);
driver.findElement(By.name("eingabe:verschicken")).click();
warteAufSeiteMitId("erfolg");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Eintrag erfolgreich"));
}
private void textEingeben(String geheim, int versuche){
textEingeben(this.text1, this.text2, geheim, versuche);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
544
Test des Beispiels (5/7)
private void textErfolgreichSuchen(String text1,
String text2, String geheim){
ausgabeSeite();
feldFuellen("abfrage:c1",text1);
feldFuellen("abfrage:c2",text2);
driver.findElement(By.name("abfrage:abfragen")).click();
warteAufSeiteMitId("antwort");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains(geheim));
}
private void textErfolglosSuchen(String text1, String text2){
ausgabeSeite();
feldFuellen("abfrage:c1",text1);
feldFuellen("abfrage:c2",text2);
driver.findElement(By.name("abfrage:abfragen")).click();
warteAufSeiteMitId("antwort");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Treffen um 730 in KN2"));
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
545
Test des Beispiels (6/7)private void textErfolgreichSuchen(String geheim){
textErfolgreichSuchen(text1, text2, geheim);
}
private void textErfolglosSuchen(){
textErfolglosSuchen(text1, text2);
}
@Test
public void testErfolglos(){
textErfolglosSuchen();
}
@Test
public void testEintragenUndLesen(){
textEingeben("TextText", 3);
textErfolgreichSuchen("TextText");
textErfolgreichSuchen("TextText");
textErfolgreichSuchen("TextText");
textErfolglosSuchen();
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
546
Test des Beispiels (7/7)
@Test
public void testLinkLesen(){
ausgabeSeite();
driver.findElement(By.linkText("Zur Startseite")).click();
warteAufSeiteMitId("main");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Was wollen Sie machen?"));
}
@Test
public void testLinkSchreiben(){
eingabeSeite();
driver.findElement(By.linkText("Zur Startseite")).click();
warteAufSeiteMitId("main");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Was wollen Sie machen?"));
}
// weitere Tests sinnvoll
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
547
Testarchitektur
• Tests müssen für Änderbarkeit konzipiert werden
• häufig: viele Tests für eine konkrete Version geschrieben, nach leichten Änderungen der zu testenden Software werden Tests als unwartbar weggelegt
• Problem: ähnlich wie Software-Architektur wird Test-Architektur benötigt
• ein Ansatz: jeweils eigene Steuerungsklasse für eine Web-Seite, Tests arbeiten nur auf diesem Abstraktionslevel
• kleine Änderungen führen zu kleinen Änderungen in der Steuerungsklasse und keinen Änderungen bei deren Nutzern
• durch Abstraktion muss nicht jeder Tester Selenium können
• -> Tests müssen von qualifizierten Software-Entwicklern geschrieben werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
548Komponentenbasierte Software-Entwicklung
Design-Tests
• Browser stellen identische Inhalte leicht verändert da
• technisch überflüssig, aber wichtig für den Zeitgeist: modische Design-Anpassungen
• Für IT-Profi: Sisyphos-Arbeit; Test mit unterschiedlichen Browsern
• Direkte Hilfsmittel:
– Lunascape: ein Browser, bei dem man zwischen drei Maschinen umschalten kann IE (Trident)+Firefox (Gecko)+Chrome, Safari (Webkit)
– Windows: USB-portable Browser ohne Installationsnotwendigkeit (verändern keine Einstellungen): Firefox, Chrome, Opera, …
• evtl. auch Capture & Replay mit Selenium zum inhaltlichen Test
Prof. Dr. Stephan Kleuker
549Komponentenbasierte Software-Entwicklung
6.4 JSF-Erweiterungen
• graphische Möglichkeiten in JSF recht schlicht (wesentlich besser als gezeigt!), Erweiterungsbibliotheken sinnvoll
• wesentliche frei nutzbare Komponentenframeworks:– RichFaces: http://www.jboss.org/richfaces– Primefaces: http://www.primefaces.org/
• alternative JSF-Implementierung (Standard: Mojarra):– Apache MyFaces: http://myfaces.apache.org/
• kritisch: Frameworks nur teilweise kompatibel, gilt auch für Framework mit Implementierung
• kritisch: in Details teilweise deutliche Darstellungsunterschiede in verschiedenen Browsern
• Hinweis: hier nur Ideen und notwendige Konfiguration• NetBeans bietet Nachlademöglichkeit; hier nicht genutzt um
einfacher IDE wechseln zu können
Prof. Dr. Stephan Kleuker
550Komponentenbasierte Software-Entwicklung
Nutzung von PrimeFaces
• unterstützt JSF 2, Projekt ab November 2008
• Dokumentation war mal kostenpflichtig
• aktuell mit Support (Elite Downloads) und ohne (Community Downloads)
• Installation durch Einbinden einer einzelnen jar-Datei
• sehr viele Gestaltungsmöglichkeiten, mit Showcases gut dokumentiert
• Elemente haben oft sehr viele Parameter für Konfigurationsmöglichkeiten
• Ansatz: zu GUI-Element gehört Java-Objekt mit Konfigurationsmöglichkeiten in Java (set)
Prof. Dr. Stephan Kleuker
551Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (1/4) - Managed Bean
@Named
@SessionScoped
public class Text implements Serializable {
private String inhalt;
public String getInhalt() {
return inhalt;
}
public void setInhalt(String inhalt) {
this.inhalt = inhalt;
}
}
Prof. Dr. Stephan Kleuker
552Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (2/4) - index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Editor</title>
</h:head>
<h:body>
<h:form >
<p:editor id="ed" width="600px" height="120px"
value="#{text.inhalt}" widgetVar="editor"/>
<p:commandButton update="o1,o2" async="true"
value="p Uebernehmen" onclick="editor.saveHTML();"/><br/>
<h:commandButton value="h Uebernehmen"/><br/>
<h:outputText id="o1" escape="true"
value="Inhalt: #{text.inhalt}"/><br/>
<h:outputText id="o2" escape="false"
value="Inhalt: #{text.inhalt}"/>
</h:form>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
553Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (3/4) - h: (Seite lädt neu)
Prof. Dr. Stephan Kleuker
554Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (4/4) - p: (Aktualisierung)
Prof. Dr. Stephan Kleuker
555Komponentenbasierte Software-Entwicklung
Anmerkungen zum Beispiel
• Normalerweise unterstützt p:commandButton Ajax direkt (nur bei p:editor Problem, deshalb Workaround mit widgetVar)
• <h:head> immer benötigt
• Generell: Genaue Analyse der oft sehr vielen Attribute einer Komponente notwendig
• Mischung von h: und p: oft (nicht immer) möglich
• Man muss mit Eigenarten der Komponenten leben (bei Großprojekten und Auswahlentscheidungen schwierig)
• detaillierte Dokumentation als PDF-Download http://www.primefaces.org/documentation
Prof. Dr. Stephan Kleuker
556
Versionsänderung - Beispiel
• Versionswechsel 2.0.2 zu 2.2.1 führt zu anderer Darstellung• Ungleichheit p: und h: korrigiert• andere Attribute statt height="120px" nun height="120"
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
557Komponentenbasierte Software-Entwicklung
JSF - es geht noch weiter
• JSF Expression Language– einfacher Zugriff auf Maps, die Umgebungsdaten, wie
param, requests, cookies und initParam haben– Nutzung von flash-Variable als Zwischenspeicher
• Erweiterung der Expression Language • Möglichkeit GET zu nutzen (und damit Bookmarks)
– <h:button> und <h:link>• JSF hat klare Regeln wie Ressourcen verwaltet werden
(Bilder, Templates, ...) • integrierte, nutzbare JavaScript-API• Viele weitere JavaScript-Möglichkeiten (u. a. h:outputScript)• weitere Möglichkeiten des Event-Handlings, z. B. eigene
Klasse reagiert auf Event (z. B. Phasenwechsel)• Ergänzung/Erweiterung des Exception Handlings• Lokalisierung, Mehrsprachlichkeit
Prof. Dr. Stephan Kleuker
558
7. Contexts and Dependency Injection
• klassisch Dependency Injection
• was kennen wir bereits
• CDI im Detail
– Aktivierung
– Qualifier
– Event Handling
– Objekte injizieren
– Produzenten
– Alternativen
– Interception
• Fazit
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
559
Ausblick auf weitere Themen
Komponentenbasierte Software-Entwicklung
Browser
Datenbank
JPA
EJBBean
Validation
Scope
JSFRESTful
WebService
Web Sockets
1
CDI
2
3
Prof. Dr. Stephan Kleuker
560
Dependency Injection klassisch (1/2)
woher kommen Objekte für Exemplarvariablen?
• Variante 1: Werte werden als Parameter übergeben, aus denen Objekte gebaut werden (Objekt baut sich benötigte Objekte selber)
• Variante 2: Objekte werden als Referenzen übergeben
– Optimierung: Typen der Objektvariablen sind Interfaces; so konkrete Objekte leicht austauschbar
• Variante 2 heißt Dependency Injection mit get- und set-Methoden oder über Konstruktoren
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
561
Dependency Injection klassisch (2/2)
Nutzer nutzer = new Nutzer(new Inter1RealA(42)
, new Inter2RealC(43)
, new Inter3RealD("Hallo"));
eng verknüpft mit Factory Pattern
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
562
JSF nutzt bereits Dependency Injection
@Named
@SessionScoped
public class SprintController implements Serializable
@Inject
PersistenzService pers;
• @Named: Objekt steht und festen Namen zur Oberflächengestaltung zur Verfügung
• @SessionScoped: Objekt steht für die gesamte Session (auch allen anderen Objekten) zur Verfügung (Context)
• @Inject: „Umgebung gib (injiziere) mir ein zum Typen passendes Objekt“
• @PostConstruct: garantiert nach Erzeugung, vor Nutzung
• Hinweis: Annotationen stammen aus dem CDI-Paket; es gibt sehr ähnliche in JSF-Paketen (JSF ohne CDI machbar)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
563
Ziele CDI
• Entkopplung von Objekten voneinander
– @Inject bestes Beispiel, es wird nur ein passendes Objekt „irgendwoher“ benötigt; dies besorgt CDI-Realisierung
• Vereinfachte Objekt-Kommunikation
– Beispiel: Informationen abonnieren (bekannt als PublishSubscribe oder Observer Observable)
• Vereinfachung von querschnittlich in mehreren Objekten benötigter Funktionalität
– Beispiel: Logging
• Hinzufügen von Funktionalität zu bestimmten Ereignissen ohne betroffene Methoden zu verändern
– Beispiel: Konsistenzprüfung, Benachrichtigung (Aspektorientierung)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
564
Informationsquellen
• CDI 1.1 gehört zu JEE 7
• JSR 299: Contexts and Dependency Injection for the JavaTM EE platform, https://jcp.org/en/jsr/detail?id=299
• JSR 346: Contexts and Dependency Injection for JavaTM EE 1.1, https://jcp.org/en/jsr/detail?id=346
• JSR 365: Contexts and Dependency Injection for JavaTM 2.0 https://jcp.org/en/jsr/detail?id=365
• Spezifikation http://www.cdi-spec.org/
• Referenzimplementierung Weld 2.0 (JBoss)
• gute Einführung: http://docs.jboss.org/weld/reference/latest/en-US/html/intro.html
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
565
NetBeans: kein Deploy on Save
gerade bei CDI ist ein vollständiges Clean & Build mit Deployfast immer sinnvoll, da Server sonst Probleme z. B. mit noch laufenden Sessions hat (Haken wegnehmen)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
566
Aktivierung
• explizit mit beans.xml in WEB-INF –Ordner; für reine EJB-Module oder jar-Dateien im Ordner META-INF<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_1.xsd"
version="1.1" bean-discovery-mode="all">
</beans>
• implizit, ohne beans.xml oder mit und bean-discovery-mode="annotated", werden nur Beans mit Scope gefunden; typischerweise @Dependent für im Scope des Nutzers
• in beans.xml können (und müssen teilweise) weitere Eigenschaften spezifiziert werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
567
zentrales Hilfsmittel Qualifier
• Qualifier sind einfache Annotationen, mit denen gewünschte bzw. geforderte Eigenschaften spezifiziert werden können
• Normale neue Annotation mit Zusatzannotation @Qualifier...
import javax.inject.Qualifier;
@Qualifier
@Target({ElementType.TYPE, ElementType.METHOD,
ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Info{}
• folgende Folien: Qualifier nur erwähnt, haben dann die hier angegebene Form
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
568
Event-Model (Observer – Observable)
• auch Publish-Subscribe
• Observer sagt Bescheid, dass er vom Observable informiert werden möchte
• Observable schickt Informationen an alle Abonnenten
• Beispielaufgabe (Balkon): Informiere alle Interessierten, dass gerade ein Objekt persistiert werden soll
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
569
Event Model Übersicht (müssen nicht EJBs sein)
Komponentenbasierte Software-Entwicklung
class MeinEvent
beliebige POJO-Klasse
@Stateless
public class PersistenceService {
@Inject @Info Event<MeinEvent> event
…
MeinEvent me = …
event.fire(me);
Observable
@Qualifier
…
public @interface Info{}
Qualifier
@Stateless
public class EventConsumer {
public void empfangeMeinEvent(
@Observes @Info MeinEvent event)
{…
Observer
Prof. Dr. Stephan Kleuker
570
Definition des Event-Objekts
• POJO mit Inhalten, die übertragen werden sollenpackage cdi.eventing;
public class MeinEvent { // POJO
private Object obj;
public MeinEvent(){
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
571
Observable – Benachrichtigen (Ausschnitt)
@Stateless
public class Persistence {
@Inject @Info Event<MeinEvent> event;
@Inject
private EntityManager em;
public void persist(MitarbeiterDTO mi) {
Mitarbeiter m = mi.toMitarbeiter();
MeinEvent e = new MeinEvent();
e.setObj(m);
event.fire(e);
this.em.persist(m);
event.fire(e);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
572
Observer 1
• Durch Annotation @Info können verschiedenartige Events unterschieden werden
• beteiligte Objekte sind EJBs oder haben einen Scope (oder übernehmen Scope des nutzenden Objekts)@Stateless
public class EventConsumer {
public void empfangeMeinEvent(
@Observes @Info MeinEvent event) {
System.out.println("obs1 " + event.getObj());
}
}
// wenn Konsument nicht existiert, wird er erzeugt!
// nicht gewünscht: @Observes(notifyObserver=Reception.IF_EXISTS)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
573
Observer 2
@Stateless
public class EventConsumer2 {
// Methode zeigt Machbarkeit des Ansatzes, ob dies hier
// sinnvoll, ist fraglich
public void empfangeMeinEvent(
@Observes @Info MeinEvent event) {
System.out.println("obs2 " + event.getObj().getClass());
if (event.getObj() instanceof Mitarbeiter){
Mitarbeiter m = (Mitarbeiter)event.getObj();
m.setVorname(m.getVorname() + " " + m.getNachname());
}
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
574
Konkretisierung injizierter Objekte
• bisher war immer eindeutig, welches Objekt bei @Inject genutzt wird
• häufig wird aber eine bestimmte Variante eines Objekts benötigt
• Ausgangspunkt: gibt Interface (oder abstrakte Klasse) mit mehreren Realisierungen
• der konkret gewünschte Objekttyp wird dann durch @Inject und die zusätzliche Angabe von Qualifiern festgelegt
• Beispiel: es gibt zwei verschiedene Ausgabemöglichkeiten, unterschieden durch Qualifier @LogQualifier und @SystemQualifier
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
575
Konkretisierung Übersicht
Komponentenbasierte Software-Entwicklung
interface Ausgabe {…
Angabe gewünschter Variante
@LogQualifier
public class AusgabeLog implements Ausgabe {…
@Qualifier
…
public @interface SystemQualifier{}
Qualifier
@Stateless
public class EventConsumer {
@Inject @LogQualifier Ausgabe aus;
@Qualifier
…
public @interface LogQualifier{}
Qualifier
@SystemQualifier
public class AusgabeSys implements Ausgabe {…
Interface
Realisierung
Realisierung
Prof. Dr. Stephan Kleuker
576
Beispiel (1/3): Realisierungen 1/2
public interface Ausgabe {
public void ausgeben(String s);
}
@SystemQualifier
public class AusgabeSystem implements Ausgabe{
@Override
public void ausgeben(String s) {
System.out.println("System " + s);
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
577
Beispiel (2/3): Realisierungen 2/2
@LogQualifier
public class AusgabeLog implements Ausgabe{
private final static Logger LOGGER = Logger
.getLogger(AusgabeLog.class.getSimpleName());
@Override
public void ausgeben(String s) {
LOGGER.log(Level.INFO, "AusgabeLog: {0}", s);
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
578
Beispiel (3/3): Auswahl der Realisierung
@Stateless
public class EventConsumerAusgabe {
@Inject @LogQualifier Ausgabe aus;
public void empfangeMeinEvent(
@Observes @Info MeinEvent event) {
//System.out.println("obs1 " + event.getObj());
aus.ausgeben("obsaus1 " + event.getObj());
}
}
• ein zentrales Logging kann man mit CDI besser durch Interceptors realisieren
• es können auch mehrere Qualifier angegeben werdenKomponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
579
Auswahlregeln
• Wenn Bean keinen Qualifier hat, wird er automatisch als @Default gesetzt (kann man auch hinschreiben)
• ohne benötigten Qualifier kann @Any genutzt werden
• Klassen haben ohne Scope-Angabe den Scope @Dependent, der sich dem Scope des nutzenden Objekts anpasst
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
580
Produzenten
• bisher wurde bei @Inject nach passenden Klassen gesucht und ein Objekt per Default-Konstruktor erzeugt
• Objekt-Erzeugung kann aber auch durch mit @Producesannotierte Konstruktoren, Methoden (Rückgabe-Objekt) oder direkt Exemplarvariablen erfolgen
• Konkretisierung des erzeugten Objekts wieder durch Qualifier (@Starttext, @Meldung im nächsten Beispiel)
• Beispiel zeigt kritische „Wiederverwendung“ von Qualifier
• javax.enterprise.inject.Produces nutzen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
581
Statische Produzenten Übersicht (public vergessen)
Komponentenbasierte Software-Entwicklung
Produktions-varianten
@Qualifier
…
@interface Info{}
Qualifier
class Produzent {
@Produces @Meldung
private String meldung;
@Produces @Starttext
public String getLogtext() {…
@Produces @Starttext @Info
public String getLogtext2() {…
@Qualifier
…
@interface Meldung{}
Qualifier
class AusgabeLog {…
@Inject @Starttext @Info
private String start;
@Inject @Meldung
private String text;
…
Beispielnutzungen
@Qualifier
…
@interface Starttext{}
Qualifier
Prof. Dr. Stephan Kleuker
582
Beispiel (1/2): Produzenten-Klasse
public class Produzent implements Serializable {
private String logtext = "logtext";
@Produces @Meldung
private String meldung;
public Produzent() { this.meldung = "Hai";}
@Produces @Starttext
public String getLogtext() {
return "prod1 " + this.logtext;
}
@Produces @Starttext @Info
public String getLogtext2() {
System.out.println("getLogtext2");
return "prod2 " + this.logtext;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
583
Beispiel (2/2): Nutzer der Produzenten
@LogQualifier2
public class AusgabeLog2 implements Ausgabe{
private final static Logger LOGGER = Logger
.getLogger(AusgabeLog.class.getSimpleName());
@Inject @Starttext @Info private String start;
@Inject @Meldung private String text;
@Override
public void ausgeben(String s) {
LOGGER.log(Level.INFO, "AusgabeLog2: {0}", s);
LOGGER.log(Level.INFO, "prodnutzer {0} {1}: {2}"
, new Object[]{this.text, this.start, s});
}
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
584
Dynamische Produzenten Übersicht
Komponentenbasierte Software-Entwicklung
Produktions-methode
Qualifier
public class Produzent{
@Produces @Aktuell
public String mach(){…
@Qualifier
…
public @interface Aktuell{}
public class AusgabeLog {
@Inject @Aktuell
Instance<String> datum;
…
public void ausgeben(…) {
…
datum.get()});
…
Beispielnutzung
Prof. Dr. Stephan Kleuker
585
Dynamische Produktion (1/2) : Erzeugung
• wieder Produzenten-Methode mit üblichen Qualifier (also nichts Neues hier)
public class Produzent implements Serializable {
@Produces @Aktuell
public String mach(){
return "dynprod " + new Date().toString();
}
...
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
586
Dynamische Produktion (2/2): Aufruf
@LogQualifier3
public class AusgabeLog3 implements Ausgabe{
@Inject @Aktuell Instance<String> datum;
...
@Override
public void ausgeben(String s) {
LOGGER.log(Level.INFO, "AusgabeLog3: {0} - {1}"
, new Object[]{s, datum.get()});
// LOGGER.log(Level.INFO, "{0} {1}: {2}"
// , new Object[]{this.text, this.start, s});
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
587
Inject-Varianten
• statt @Inject an Exemplarvariablen zu schreiben, ist dies auch möglich:@Inject
public Konstruktor(Typ ichWerdeInjected){ …
@Inject
public void methode(Typ ichWerdeInjected){ …
• natürlich wieder Qualifier nutzbar
• weiterführend: mit @Typed an Klasse einschränken, für welche Klassen und Interfaces diese eingesetzt werden kann
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
588
Alternativen
• über Qualifier können passende Klassen genau ausgewählt werden
• z. B. zu Testzwecken, sollte diese Auswahl aber einfach änderbar sein
– hierzu wird Klasse mit @Alternative markiert
– UND muss in der beans.xml als ausgewählte Alternative angegeben werden
• durch Änderung der beans.xml sehr einfach Klassenauswahl auf Testphase oder länderspezifische Auswahlen änderbar
• mehrere Alternativen bei mehreren genutzten jars angebbar, dann Auswahl so steuerbar:
@Priority(Interceptor.Priority.APPLICATION + 10)
komplexes, sehr flexibles Auswahlsystem, wann welche Klasse genutzt wird (@Specialization)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
589
Nutzung der Alternative (1/2): weitere Klasse
import cdi.qualifier.ausgabe.LogQualifier;
import javax.enterprise.inject.Alternative;
@Alternative
@LogQualifier
public class AusgabeLogMock implements Ausgabe{
@Override
public void ausgeben(String s) {
System.out.println("LogMock: " + s );
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
590
Nutzung der Alternative (2/2): beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<class>cdi.AusgabeLogMock</class>
</alternatives>
</beans>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
591
Interceptor Übersicht
Komponentenbasierte Software-Entwicklung
unterbreche alle Methoden von
Interceptor-Benennung
@Stateless
@InterceptQualifier
public class BspKlasse {
public void meth1(…) {…
public void meth2(…) {…
…
@Inherited
@InterceptorBinding
…
public @interface InterceptQualifier{}
@InterceptQualifier
@Interceptor
public class MeinInterceptor {
@AroundInvoke
public Object logCall(
InvocationContext ctx)
throws Exception { …
Method meth = ctx.getMethod();
…
for (Object o: ctx.getParameters())
…
return ctx.proceed();
} …
bei Unterbrechung zu nutzen
<interceptors>
<class>cdi.MeinInterceptor
</class>
</interceptors>
Prof. Dr. Stephan Kleuker
592
Interception
• Ansatz: sich in verschiedenen Methoden wiederholende Aufgaben zentral auslagern (Aspekt-Orientierung)
• (einziger) Klassiker: Logging (evtl. Sicherheit)
• Ansatz: Werden markierte Methoden oder Methoden in markierten Klassen ausgeführt, wird zunächst zum Interceptor gehörende Methode durchgeführt
• Interceptor kann auf Methode und Parameter zugreifen
• Interceptor muss Methodenausführung starten (proceed())
• Interceptor muss über beans.xml eingeschaltet werden (nur in diesem Archiv aktiv) oder @Priority-Annotation besitzen
• Interceptor benötigt eigene Art von Qualifier
• folgendes Beispiel zeigt ungewöhnliche Nutzung (auch Verstoß, dass möglichst wenig beobachtet werden soll)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
593
Nutzung von Interception (1/5): Annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
//@Qualifier
@Inherited
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface InterceptQualifier{}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
594
Nutzung von Interception (2/5): Bereich festlegen
• Hier werden alle Methoden der Klasse beobachtet, man kann die Annotation auch nur für einzelne Methoden nutzen
@Stateless
@InterceptQualifier
public class EventConsumer3 {
public void empfangeMeinEvent(
@Observes @Info MeinEvent event) {
System.out.println("obs3 " + event.getObj().getClass());
if (event.getObj() instanceof Mitarbeiter) {
Mitarbeiter m = (Mitarbeiter) event.getObj();
m.setVorname(m.getVorname() + " X");
}
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
595
Nutzung von Interception (3/5): Realisierung 1/2
@InterceptQualifier
@Interceptor
public class MeinInterceptor {
@AroundInvoke // gibt auch @AroundConstruct, @PostConstruct
public Object logCall(InvocationContext context)
throws Exception {
Method meth = context.getMethod();
System.out.println("inter Methode: " + meth);
/*
for (Object o : context.getParameters()) {
System.out.print(o + " ");
}
*/Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
596
Nutzung von Interception (4/5): Realisierung 2/2
MeinEvent event = (MeinEvent) context.getParameters()[0];
if (event.getObj() instanceof Mitarbeiter) {
Mitarbeiter m = (Mitarbeiter) event.getObj();
if (event.getObj() instanceof Mitarbeiter) {
Mitarbeiter m = (Mitarbeiter) event.getObj();
m.setNachname(m.getNachname() + "Y");
}
return context.proceed(); // wichtig irgendwann aufrufen
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
597
Nutzung von Interception (5/5): beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_1.xsd"
bean-discovery-mode="all">
<interceptors>
<class>cdi.MeinInterceptor</class>
</interceptors>
</beans>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
598
Decorator (1/2): Erinnerung Pattern
Komponentenbasierte Software-Entwicklung
• ergänze neue Klasse (Decorator) die das Interface realisiert und ein Objekt der Klasse als Exemplarvariable hält
• Idee: delegiere Aufrufe an diese Exemplarvariable und ergänze drum herum neue Funktionalität
• flexibler: Exemplarvariable nutzt Interface-Typ
Prof. Dr. Stephan Kleuker
599
Decorator (2/2): Umsetzung in CDI
@Decorator
public class AusgabeDecorator implements Ausgabe{
@Inject @Delegate @Any Ausgabe aus;
@Override
public void ausgeben(String s) {
System.out.println("Starte Dekoration: " + s);
this.aus.ausgeben(s);// nutze dekoriertes Objekt
System.out.println("Ende Dekoration");
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
600
Stereotype
• mit CDI können große Mengen von Annotationen entstehen
• häufiger haben ähnliche Klassen die gleichen Annotationen
• diese können als neue Annotation zusammengefasst werden
@RequestScoped
@Named
@MeineAnnotation
@Stereotype
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aktion {}
• Klassen können auch mit mehreren Stereotypes (auch überlappend) annotiert werden
• Beispiel: @Model vereint @Named und @RequestScopedKomponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
601
Weiterführende Themen
• CDI auch zu eigener Transaktionssteuerung nutzbar
Meinung: wenn JEE und EJB genutzt werden, spricht wenig für diesen Ansatz (Transaktion über mehrere Methoden)
interessant:
[Mül 14] B. Müller, JSF und JPA im Tandem, Teil 1, in: Javamagazin, 5/2014, Seiten 98-102, Software & Support Media GmbH, Frankfurt a. M. , 2014
• Auf Spezifikationsseite kann man sich über neue Versionen informieren
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
602
Fazit
• CDI ermöglicht eine sehr große Entkopplung der Klassen voneinander
• Klassen werden so flexibler einsetzbar, evtl. Programmiermodel intuitiver
• im Beispiel wird „Balkon“ an Projekt programmiert, da @Inject und @...Scope ausreichen; nicht untypisch für klassisches JEE-Projekt
• CDI macht SW zur Zeit noch langsamer
• Annotations-Warfare-Area wird drastisch vergrößert
Komponentenbasierte Software-Entwicklung
Recommended