Category Archives: 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.
(weiterlesen…)

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.
(weiterlesen…)

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

 

© 2010 codecentric