Initialisierung von JSF-Managed-Beans

Allgemein bekannt ist die Tatsache, dass Managed-Beans in der Faces-Config-XML-Datei nicht nur deklariert, sondern auch initialisiert werden können. Schon weniger bekannt ist die Tatsache, dass bei der Initialisierung der Beans auch Verknüpfungen zu anderen Managed-Beans hergestellt werden können. Zur Verdeutlichung soll der folgende Code-Schnipsel dienen:

<faces-config version="1.2" ...>
  <managed-bean>
    <managed-bean-name>user</managed-bean-name>
    <managed-bean-class>
            my.app.model.Person</managed-bean-class>
    <managed-bean-scope>application</managed-bean-scope>
    <managed-property>
      <property-name>homeAddress</property-name>
      <value>#{address}</value>
    </managed-property>
  </managed-bean>
  <managed-bean>
    <managed-bean-name>address</managed-bean-name>
    <managed-bean-class>
            my.app.model.Address</managed-bean-class>
    <managed-bean-scope>application</managed-bean-scope>
    <managed-property>
      <property-name>city</property-name>
      <value>Dresden</value>
    </managed-property>
    <managed-property>
      <property-name>street</property-name>
      <value>Leipziger Str. 93</value>
    </managed-property>
  </managed-bean>
</faces-config>

WEB-INF/faces-config.xml: Initialisierung einer Managed-Bean

In Zeile 9 wird die Verbingung zwischen der Bean „user“ und der Bean „address“ über die Eigenschaft „homeAddress“ hergestellt.

Wie lässt sich allerdings die Sache angehen, wenn man für das Address-Objekt keine eigenständige Bean deklarieren möchte?

Property-Editoren

Eine vereinfachte Initialisierung der user-Bean könnte so aussehen:

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>my.app.model.Person</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
  <managed-property>
    <property-name>homeAddress</property-name>
    <value>Leipziger Straße 93, Dresden</value>
  </managed-property>
</managed-bean>

WEB-INF/faces-config.xml: vereinfachte Bean-Initialisierung

Statt des Verweises auf eine andere Managed-Bean werden in Zeile 7 alle notwendigen Informationen angegeben, um die abhängige address-Bean direkt zu initialisieren. Allerdings quittiert die JSF-Umgebung diesen Versuch mit:

java.lang.IllegalArgumentException: 
Cannot convert Leipziger Straße 93, Dresden 
  of type class java.lang.String 
  to class model.Address

Ein Blick in die JSF-Spezifikation (Seite 5-7 oben) zeigt, dass der String zwischen den <value>-Tags nach den Regeln der JSP-Spezifikation behandelt wird. Es gelten demnach die selben Regeln wie für <jsp:setProperty>.

Im Abschnitt JSP.1.14.2.1 Conversions from String values (Seite 1-62) sagt die JSP-Spezifikation aus, dass:

String values can be used to assign values to a type that has a PropertyEditor class as indicated in the JavaBeans specification. When that is the case, the setAsText(String) method is used.

Unsere Suche geht damit in der JavaBeans Spezifikation weiter. Im Abschnitt 9.2 Property editors, bzw. 9.2.1 Locating property editors findet man all die Möglichkeiten, um Property-Editoren zu definieren:

  • Registrierung via java.beans.PropertyEditorManager
  • Namenskonvention: my.package.MyBeanEditor
  • Namenskonvention: MyBeanEditor in den definierten Such-Paketen des PropertyEditorManagers

Für den vorliegenden Fall ist es ausreichend, eine Klasse my.app.model.AddressEditor anzulegen. Sie implementiert gemäß JSP-Spezifikation die Methode setAsText(String) folgendermaßen:

package my.app.model;

import java.beans.PropertyEditorSupport;

public class AddressEditor extends PropertyEditorSupport {
  @Override
  public void setAsText(String text) 
                        throws IllegalArgumentException {
    String[] split = text.split(",\\s*");
    Address newAddress = new Address(split[0], split[1]);

    setValue(newAddress);
  }
}

Das Vorhandensein dieser Klasse ist ausreichend dafür, dass die „Kurzschreibweise“ für die Initialisierung der address-Bean nun klaglos funktioniert. Die eigentliche Initialisierung übernimmt der Konstruktor Address(String street, String city).

Prähistorie

