Web

JSP Tag Pooling Memory Leaks

JSP Custom Tags waren früher verbreitet im Einsatz. Obwohl man sie heute nurnoch vereinzelnd schreibt, findet sich ein spezielles Problem in fast jedem Projekt in dem JSP Custom Tags eingesetzt werden.
Da in Produktion der eingesetzte Web Container die benutzten Tags poolen wird, muss man beim Schreiben von JSP Custom Tags den Lebenszyklus (lifecycle) berücksichtige. Das Tagpooling ist von der Spezifikation erlaubt und sogar empfohlen, verursacht aber Probleme wenn man daran nicht denkt. Speichert man große Objekte in Tags, verursacht man ein Memory Leak welches den Server abstürzen lässt (oder es passiert nichts schlimmes, für den Fall daß der Pool oder das Objekt klein genug ist). In der Entwicklungsumgebung fällt dies auch oft nicht auf.

Der Verursacher sieht meist so aus wie dieser Codeschnipsel:

public class MyTag extends TagSupport {
  public Object leak;
  public int doStartTag(){
    leak = new BigObject();
  }
}

Erzeugt wird das Problem dann durch diesen Lebenszyklus:

  1. Klasse laden
  2. Instanz erzeugen
  3. setPageContext() aufrufen
  4. Setter aufrufen
  5. doStartTag aufrufen
  6. In Abhängigkeit von Typ und Rückgabewerten andere Methoden aufrufen
  7. doEndTag() aufrufen
  8. Die Instanz in den Pool legen

Wenn nun der gleiche Tag wieder benutzt wird darf der Container bei Schritt 3 beginnen. Wenn nun der Tagpool auf 10 Objekte eingestellt ist und der gleiche Tag durch 10 simultane Anfragen erzeugt wird, landen 10 Instanzen im Pool. Folgen dann aber nur sporadisch Anfragen, dümpeln 10 Instanzen mit einer Referenz auf das große Objekt im Pool. Und schon haben wir unser Memory Leak.

Man kann dies aber ganz einfach vermeiden indem man “transiente” Variablen am Ende auf null setze und sie in setPageContext() oder doStartTag() neu befüllt. Am Rande sei hier angemerkt, daß der Konstruktor eines Tags nur einmal gerufen werden könnte, obwohl der gleiche Tag auf hunderten von Seiten verwendet wird. Wie viele Instanzen erzeugt, und damit wie viele Konstruktoren aufgerufen, werden hängt von den Einstellungen des Containers, des Pools und der Serverlast ab.

public class MyTag extends TagSupport {
  public Object noLeak;
 
  public void setPageContext(PageContext pc){
    noLeak = new BigObject();
  }
 
  public int doStartTag(){
  }
 
  public void doEndTag(){
    noLeak = null;
  }
}

Weitere und bessere Alternativen wären diese Objekte so lokal wie möglich zu machen. Der Tag selbst sollte nur seine gesetzten Attribute kennen.
Ausser dem bisher beschriebenen Memoryleak entsteht noch ein weiteres Problem. Nehmen wir mal an ein Objekt wird nur gesetzt wenn ein bestimmtes Attribut einen bestimmten Wert hat. Ist dies der Fall, wird das Objekt aber nicht wieder entfernt, so findet man das Objekt bei der nächsten Benutzung des Tags wieder, obwohl die Bedingung auf das Attribut nicht mehr zutrifft.

Fabian Lange

 

Multiple Selects mit Spring MVC

Während unserer coding night, zu der mit Sicherheit noch ein separater Blogeintrag folgt, setzen wir voll auf Spring Technologien. Dabei hat sich eine vermutlich simple Anforderung als ziemlich halsbrecherisch herausgestellt.

Wie kann man mit Spring MVC eine Select box darstellen, bei der man multiple Elemente auswählen kann, welche dann in eine Collection der Bean hinzugefügt werden?

Wir implementiere eine einfache Zeiterfassung. In dem Domänenmodell gibt es Projekte, denen Mitarbeiter zugeordnet sind. Zudem können Projekte Aufgaben haben, denen auch Mitarbeiter zugeordnet sein können. In der View zum erstellen der Aufgaben benötigen wir also eine Selectbox aus allen verfügbaren Mitarbeitern.

Modell

Hier das wichtigste der Aufgaben und Mitarbeiter:

