Nacktes Thymeleaf

Oftmals nutzen die Beispiele im Internet Spring, oder — noch spezieller — Thymeleaf wird als Template-Engine in Spring MVC eingesetzt, daher jetzt nackt, so einfach es geht.

In unsere POM kommt:

<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
 <groupId>org.thymeleaf</groupId>
 <artifactId>thymeleaf</artifactId>
 <version>3.0.9.RELEASE</version>
</dependency>

Wir schreiben das Template demo.html:

<!DOCTYPE html>
<html>
<body>
<p>Hey <span data-th-text="${name}">CHRISTIAN</span></p>
</body>
</html>

Und dann kann ein Java-Programm name füllen:

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;

public class ThymeleafDemo {

 public static void main( String[] args ) {

  FileTemplateResolver resolver = new FileTemplateResolver();
  resolver.setCharacterEncoding( "UTF-8" );

  TemplateEngine templateEngine = new TemplateEngine();
  templateEngine.setTemplateResolver( resolver );

  Context ctx = new Context();
  ctx.setVariable( "name", "tutego" );
 
  String result = templateEngine.process( "demo.html", ctx );
  System.out.println( result );
 }
}

 

Spring Retry

Das Spring Retry Projekt

Spring Retry ist ein Zusatzprojekt (https://github.com/spring-projects/spring-retry), um Codeblöcke wiederholt auszuführen, wenn sie zu einem Fehler führen.

  • Das ist nützlich beim Ansprechen von Remote-Diensten, die temporär nicht verfügbar sein können.

Als erstes müssen zwei Dependencies in unsere POM:

<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
</dependency>

@Retryable

Schreiben wir eine Bean, die zwei Anläufe braucht, eine Operation durchzuführen.

@Component
class Achiever {

  int tries = 1;

  @Retryable   
  public int tryIt() {   

    System.out.println( "Runde " + tries );
    tries++;

    if ( tries != 3 )
      throw new IllegalStateException( "Runde nicht 3" );

    return tries;
  }
}
Retryable drückt aus, das eine Operation bei Ausnahmen automatisch neu aufgerufen werden soll.
Die Methoden können eine Rückgabe haben, oder auch keine.

@EnableRetry

Damit Spring für die annotierten Methoden einen Proxy baut, müssen wir eine @Configuration mit @EnableRetry annotieren.

Wir können nun den Achiever injizieren:

@Autowired Achiever archiever;

Und die Methode ausführen:

System.out.println( "Vor dem Aufruf" );
int i = archiever.tryIt();
System.out.println( "Nach dem Aufruf ist i=" + i );

Auf der Konsole erscheint keine Exception. Nur:

Vor dem Aufruf
Runde 1
Runde 2
Nach dem Aufruf

Der Annotationsty Retryable

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
	String interceptor() default "";
	Class<? extends Throwable>[] value() default {};
	Class<? extends Throwable>[] include() default {};
	Class<? extends Throwable>[] exclude() default {};
	String label() default "";
	boolean stateful() default false;
	int maxAttempts() default 3;              
	String maxAttemptsExpression() default "";
	Backoff backoff() default @Backoff();
	String exceptionExpression() default "";
}
Die Anzahl der Wiederholungen ist nicht unendlich, Spring stoppt standardmäßig bei 3.

Ausblick

Die Dokumentation unter https://github.com/spring-projects/spring-retry zeit weitere Möglichkeiten auf. Ein zusätzliches Beispiel:

@Retryable
public void tryIO() throws IOException {
  throw new IOException( LocalTime.now().toString() );
}

@Recover  
public void recover( IOException e ) {
  System.err.println( "Aarrrrg: " + e );
}
Die mit @Recover annotierte Methode wird am Ende einer nicht erfolgreichen Aufrufkette aufgerufen.

Caching in Spring Boot nutzen

Optimierung durch Caching

Bei Methoden, die idempotent sind, also zu einem Parameter immer den gleichen Wert liefern, kann Spring einen Cache zur Optimierung einsetzen.

  • Im Grunde ist der Cache ein Assoziativspeicher, der die Methodenparameter als Schlüssel nutzt.

Beispiele:

  • Berechnen einer Prüfsumme
  • Dateiinhalt
  • DNS-Cache

Achtung: Gewisse Cache-Inhalte können nach einer Zeit ungültig werden.

Caching in Spring

Um das Caching zu aktivieren annotiert man

  1. eine @Configuration mit @EnableCaching
  2. eine public (!!!) Methode mit @Cacheable und vergibt einen Cache-Namen.

@Cacheable-Beispiel (1/2)

@Component
class Hash {