Ein ähnlich gelagertes Problem erhält man, wenn man Properties mit nicht-primitiven Datentypen, wie Locale oder URL, über die Faces-Config-XML-Datei setzen möchte:

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>my.app.model.Person</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
  <managed-property>
    <property-name>homepage</property-name>
    <value>http://www.buschmais.de</value>
  </managed-property>
</managed-bean>

WEB-INF/faces-config.xml: Initialisierung einer URL

Die Herausforderung besteht nun darin, dass eine Klasse namens java.net.URLEditor durch den besonderen Schutz des java.*-Namensraums nicht definiert werden darf. Bleiben noch die Möglichkeiten „Registrierung“ und „Such-Pfad“ für die Definition des URL-Property-Editors.

Ein Blick in den Quelltext der Methode PropertyEditorManager#register​Editor(Class, Class) lässt sehr schnell deutlich werden, dass auch die Registrierung eines Property-Editors keine Option ist. Dieser Code scheint noch aus den Anfängen von Java zu stammen und wurde seitdem nur wenig angepasst. Die Editoren werden in einer Hashtable abgelegt, so dass Speicherprobleme aufgrund von ClassLoader-Lecks bei Web-Anwendungen unausweichlich sind.

Bleibt die Alternative „Such-Pfad“. Für ihre Umsetzung muss der zusätzliche Such-Pfad am PropertyEditorManager registriert werden. Hierfür bietet sich ein (Pseudo-)Servlet als Lifecycle-Listener an. Damit ist sichergestellt, dass Registrierung und De-Registrierung mit den Deployment und Undeployment der Anwendung stattfinden. Außerdem kann über das <load-on-startup>-Tag beinflusst werden, dass die Initialisierung auf jeden Fall vor der Initialisierung von JSF stattfindet (Zeilen 5 und 11):

<web-app version="2.5" ...>
  <servlet>
    <servlet-name>InitServlet</servlet-name>
    <servlet-class>my.app.servlets.InitServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet>
    <servlet-name>FacesServlet</servlet-name>
    <servlet-class>
            javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
  </servlet>
</web-app>

WEB-INF/web.xml: Registrierung eines Init-Servlets

Die Implementierung des Init-Servlets ist sehr geradlinig. Es ist lediglich darauf zu achten, dass bei Redeployments der Suchpfad nicht immer weiter anwächst:

public class InitServlet extends GenericServlet {

  private static final long serialVersionUID = -123;

  @Override
  public void init(ServletConfig config)
                   throws ServletException {
    super.init(config);
    synchronized (PropertyEditorManager.class) {
      String[] searchPath = PropertyEditorManager
               .getEditorSearchPath();
      HashSet<String> searchPathAsSet = new HashSet<String>(
               Arrays.asList(searchPath));
      searchPathAsSet.add("my.app.logic");

      PropertyEditorManager.setEditorSearchPath(
               searchPathAsSet.toArray(new String[0]));
    }
  }

  @Override
  public void destroy() {
    synchronized (PropertyEditorManager.class) {
      String[] searchPath = PropertyEditorManager
               .getEditorSearchPath();
      HashSet<String> searchPathAsSet = new HashSet<String>(
               Arrays.asList(searchPath));
      searchPathAsSet.remove("my.app.logic");

      PropertyEditorManager.setEditorSearchPath(
               searchPathAsSet.toArray(new String[0]));
    }
    super.destroy();
  }

  @Override
  public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException {
    throw new UnsupportedOperationException();
  }
}

my.app.servlets.InitServlet: Implementierung des Init-Servlets

Der Property-Editor für den URL-Typ wartet dank String-Konstruktor auch mit kaum einer Überraschung auf:

public class URLEditor extends PropertyEditorSupport {
  @Override
  public void setAsText(String text)
                       throws IllegalArgumentException {
    URL url = null;
    try {
      url = new URL(text);
    } catch (MalformedURLException e) {
      throw new RuntimeException(e);
    }
    setValue(url);
  }
}

my.app.logic.URLEditor: Property-Editor für java.net.URL

Mit diesen Änderungen wird die URL-Property „homepage“ erfolgreich initialisiert.

An dieser Stelle sei mir noch der Hinweis auf unser Seminar Enterprise Java für Architekten gestattet. Ein Bestandteil dieses Seminars ist die Erstellung von Web-Anwendungen mittels JavaServer Faces. Die aktuellen Termine erfahren Sie ebenfalls auf der Kurs-Seite.

Kommentare sind abgeschaltet.