Task

@Entity
public class Task implements Serializable {
	@Id
	@GeneratedValue
	private long id;
 
	@ManyToMany
	private Set<Staff> staffs = new HashSet<Staff>(0);
 
	//...
}

Staff

@NamedQuery(name = "staff.activeStaff", query = "select s from Staff s where s.disabled = false")
@Entity
@DiscriminatorValue("STAFF")
public class Staff extends Person {
 
	//...
}

Person

@NamedQuery(name = "person.findByUsername", query = "from Person p where p.login.username = :username")
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Person implements Serializable {
 
	@Id
	@GeneratedValue
	private long id;
 
	//...
}

View

In der view, in der die Tasks erstellt werden sollen, brauchen wir also eine select box, welche alle verfügbaren Mitarbeiter anzeigt. Die ausgewählten Mitarbeiter sollen dann der Aufgabe hinzugefügt werden. Die View ist mit der Task hinterlegt, und fügt alle aktiven Mitarbeiter zu einem Attribut hinzu:

@Controller
@SessionAttributes("project")
public class ProjectController {
	@RequestMapping(method = RequestMethod.GET, value = "/project/createTask.action")
	public void createTaskView(@ModelAttribute Task task, Model model) {
		List<Staff> activeStaff = timetrackingService.getActiveStaff();
		model.addAttribute("activeStaff", activeStaff);
	}
 