  @Cacheable( "md5" )
  public byte[] md5( String text ) {

    System.out.println( "hash: " + text );
    try {
      MessageDigest md = MessageDigest.getInstance( "MD5" );
      return md.digest( text.getBytes( StandardCharsets.UTF_8 ) );
    }
    catch ( NoSuchAlgorithmException e ) {
      throw new IllegalStateException( e );
    }
  }
}

@Cacheable-Beispiel (2/2)

Wir können Hash injizieren

@Autowired
Hash hash;

und nutzen

byte[] md5_1 = hash.md5( "tutego" );
byte[] md5_2 = hash.md5( "tutego" );
System.out.println( Arrays.equals( md5_1, md5_2 ) ); // true

System.out.println( hash.getClass() ); // com.tutego.boot.basic.Hash$$EnhancerBySpringCGLIB$$4548965

Ausblick (1/2)

  • Spring generiert den Schlüssel zum Cache aus den Argumente. Man kann kann eigene KeyGenerator en verwenden.
  • Mit der SpEL lassen sich von Anfrageobejkte zum Beispiel die Schlüssel erfragen, etwa so @Cacheable(cacheNames="books", key="#isbn.rawNumber") .
  • Unter Umständen sollen große Objekte nicht in den Cache, hier kann man eine Bedingung angeben: @Cacheable(cacheNames="book", condition="#name.length() < 32")
  • Ist eine Methode mit @CachePut annotiert, kann man den Cache selbst füllen, bzw. Werte überschreiben.
  • Ist eine Methode mit @CacheEvict annotiert, etwa @CacheEvict(cacheNames="books", allEntries=true) public void clear() wird der Cache gelöscht.

Ausblick (2/2)

Das Spring Framework kann automatisch diverse Caching-Implementierung nutzen.

  • Standardmäßig ist es eine ConcurrentHashMap.
  • Unterstützt werden u.a.: EhCache 2.x, Hazelcast, Infinispan, Couchbase, Redis, Caffeine.
  • Spring erkennt anhand des Eintrags im Klassenpfad, was gewünscht ist.

In der application.[properties|yml] lassen sich dann Dinge wie Lebensdauer, Größe, etc. extern konfigurieren.

JMX in Spring Boot nutzen

Die Java Management Extensions (JMX)

  • Die Java Management Extensions (JMX) ist eine Spezifikation zur Verwaltung und Überwachung von Java-Anwendungen.
  • Teile der JMX Spezifikation sind ab Java 5 in der Java SE integriert.
  • Es gibt einen JMX Agent, an dem Managed Beans angemeldet werden.
  • Von außen lassen sich diese Managed Beans erfragen.
  • JConsole ist ein GUI-Programm im JDK, das zur Verwaltung von MBeans verwendet werden kann.

JMX in Spring Boot

Spring nutzt drei Annotationen, um Managed Beans mit ihren Zuständen und Operationen beim JMX Agent zu registrieren:

@ManagedResource
Definiert mit der Typ-Annotation die Managed Bean
@ManagedAttribute
Definiert ein Attribut (Setter/Getter)
@ManagedOperation
Definiert eine Operation

Beispiel-Managed-Bean

package com.tutego.boot.actuator;

import java.util.HashMap;
import java.util.Map;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;

@Component
@ManagedResource
public class StringMapManagedBean {
  private final Map<String, String> map = new HashMap<>();

  @ManagedAttribute
  public int getSize() {
    return map.size();
  }

  @ManagedOperation
  public String get( String key ) {
    return map.get( key );
  }


  @ManagedOperation
  public String put( String key, String value ) {
    return map.put( key, value );
  }

  public void remove( String key ) {
    map.remove( key );
  }
}

Annotationstyp @ManagedResource

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ManagedResource {

	@AliasFor("objectName")
	String value() default "";

	@AliasFor("value")
	String objectName() default "";

	String description() default "";

	int currencyTimeLimit() default -1;
	boolean log() default false;
	String logFile() default "";
	String persistPolicy() default "";
	int persistPeriod() default -1;
	String persistName() default "";
	String persistLocation() default "";
}

Springs eigene Managed Beans

Spring Boot veröffentlicht selbst diverse Managed Beans unter der Domäne org.springframework.boot.

  • org.springframework.boot:type=Endpoint,name=Health zum Beispiel für die Gesundheitsinformationen

Sie lassen sich abschalten mit management.endpoints.jmx.exposure.exclude=*.

Falls mehrere ApplicationContext exisistieren kommt es zum Namenskonflikt; der lässt sich mit management.endpoints.jmx.unique-names=true vermeiden.

Der Domain-Name lässt sich anpassen mit management.endpoints.jmx.domain.