Upload
buihanh
View
216
Download
0
Embed Size (px)
Citation preview
Prof. Dr. Stephan Kleuker
476
5. RESTful Web Services
• JavaScript Object Notation
• JSONP
• 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
477
Ausblick auf weitere Themen
Komponentenbasierte Software-Entwicklung
Browser
Datenbank
JPA
EJBBean
Validation
CDIScope
JSFRESTful
WebService
Web Sockets 2
1
3
Prof. Dr. Stephan Kleuker
478
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
479
Vereinheitlichung von JSON in Java
in JEE 7 ergänzt:
• JSR 353: JavaTM API for JSON Processing (23.5.2013), https://jcp.org/en/jsr/detail?id=353
• Referenzimplementierung jsonp https://jsonp.java.net/
• in Glassfish 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
480
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
481
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
482
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
484
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
485
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
486
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
487
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
488
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
489
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
490
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
491
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
492
Woher kommt „ Representational State Transfer“
Client fordert Information mit Hilfe einer URL an.
Eine Repräsentation der Information wird als Ergebnis zurückgegeben (z. B. in Form eines JSON-Objekts), Client hat Informationszustand.
Client nutzt Hyperlink 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
493
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)
• Der vollständige Informationszustand kann beim Client oder beim Server liegen, oder auch über beide verteilt sein
• HTTP-Kommunikationsprotokoll selbst bleibt zustandslos
• Grundregel: GET, PUT, DELETE 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
494
Wer nutzt es (Beispiele)?
• Hinweis: Öfter wird gegen die geforderte Reinform von RESTful WebServices verstoßen, und normale Anfragemöglichkeit mit GET als RESTful bezeichnet
• Google Maps
• Google AJAX Search API
• Yahoo Search API
• Amazon WebServices
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
495
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: The JavaTM API for RESTful Web Services, https://jcp.org/en/jsr/detail?id=311 (10.10.2008)
• JSR 339: JAX-RS 2.0: The Java API for RESTful Web Services, https://jcp.org/en/jsr/detail?id=339 (24.5.2013)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
496
JAX-RS aktivieren
• in JEE-aware Servern reicht theoretisch folgendes ausimport javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("resources")
public class ApplicationConfig extends Application {
}
• ist generell im .war-File
• sonst Konfiguration als Servlet nötig
• Beschreibung in web.xml
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
497
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
498
erste RESTful-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
499
detaillierte Analyse
@Path("/helloworld")
• Gibt Aufrufpfad an, hier resources/helloworld
• 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
500
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)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
501
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
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
502
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
503
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
504
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
• 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
505
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
506
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/resources/helloworld");
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
507
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$1
Language : null
Location : null
Mediatype : text/html
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
508
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$1
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
509
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
510
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
511
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
512
Kleine Beispiele (7/7)
public void analyse5() {
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);
}
javax.ws.rs.client.Entity@49fa424c : [2,3]
ok
• Klasse Entity bietet einige Marshalling-Methoden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
513
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
514
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
515
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
516
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/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
517
Beispiel: Umsetzung von Pfaden (4/4)
{"fehler":"kein Kunde"}
{"fehler":"kein Kunde"}
{"summe":32000000}
{"fehler":"kein Konto"}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
518
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
519
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
520
Externer Service zur Analyse von IPs (2/4)
ip: 93.196.192.46
country_code: DE
country_name: Germany
region_code: 07
region_name: Nordrhein-Westfalen
city: Hopsten
zipcode:
latitude: 52.3833
longitude: 7.6167
metro_code:
area_code:
ip: 209.8.115.88
country_code: US
country_name: United States
region_code: TX
region_name: Texas
city: Dallas
zipcode:
latitude: 32.7831
longitude: -96.8067
metro_code: 623
area_code: 214
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
521
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
522
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
523
Ü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
524
Übergabe von Aufrufparametern (2/2)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
525
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
526
Dienstnutzung mit Aufrufparametern (2/2)
{"results":[{"address_components":[{"long_name":"Quakenbrück","
short_name":"Quakenbrück","types":["locality","political"]},{"l
ong_name":"Osnabrück","short_name":"OS","types":["administrativ
e_area_level_3","political"]},{"long_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}}},"types":["locality","political"
]}],"status":"OK"}
{"lat":52.675599,"lng":7.950777699999999}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
527
Aufgabe
Sprinter soll um eine RESTful-Schnittstelle ergänzt werden,
• mit der von außen auf Sprints zugegriffen werden kann,
• die nur eine Teilmenge der Daten der Sprints sieht,
• die neue Sprints anlegen kann,
• die Sprints editieren kann,
• die Sprints löschen kann
• Entscheidung: Ergänze Programm um RESTful Webservices
• Schnittstelle wird in neuem Projekt genutzt (das zum einfacheren Verständnis eine JSF-Oberfläche bekommt)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
528
Nutzungsszenario
• Links nicht ausimplementiert
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
529
Architektur: hierarchischer Aufbau
Resource POST(CREATE)
GET(READ)
PUT(UPDATE)
DELETE(DELETE)
/sprints erzeugt neuen Sprint
Übersicht über alle Sprints
Aktualisiere alle Sprints (oder weglassen)
alle Sprints löschen
/sprints/42 Fehler! Zeige Sprint mit id 42
wenn Sprint mit id 42 existiert, dann aktualisieren, sonst Fehler
Lösche den Sprint mit id42
Hinweise: noch sauberer wäre /sprint/42 (Einzahl)graue Felder nicht realisiert
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
530
Einordnung SprintRestController (Server)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
531
Client (minimal)
Komponentenbasierte Software-Entwicklung SprinterRESTClient
Prof. Dr. Stephan Kleuker
532
Vorbereitung im Server
@Stateless // oder @Singleton
@Path("")
public class SprintRestController implements Serializable{
@Inject
private PersistenzService pers;
@Context
private UriInfo uriInfo; // später genauer
private SimpleDateFormat formatter
= new SimpleDateFormat("dd.MM.yyyy");
public SprintRestController() {
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
533
Hilfsmethode zum Sprint einpacken
private JsonObject jsonSprint(Sprint s, boolean einzeln) {
String idzeigen = (einzeln) ? "" : "" + s.getId();
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("id", s.getId())
.add("motto", s.getMotto())
.add("starttermin", formatter.format(s.getStarttermin()))
.add("endtermin", formatter.format(s.getEndtermin()))
.add("geplanterAufwand", s.getGeplanterAufwand())
.add("farbe", s.color())
.add("link", uriInfo.getAbsolutePathBuilder()
.path(idzeigen + "/backlogElemente")
.build().getPath());
return js.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
534
GET /sprints (1/2)
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/sprints")
public JsonObject getSprints(
@DefaultValue("-1") @QueryParam("von") int von,
@DefaultValue("-1") @QueryParam("bis") int bis) {
List<Sprint> alle = pers.findAllSprint();
if (von < 0 || von >= alle.size()) {
von = 0;
}
if (bis < 0 || bis >= alle.size()) {
bis = alle.size() - 1;
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
535
GET /sprints (2/2)
JsonArrayBuilder elemente = Json.createArrayBuilder();
for (int i = von; i <= bis; i++) {
elemente.add(jsonSprint(alle.get(i), false));
}
return Json.createObjectBuilder()
.add("sprints", elemente)
.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
536
Client – Vorbereitung (1/2)
• Client braucht keine echte Datenhaltung
• Ansatz: Daten lokal in SessionScope halten (für kleinere Datenmengen ok
@Named
@SessionScoped
public class SprintController implements Serializable {
private Client client;
private List<Map<String, Object>> sprints;
private final static String[] keys = {"id", "motto"
, "starttermin", "endtermin", "geplanterAufwand"
, "link", "farbe"};
private final static String SPRINTS
= "http://localhost:8080/Sprinter/resources/sprints";
private final static String HOME = "index";
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
537
Client – Vorbereitung (2/2)
• eine Variable pro Eigenschaft mit get und setenum Status {BASIC, EDIT;}
private long id;
private String motto;
private Date starttermin;
private Date endtermin;
private int geplanterAufwand;
private Status modus;
private String meldung = ""; // Statusmeldung ohne Voodoo
SimpleDateFormat formatter
= new SimpleDateFormat("dd.MM.yyyy");
public SprintController() {
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
538
Client – Initialisierung (1/2)
@PostConstruct
public void init() {
this.modus = Status.BASIC;
this.client = ClientBuilder.newClient();
WebTarget wt = client.target(SPRINTS);
Invocation.Builder buil = wt
.request(MediaType.APPLICATION_JSON);
JsonObject ergebnis = buil.get(JsonObject.class);
JsonArray array = ergebnis.getJsonArray("sprints");
this.sprints = new ArrayList<Map<String, Object>>();
for (JsonValue val : array) {
JsonObject js = (JsonObject) val;
// speichert einen String als Attribut/Wert-Paar
Map<String, Object> werte = new HashMap<String, Object>();
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
539
Client – Initialisierung (2/2)
for (String k : keys) {
werte.put(k, js.get(k));
}
this.sprints.add(werte);
}
this.motto = "";
this.starttermin = null;
this.endtermin = null;
this.geplanterAufwand = 0;
} in älteren Versionen (Übung !), überflüssige " entfernen for (String k : keys) {
Object tmp = js.get(k);
String txt = tmp.toString();
if(txt.startsWith("\"") && txt.endsWith("\"") && txt.length() > 1){
tmp = txt.substring(1, txt.length()-1);
}
werte.put(k, tmp);
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
540
Erfolgloses Löschen möglich
• vom anderen Nutzer gelöscht oder modifiziert
• Idempotent wäre, diesen Fehler zu ignorieren (ist gelöscht)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
541
Server Action loeschen
@DELETE
@Path("/sprints/{id}")
public JsonObject loeschen(@PathParam("id") long id) {
pers.removeSprint(id);
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("status", "geloescht");
return js.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
542
Client löschen
public String loeschen(Object sid) {
System.out.println("loeschen: " + sid);
WebTarget wb = client.target(SPRINTS + "/" + sid);
Invocation.Builder build = wb
.request(MediaType.APPLICATION_JSON);
try {
JsonObject ergebnis = build.delete(JsonObject.class);
this.meldung = "loeschen erfolgreich: " + ergebnis;
} catch (Exception e) {
this.meldung = "loeschen gescheitert: " + e;
}
init();
this.modus = Status.BASIC;
return HOME;
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
543
Neuer Sprint – Server (1/2)
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/sprints")
public JsonObject hinzufuegen(JsonObject jo) {
Sprint sprint = new Sprint();
sprint.setMotto(jo.getString("motto"));
sprint.setGeplanterAufwand(jo.getInt("geplanterAufwand"));
SimpleDateFormat formatter
= new SimpleDateFormat("dd.MM.yyyy");
try {
sprint.setStarttermin(formatter
.parse(jo.getString("starttermin")));
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
544
Neuer Sprint – Server (2/2)
sprint.setEndtermin(formatter
.parse(jo.getString("endtermin")));
} catch (ParseException ex) {
return null;
}
pers.persist(sprint);
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("link"
, uriInfo.getAbsolutePathBuilder()
.path(sprint.getId() + "/backlogElemente")
.build().getPath());
return js.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
545
Neuer Sprint – Client Aktion uebernehmen (1/4)
public String uebernehmen() {
// Validerung des Clients muss dieser regeln
if (this.starttermin == null || this.endtermin == null){
this.meldung = "Start- und Endtermin angeben!";
return HOME;
}
if (this.starttermin.compareTo(this.endtermin) > 0){
this.meldung = "Endtermin nicht vor Starttermin";
return HOME;
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
546
Neuer Sprint – Client Aktion uebernehmen (2/4)
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("motto", this.motto)
.add("starttermin", formatter.format(this.starttermin))
.add("endtermin", formatter.format(this.endtermin))
.add("geplanterAufwand", this.geplanterAufwand);
if (this.modus.equals(Status.BASIC)) {
neuerSprint(js);
}
if (this.modus.equals(Status.EDIT)) {
editiereSprint(js);
}
init();
this.modus = Status.BASIC;
return HOME;
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
547
Neuer Sprint – Client Aktion uebernehmen (3/4)
private void neuerSprint(JsonObjectBuilder js){
WebTarget wb = client.target(SPRINTS);
Invocation.Builder build = wb
.request(MediaType.APPLICATION_JSON);
Entity entity = Entity.entity(js.build()
, MediaType.APPLICATION_JSON);
try {
JsonObject ergebnis = build.post(entity, JsonObject.class);
this.meldung = "einfuegen erfolgreich: " + ergebnis;
} catch (Exception e) {
this.meldung = "einfuegen gescheitert: " + e;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
548
Sprint editieren – Server (1/2)
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/sprints/{id}")
public JsonObject aktualisieren( @PathParam("id") long id
, JsonObject jo) {
Sprint sprint = pers.findSprint(id);
sprint.setMotto(jo.getString("motto"));
sprint.setGeplanterAufwand(jo.getInt("geplanterAufwand"));
try {
sprint.setStarttermin(formatter
.parse(jo.getString("starttermin")));
sprint.setEndtermin(formatter
.parse(jo.getString("endtermin")));
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
549
Sprint editieren – Server (2/2)
} catch (ParseException ex) {
return null;
}
pers.merge(sprint);
JsonObjectBuilder js = Json.createObjectBuilder();
js.add("link"
, uriInfo.getAbsolutePathBuilder()
.path("/backlogElemente").build().getPath());
return js.build();
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
550
Editiere Sprint – Client Aktion uebernehmen (4/4)
private void editiereSprint(JsonObjectBuilder js) {
WebTarget wb = client.target(SPRINTS + "/" + this.id);
Invocation.Builder build = wb
.request(MediaType.APPLICATION_JSON);
js.add("id", id);
Entity entity = Entity.entity(js.build()
, MediaType.APPLICATION_JSON);
try {
JsonObject ergebnis = build.put(entity, JsonObject.class);
this.meldung = "aktualisieren erfolgreich: " + ergebnis;
} catch (Exception e) {
this.meldung = "aktualisieren gescheitert: " + e;
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
551
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
552
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
553
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
554
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
555
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
556
@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
557
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
558
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
559
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
560
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