	//...

Die entsprechende JSP sieht folgendermaßen aus:

<td><label for="staffs">Chose Staff: </label></td>
<td><form:select path="staffs" multiple="true" items="${activeStaff}" itemLabel="fullName" itemValue="id"/></td>
<td><form:errors path="staffs" /></td>

Das Problem, sobald man die Form submitted, bekommt man eine ServletRequestBindingException, denn Spring weiß nicht wie man aus einem String (bzw. String[] wenn mehrere Personen markiert waren) ein Set erstellt.

org.springframework.web.bind.ServletRequestBindingException: Errors binding onto object 'task'; nested exception is org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'task' on field 'staffs': rejected value [3]; codes [typeMismatch.task.staffs,typeMismatch.staffs,typeMismatch.java.util.Set,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [task.staffs,staffs]; arguments []; default message [staffs]]; default message [Failed to convert property value of type [java.lang.String] to required type [java.util.Set] for property 'staffs'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [de.codecentric.timetracking.model.Staff] for property 'staffs[0]': no matching editors or conversion strategy found]

Zudem ist der generierte HTML-code lückenhaft, er enthält keine id für die einzelnen Mitarbeiter!

<select id="staffs" multiple="multiple" name="staffs">
<option value="">firstname lastname</option>
<option value="">aaaa aaaa</option>
<option value="">firstname lastName</option>
<option value="">firstname aaaa</option>
<option value="">firstname lastname</option>
</select>
<input type="hidden" value="1" name="_staffs"/>

Id als String

Um die ID des Mitarbeiters in das value-Attribut der option zu bekommen, kann man einen eigenen Getter auf der Person implementieren, und diesen dann in der JSP statt der ID verwenden:

public abstract class Person implements Serializable {
	//...
	public String getIdAsString() {
		return new Long(id).toString();
	}
}
<form:select path="staffs" itemValue="idAsString" multiple="true" items="${activeStaff}" itemLabel="fullName"/>

InitBinder

Die Lösung für das Binding-Problem ist, dass wir für das Attribut ’staffs’ einen eigenen PropertyEditor registrieren müssen. Spring bringt eine Reihe eigener PropertyEditoren mit, in diesem Falle können wir den CustomCollectionEditor wiederverwenden. Um von der String-ID wieder auf den Mitarbeiter mappen zu können, müssen wir uns auch noch eine Map initialisieren, die dieses Mapping vorhält.

public class ProjectController {
 
private Map<String, Staff> staffCache;
 
@RequestMapping(method = RequestMethod.GET, value = "/project/createTask.action")
public void createTaskView(@ModelAttribute Task task, Model model) {
	List<Staff> activeStaff = timetrackingService.getActiveStaff();
	staffCache = new HashMap<String, Staff>();
	for (Staff staff : activeStaff) {
		staffCache.put(staff.getIdAsString(), staff);
	}
	model.addAttribute("activeStaff", activeStaff);
}
 
@InitBinder
protected void initBinder(WebDataBinder binder) throws Exception {
	binder.registerCustomEditor(Set.class, "staffs", new CustomCollectionEditor(Set.class) {
		protected Object convertElement(Object element) {
			if (element instanceof Staff) {
				System.out.println("Converting from Staff to Staff: " + element);
				return element;
			}
			if (element instanceof String) {
				Staff staff = staffCache.get(element);
				System.out.println("Looking up staff for id " + element + ": " + staff);
				return staff;
			}
			System.out.println("Don't know what to do with: " + element);
			return null;
		}
	});
}

Der Code enthält noch etwas Debug-output nach System.out, der natürlich noch entfernt werden muss. Er zeigt aber sehr schön, dass der Code sehr (zu?) häufig aufgerufen wird. Außerdem wird erwartetn, dass die Property in beide Richtungen konvertiert werden kann!

Allein wenn die select box angezeit werden soll, steht folgendes im Log:

Looking up staff for id 1: Staff(firstname lastname)
Looking up staff for id 1: Staff(firstname lastname)
Converting from Staff to Staff: Staff(firstname lastname)
Looking up staff for id 2: Staff(aaaa aaaa)
Looking up staff for id 2: Staff(aaaa aaaa)
Converting from Staff to Staff: Staff(aaaa aaaa)
Looking up staff for id 3: Staff(firstname lastName)
Looking up staff for id 3: Staff(firstname lastName)
Converting from Staff to Staff: Staff(firstname lastName)
Looking up staff for id 4: Staff(firstname aaaa)
Looking up staff for id 4: Staff(firstname aaaa)
Converting from Staff to Staff: Staff(firstname aaaa)
Looking up staff for id 5: Staff(firstname lastname)
Looking up staff for id 5: Staff(firstname lastname)
Converting from Staff to Staff: Staff(firstname lastname)

Wenn man einen Mitarbeiter aus der Select-Box auswählt und die Form submitted, findet sich dann aber wie erwartet nur ein Eintrag im Log:

Looking up staff for id 3: Staff(firstname lastName)

Fazit

Angesichts der Masse an Webframeworks, die es gibt, finde ich es erschreckend Kompliziert so etwas einfaches wie eine multiple select Box mit Spring MVC zu implementieren. Da ich mir Spring MVC aber auch erst seit gestern genauer angesehen habe, verstehe ich es vielleicht noch nicht richtig, von daher bin ich sehr für Vorschläge zu haben, wie man das beschriebene Szenario mit Spring MVC-Mitteln eleganter und einfacher implementieren kann.

Andreas Ebbert-Karroum

 

Java Framework Marketing

Der erste Eindruck ist wichtig, denn er ist entscheidend für unsere Erwartungen. Wenn man sich nach einer neuen Technologie, oder einem Framework umsieht, so sind Erfolgsgeschichten anderer Nutzer ein wichtiger Faktor um Vertrauen aufzubauen. Natürlich ist es fraglich immer anderen Firmen nachzulaufen, aber das Risiko der allererste Nutzer zu sein ist doch sehr hoch. Aber nicht nur solche Case Studies, sondern auch eine echte Community (also kein totes Forum) mit Feeds, Blogs und vielem mehr, sollten heute auf jeder Framework Webseite zu finden sein, zeigt eine wohl ausgestattete Webseite doch transparent die aktive Entwicklung und Pflege. Natürlich sollte die Seite auch nett und ansprechend gemacht sein, idealerweise mit dem Framework selbst.

Wie aber kümmern sich die Großen unter den Java Frameworks um den ersten Eindruck?

