Andreas Bruns

Softwareentwicklung für Oldenburg und Bremen

Freemarker: Behandlung von undefinierten Werten

Die Standard-Templatesprache zur Generierung von Webseiten mit Java bilden die
Java Server Pages (JSP). Eine gute Alternative zu JSPs ist die Templatesprache Freemarker, mit der sich eine striktere Trennung zwischen Anwendungslogik und View durchsetzen lässt. Wie die Integration von verschiedenen Templatesprachen mit Spring-MVC erfolgt, habe ich in diesem Artikel beschrieben: Velocity, Freemarker, Jade4J – Alternativen zu JSPs

Beim Einsatz von Freemarker muss man darauf achten, dass alle Modell-Werte korrekt gefüllt sind, damit die Platzhalter im Template korrekt ersetzt werden können. Falls ein Wert nicht vorhanden ist, wird mit der Freemarker-Standardkonfiguration Folgendes passieren:

  1. Freemarker wirft eine Exception: freemarker.core.InvalidReferenceException
  2. die Fehlermeldung wird geloggt
  3. das weitere Generieren der Seite wird abgebrochen
  4. die Webseite zeigt dann eventuell eine halb fertig generierte Seite oder einen Stacktrace der Exception an

Das ist zwar während der Entwicklung ganz praktisch, aber die Anwender sollten natürliche keine fehlerhafte Seite sehen. Zur Vermeidung von Fehlermeldungen, wenn ein Wert nicht gesetzt ist, können wir folgendes tun:

  • Default-Werte im Template definieren
  • einen anderen Freemarker-Exception-Handler verwenden

Für die erste Möglichkeit müssen wir viele Stellen im Template anpassen, was umständlich und fehleranfällig ist. Beispielsweise geht das Setzen von Default-Werten im Template seit Version 2.3.7 mit ${address.city!""} oder für vorherige Versionen mit ${address.city?default("")}.

Eine umfassendere Lösung bildet die zweite Möglichkeit. Wir brauchen lediglich einen anderen Freemarker-Exception-Handler einzusetzen und können dann innerhalb des Exception-Handlers flexibel auf die Ausnahmesituation reagieren. Freemarker ist mit dem TemplateExceptionHandler.DEBUG_HANDLER vorkonfiguriert und bricht damit nach dem ersten Fehler ab. Außerdem werden noch drei weitere Exception-Handlers mitgeliefert:

  • HTML_DEBUG_HANDLER wie DEBUG_HANDLER, aber generiert Meldung als HTML
  • RETHROW_HANDLER loggt die Exception und wirft die Exception weiter
  • IGNORE_HANDLER loggt die Exception und ignoriert sie

Für den produktiven Einsatz empfiehlt sich der IGNORE_HANDLER, weil damit die gesamte Seite trotz fehlender Werte ohne Fehlermeldungen generiert wird. In dem folgenden kleinen Beispiel wird im Java-Quellcode der IGNORE_HANDLER eingesetzt.

Java-Code: FreemarkerExceptionExample.java

package de.bruns.example.freemarker.exception;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;

public class FreemarkerExceptionExample {

	public static void main(String[] args) throws IOException, TemplateException {
		Configuration configuration = new Configuration();
		configuration.setClassForTemplateLoading(FreemarkerExceptionExample.class, "");
		configuration.setObjectWrapper(new DefaultObjectWrapper());
		configuration.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);

		Map<String, Object> addressModel = new HashMap<String, Object>();
		addressModel.put("country", "USA");
		addressModel.put("city", "Springfield");

		Map<String, Object> personModel = new HashMap<String, Object>();
		personModel.put("name", "Homer Simpson");
		// personModel.put("address", addressModel);

		Template temp = configuration.getTemplate("template.ftl");
		Writer out = new OutputStreamWriter(System.out);
		temp.process(personModel, out);
		out.flush();
	}
}

Freemarker-Template: template.ftl

<html>
<body>
  <h1>Freemarker Exception Beispiel</h1>
  <p>
  ${name} lebt in ${address.city} (${address.country}).
  </p>
</body>
</html>

Ausgabe: TemplateExceptionHandler.IGNORE_HANDLER

<html>
<body>
  <h1>Freemarker Exception Beispiel</h1>
  <p>
  Homer Simpson lebt in  ().
  </p>
</body>
</html>

Besonders flexibel können wir mit einem eigenen Exception-Handler reagieren. Also einfach die einzige Methode des Interfaces TemplateExceptionHandler.handleTemplateException(...) implementieren und die Freemarker-Konfiguration entsprechend anpassen (Quellcode: https://github.com/me4bruno/blog-examples -> example-freemarker-exception):

Java-Code: TemplateExceptionHandler.java

		TemplateExceptionHandler exceptionHandler = new TemplateExceptionHandler() {
		public void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException {
				try {
					out.write("[Missing Value]");
				} catch (IOException e) {
					throw new TemplateException("Failed to print error message. Cause: " + e, env);
				}
			}
		};
		Configuration configuration = new Configuration();
		configuration.setTemplateExceptionHandler(exceptionHandler);

Ausgabe: TemplateExceptionHandler

<html>
<head>
  <title>Freemarker Exception Beispiel</title>
</head>
<body>
  <h1>Freemarker Exception Beispiel</h1>
  <p>
  Homer Simpson lebt in [Missing Value] ([Missing Value]).
  </p>
</body>
</html>

Wenn wir Freemarker mit Spring MVC einsetzen, können wir den Exception-Handler deklarativ in einer XML-Datei deklarieren. Dazu können wir beispielsweise die Methode FreeMarkerConfigurer.setFreemarkerSettings(Properties settings) nutzen und mit dem Key-Value Paar "template_exception_handler" => "rethrow", "debug", "html_debug" oder "ignore" einen Exception-Handler auswählen. Die genauen Parameter erläutert der Javadoc-Kommentar zu der folgenden Methode:

freemarker.core.Configurable.setSetting(String key, String value)

Juhu – keine freemarker.core.InvalidReferenceExceptions mehr 😉

Kommentare sind geschlossen.