  • Tapestry, ein schönes Framework mit vielen Generatoren, hat auf seiner Seite keinen Marketing Bereich, man findet nur recht schnell ein herunterladbares Logo, mit welchem man auf seiner Seite zeigen kann, daß sie mit Tapestry läuft. Die Dokumentationsseiten sehen sehr statisch aus, das Wiki etwas unübersichtlich, jedoch enthüllt es nach etwas Recherche eine Liste von Referenzen. Zumindest ist die Seite sauber strukturiert und offensichtlich aktuell.
  • Struts, in Version 2, ist der Nachfolger des wahrscheinlich am häufigsten benutzten Java Web Framework. Alte und teilweise falsche Informationen, präsentiert auf einer ganz und gar nicht ansprechenden Seite. Selbst wenn man weiß wonach man sucht findet man es im Wust doppelter (wohl geschuldet an die Zusammenführung von WebWork und Struts2) und veralteter Seiten nicht immer.
  • Java Server Faces, vertreten durch den größten “Community Player”: MyFaces, hat leider auch wenig Marketing. MyFaces hat dabei sogar erst kürzlich die Seiten komplett neu gestaltet. Dennoch finden sich keine Informationen über Kunden, oder gar Demonstrationen. Sehr viele technische Informationen (mit der JSF Spec gleich auf der Startseite), jedoch kein Versuch von dem Nutzen von MyFaces zu überzeugen.
  • Spring MVC oder Web Flow haben keine echten eigenen Seiten, sie sind als Unterpunkte auf der SpringSource Seite gelistet. Exzellente Dokumentation und Foren helfen Entwickler, überzeugen aber wohl keine Entscheider.
  • Wicket, ein Java Web Framework welches anders sein will, ist da schon besser aufgestellt. Blogs, verschiedene Themenfeeds, Live Demos und eine aktive Community zeichnen ein sehr gutes Bild von dem Framework. Viel Information verschiedenster Art wird übersichtlich präsentiert.

Ein kleiner Blick auf die Kontrahenten: Die dynamischen / Scripting Sprachen. Ich wähle einen Vertreter jeder Gattung:

  • Das PHP “symfony framework” berichtet häufig über große Installationen, z.B bei dailymotion, Yahoo! Answers, Bookmarks und Delicious. Ein Blog, Forum, Wiki (welches aber mal aufgeräumt werden könnte), Screencasts, und Unmengen von Dokumentation, welche in einer Vielzahl von Sprachen erhältlich ist, sind verfügbar. Und die Seite ist selbst mit symfony erstellt. Vom Design her gehe die kürzlich veröffentlichten standalone components einen weiteren Schritt in Richtung ansprechender Präsentation.
  • Ruby on Rails hat sogar eine application gallery, in der große Referenzen wie Twitter, Basecamp oder die yellow pages vorgestellt werden. Hervorragende screencasts, anwendergeschriebene How-tos und ein Blog halten die Community informiert und bindet sie ein. “Get Excited” ist der Titel des ersten Unterbereiches – eine sehr verführerische Aufforderung…
  • Django, ein Python basiertes Web Framework, hat sogar eine eigene Seite, welche Anwenundgen “powered by django” präsentiert, auch wenn sie nicht nach Wichtigkeit oder Größe sortiert sind. Es gibt ebenfalls ein Blog und ein großes Community Wiki. Obwohl das Layout etwas gewöhnungsbedürftig ist und die Seite selst sehr textlastig, fällt die Suche leicht.

Mit der Ausnahme von Wicket musste ich bei allen Java Web Frameworks feststelle, daß die Seiten sich an Entwickler wenden welche das Framework bereits kennen. Marketing betreiben sie in keiner Form. Ich weiß nicht warum dem so ist, jedenfalls sind das meine Beobachtungen. Vielleicht liegt es daran, daß:

  • … Java eine Sprache für Geschäftsanwendungen ist. Es gibt kein Interesse an dem Web Massenmarkt.
  • … Java von Technikern entwickelt ist: Gute Dokumentation aber keine Werbemaßnahmen.
  • … Java ganz anders als das Web skaliert. Ist eine “shared nothing” Architektur besser als die von Java favorisierten Cluster?
  • … es zu viele große Mitspieler in der Java Welt gibt. In anderen Sprachen sind die Top frameworks leichter zu finden.
  • … Java Entwickler lieber Code als Dokumentation und Werbung schreiben.
  • … Java Frameworks Webseiten entweder mit java.net, apache.org oder maven-site Stil gebaut werden. Dieser Stil ist zwar stukturiert und kann generiert werden, ist aber überhaupt nicht ansprechend.

Da ich ein großer Fan von Java Frameworks bin macht mich dies natürlich traurig, daher hier meine Ideen um Abhilfe zu schaffen:

  • Tue Gutes und sprich darüber. Es gibt keinen Grund zu übertreiben, aber zeigt was ihr könnt.
  • Euer Framework ist toll! Nutzt es und macht Eure Webseiten damit. Eine Bessere demonstration kann es nicht geben.
  • Benutzt aktuelle Tools, wie ein Blog, RSS feeds und ein Wiki (schonmal vom web 2.0 gehört?)
  • Seid transparent – nutzt einen schnellen und einfach zu bedienenden Bugtracker (tschüß bugzilla).
  • Macht ein einfaches Design. Es gibt sehr viele freie schöne ansprechende Designs. Denkt daran: Ihr präsentiert Euch und Eure Arbeit.
Fabian Lange

 

Struts2 Interfaces und EJB Probleme

Heute schreibe ich über ein Problem welches mir kürzlich mit Struts2 begegnet ist. Es ist allerdings auch gültig für viele andere Szenarien. Hier ein Beispiel:

Die Wahlen in den Staaten nähern sich, deshalb wollen wir heute eine kleine Vorabstimmung machen.

Wir bauen dafür einen Service, welcher 3 Methoden enthält:

public interface ElectionFavorite {
  public List getFavs(int userId);
  public List getChoices();
  public void setFavs(int userId, List);
}

Sieht doch ganz einfach aus. Unsere Auswahl sind mehrere Top Kandidaten, unter anderem Homer Simpson, Dilbert und Captain Kirk. Ich denke diese wären wohl alle eine gute Wahl.

Als Oberfläche für die Abstimmung schreiben wir eine kleine Struts2 Anwendung und benutzen die CheckboxList um die Wahlmöglichkeiten darzustellen. Die beiden Getter des Interfaces, welches wir als EJB realisieren, verwenden wir um die Möglichkeiten zu laden und falls vorhanden auch eine bereits getätigte Auswahl eines Users anzuzeigen.

Die Anwendung funktioniert prima, bis wir speichern:

NoClassDefFoundException: com.opensymphony.xwork.util.XWorkList not found

Wie kann das passieren? Nun Struts2 muss einiges an Konvertierungsmagie für uns durchführen. Der HTML Request liefert leider nur ein HTML Array der Auswahl ab, wir wollten aber eine Liste haben. Also baut Struts2 uns eine Liste. Reichen wir diese Liste nun weiter an unsere Implementierung des ElectionFavorite findet sich diese Liste wahrscheinlich nicht auf dem Classpath. Je nach Classpathkonfiguration kann das Beispiel noch funktionieren, aber spätestens bei einem getrennten Service EAR und der Webapplikation in einem WAR ist dann auch Schluss.

Um das ganze ans Laufen zu bekommen, muss man auf Clientseite in der Struts2 Action z.B. einen Proxy verwenden welcher die Liste umwandeln kann:

setFavs(List favs){
  this.favs = new ArrayList();
  this.favs.addAll(favs);
}

Die Moral dieser Geschichte ist, dass Interfaces eine tolle Sache für APIs sind, weil es die Benutzung verschiedener Implementierungen erlaubt, aber wenn genau diese Implementierung nicht überall vorhanden ist können Probleme entstehen. Es gibt grob gesagt 3 Möglichkeiten dieses Problem zu lösen:

  1. Stellt sicher dass alle Klassen von Clients auch auf Serviceseite verfügbar sind
  2. Eine Konvention für das jeweilige Projekt nach der nur JDK Implementierungen für Interfaces übergeben werden dürfen
  3. Benutzt keine Interfaces in Euren Service APIs

Keine dieser Optionen ist eine einfache Wahl. Leider. Vielleicht hat ja jemand eine viel bessere Idee?

(Möglichkeit 3 funktioniert durch die Magie innerhalb von Struts2 leider auch nicht)

Fabian Lange

 

Tomcat 6 Vortrag von Peter Roßbach bei der RheinJUG

Donnerstag hat Peter Roßbach, Entwickler im Tomcat Projekt, auf der RheinJUG in Düsseldorf einen Vortrag gehalten. Dabei handelte es sich nicht um einen langweiligen trockenen technischen Talk, sondern um ein lockeres Potpourri aus Tomcat Architektur, Best Practices, Open Source Community sowie aktueller und zukünftiger Entwicklung.

Performanceoptimierung im Tomcat

Zum Thema Performance nannte er als wesentlichen negativen Faktor die Benutzung des Entwicklungsmodus für Produktion. Im folgenden einige weitere Parameter die er mit ihrer Auswirkung auf die Performance beschrieb.

So gibt es im Tomcat 6 eine Verbesserte Protokollimplementierung:

 

außerdem ermöglicht Java NIO eine bessere Dateidownload Option. Servlets sollten dafür nur die Sendfile Request Parameter setzen und keine weiteren Daten ausgeben:

 org.apache.tomcat.sendfile.filename: Canonical filename of the file which will be sent as a String
 org.apache.tomcat.sendfile.start: Start offset as a Long
 org.apache.tomcat.sendfile.end: End offset as a Long

Produktionsserver sollten vor allem nicht automatisch deployen:

 

Der Jasper JSP Compiler sollte ebenfalls getuned werden:

  jsp
  org.apache.jasper.servlet.JspServlet
 
development
false
 
genStringAsCharArray
true
 
trimSpaces
true

mod_jk zum Loadbalancing

Als zweiten Schwerpunkt stellte er das Modul “mod_jk” vor, welches in Verbindung mit einem Apache Httpd und dem Tomcat AJP Protokoll dafür sorgt, daß Java Webapplikationen sich sinnvoll auf Tomcat Server verteilen lassen.

Er erläuterte die verschiedenen Loadbalancing Konfigurationen und daß es durchaus sinnvoll sein kann einen Tomcat Server pro Applikation zu verwenden. Dies begründet er insbesondere mit Memory Management und Ausfallsicherheit.

Tomcat Konfiguration:

 

mod_jk Konfiguration im Httpd:

  LoadModule jk_module "modules/mod_jk.so"
 
JkShmFile "logs/mod_jk.shm"
 
JKWorkerProperty worker.list=loadbalancer
JKWorkerProperty worker.node01.port=8009
JKWorkerProperty worker.node01.host=localhost
JKWorkerProperty worker.node01.type=ajp13
JKWorkerProperty worker.loadbalancer.type=lb
JKWorkerProperty worker.loadbalancer.connection_pool_minsize=0
JKWorkerProperty worker.loadbalancer.connect_timeout=30000
JKWorkerProperty worker.loadbalancer.prepost_timeout=10000
JKWorkerProperty worker.loadbalancer.balance_workers=node01
JKWorkerProperty worker.loadbalancer.method=Request
JKWorkerProperty worker.loadbalancer.retries=2
JKWorkerProperty worker.loadbalancer.recovery_options=7
JKMount /myapps* loadbalancer

Weitere Dokumentation zum mod_jk worker findet sich hier:

http://tomcat.apache.org/connectors-doc/reference/workers.html

Insgesamt ein recht unterhaltsamer Vortrag, aus dem man einiges lernen konnte über das Tomcat Projekt und die Tomcat Architektur, wie Open Source “lebt” und was man als Entwickler/Architekt einer Java Webanwendung so alles beachten sollte.

Fabian Lange

 

Internet Explorer 8 wird neue AJAX-Funktionalität enthalten

Ein Blog-Eintrag bei MSDN berichtet von einer wesentlichen Neuerung im Internet Explorer 8: es wird eine Steuerung der Navigationshistorie per JavaScript geben.

Bisher war es für den Anwender AJAX-basierter Webanwendungen problematisch, vor- und rückwärts durch die Anwendung zu navigieren. Nach dem Laden der Seite wird unter Umständen der Zustand des HTML-Dokuments durch AJAX verändert, zum Beispiel durch dynamisches Nachladen von Texten oder Daten. Klickt der Anwender in seinem Browser auf “zurück”, so springt der Browser zurück zur vorher geladenen Seite in der Anwendung. Damit geht der komplette, per AJAX veränderte Zustand, verloren.

Die neue Implementierung im Internet Explorer 8, adaptiert aus HTML 5, soll dieses Problem beheben. Es wird dann möglich sein, AJAX-basierte Vorgänge mit in die Navigationshistorie aufzunehmen, so dass der Anwender wieder nach dem altbekannten Vor-/Zurück-Prinzip durch die Applikation navigieren kann. Damit wäre ein großes Usability-Problem mit AJAX-Anwendungen gelöst.

Das neue Feature wird hier als Video präsentiert.

Über die Adaption dieser Funktionalität in Firefox oder anderen Browsern gibt es derzeit noch keine konkreten Aussagen. Man wird abwarten müssen, wie sich die einzelnen Hersteller orientieren.

Robert Spielmann

 

Nächste Seite »