Tipps zu Spring Async – Teil 2

Gastbeitrag von | 06.04.2019

In Teil 2 der Tipps zu Spring Async möchte ich Ihnen beschreiben, wie Sie eine Exception mit Spring Boot und der @Async-Annotation händeln. Idealerweise sollten Sie Teil 1 gelesen haben.

Legen wir gleich los. Beim Forken eines Threads von einem Hauptthread haben Sie zwei Optionen:

1. Fire and forget: Sie forken einfach einen Thread, weisen ihm eine Aufgabe zu und vergessen ihn wieder. Sie kümmern sich nicht um das Ergebnis oder den Rückgabetyp, da Sie diese Informationen zur Ausführung der nächsten Geschäftslogik nicht benötigen. Mit einem kleinen Beispiel ist dies gut nachvollziehbar: Angenommen, Sie wollen Mitarbeitern Gehalt überweisen. Sie senden an jeden Mitarbeiter aysnchron eine E-Mail mit der entsprechenden Gehaltsabrechnung. Das Versenden des Gehaltsnachweises per E-Mail an die Mitarbeiter ist nicht Teil der Kerngeschäftslogik, sondern ein Querschnittsthema. In vielen Fällen ist dies “nice-to-have”, in manchen vielleicht ein “must-have”. Da Sie dies nicht manuell durchführen wollen, überlegen Sie sich einen Mechanismus, der das Versenden beim nächsten Mal übernimmt.

2. Fire with Callback: Hier fügen Sie dem geforkten Thread einen Callback hinzu, wodurch der Hauptthread den Callback auf das Ergebnis überprüft, da er dieses benötigt, um weiter fortfahren zu können. Beispiel: Angenommen, Sie bereiten einen Mitarbeiterbericht vor. Ihre Software speichert Mitarbeiterinformationen basierend auf der Datenkategorie in unterschiedlichen Backends. Der Allgemeine Dienst beinhaltet bspw. die allgemeinen Daten der Mitarbeiter (Name, Geburtstag, Geschlecht, Adresse) und der Finanzdienst die gehalts-, steuer- und rentenbezogenen Daten usw. In diesem Beispiel forken Sie zwei Threads parallel – einen für den Allgemeinen Service und einen für den Finanzdienst. Sie benötigen jedoch beide Datensätze, um den Bericht zu erstellen, da Sie die Daten kombinieren müssen. Für den Hauptthread bedeutet dies, dass die Ergebnisse als Callback von den Subthreads angezeigt werden müssen. Durch die Verwendung von CompletebleFuture lässt sich diese Herausforderung meistern.

Was tun Sie nun, wenn in den beiden genannten Szenarien eine Exception auftritt?

Im zweiten Szenario ist es eher einfach, da der Callback entweder erfolgreich oder eben nicht erfolgreich ist. Im Falle eines Fehlers wird die Exception innerhalb von CompltebleFuture eingeschlossen. Die Überprüfung erfolgt im Hauptthread und da dies relativ einfach funktioniert, möchte ich hier nicht im Detail darauf eingehen.

Das 1. Szenario ist etwas kniffliger, denn wie erkennen Sie, ob es sich um einen Erfolg oder einen Fehler handelt? Wie können Sie feststellen, dass etwas nicht funktioniert hat? Nun, die Antwort liegt im Exception Handling. Alles was Sie tun müssen, ist einen eigenen Exception Handler zu injizieren, um beim Auftreten einer Exception eine Async-Methode auszuführen. Dies erreichen Sie mittels AsyncConfigurer.

Der AsyncConfigurer ist ein von Spring bereitgestelltes Interface, das zwei Methoden zur Verfügung stellt: mit der einen überschreiben Sie den TaskExecutor (Threadpool) und die andere ist der Exception Handler, den Sie in einer eigenen Klasse aufrufen können. Ich möchte aber in meinem Beispiel einen anderen Weg gehen und eine AsyncConfigurerSupport Klasse verwenden, die @Configuration und @EnableAsync bereitstellt:

package com.example.ask2shamik.springAsync;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class CustomConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
return new SimpleAsyncTaskExecutor();
}
@Override
@Nullable
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, obj)->{
System.out.println("Exception Caught in Thread - " + Thread.currentThread().getName());
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : obj) {
System.out.println("Parameter value - " + param);
}
};
}
}

Bitte beachten Sie, dass ich im Falle einer getAsyncExecutor-Methode keinen neuen Executor erstelle, da ich keinen eigenen Task-Executor sondern Springs SimpleAsyncExecutor verwenden möchte. Dazu benötige ich lediglich einen benutzerdefinierten Exception Handler und erstelle einen Lambda-Ausdruck, der die AsyncUncaughtExceptionHandler-Klasse erweitert und die handleuncaughtexception-Methode überschreibt.

Auf diese Weise instruiere ich Spring beim Laden mit meinem anwendungsspezifischen AsyncConfigurer (CustomConfiguration) und Lambda-Ausdruck als Exception-Handler. Alles was ich dann benötige, ist eine @Async-Methode, die eine Ausnahme auslöst:

package com.example.ask2shamik.springAsync.demo;
import java.util.Map;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncMailTrigger {
@Async
public void senMailwithException() throws Exception{
throw new Exception("SMTP Server not found :: orginated from Thread :: " + Thread.currentThread().getName());
}
}

Als nächstes erstellen wir eine Caller-Methode:

package com.example.ask2shamik.springAsync.demo;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncCaller {
@Autowired
AsyncMailTrigger asyncMailTriggerObject;
public void rightWayToCall() throws Exception {
System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
asyncMailTriggerObject.senMailwithException();
}
}

Jetzt führen wir die Anwendung mit Spring Boot aus, und schauen, wie sie die von der Methode sendMailwithException geworfene Ausnahme abfängt.

package com.example.ask2shamik.springAsync;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import com.example.ask2shamik.springAsync.demo.AsyncCaller;
@SpringBootApplication
public class DemoApplication {
@Autowired
AsyncCaller caller;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
caller.rightWayToCall();
};
}
}

Und als Ergebnis erhalten wir:

Calling From rightWayToCall Thread main
Exception Caught in Thread - SimpleAsyncTaskExecutor-1
Exception message - SMTP Server not found:: originated from Thread:: SimpleAsyncTaskExecutor-1
Method name - senMailwithException
Hinweise: 

Interessieren Sie sich für weitere Tipps aus der Praxis? Testen Sie unseren wöchentlichen Newsletter mit interessanten Beiträgen, Downloads, Empfehlungen und aktuellem Wissen.

Der Beitrag von Shamik Mitra ist im Original unter https://dzone.com/articles/effective-advice-on-spring-async-exceptionhandler-1 erschienen. Wenn Sie Fragen oder Hinweise haben, freut sich Shamik Mitra bestimmt über Ihren Kommentar auf der Originalseite.

Mit Zustimmung von Shamik Mitra übersetzen wir verschiedene Beiträge von ihm hier in unserem Blog vom Englischen ins Deutsche. Demnächst auch “Tipps zu Spring Async – Teil 3”.

Shamik Mitra
Shamik Mitra

Shamik Mitra ist ein selbsternannter Java Maniac. Er arbeitet als technischer Projektmanager bei Tata Consultancy Services (TCS), und war zuvor bei Cognizant als Architekt und bei IBM als technischer Leiter aktiv. Er ist der Most Valuable Blogger bei DZone und Techathon Coding Competition Jury Award Winner. Er veröffentlicht regelmäßig Tutorials bei A4Academics und er ist technischer Reviewer bei PACKT Publication. Auf vielen dieser Plattformen teilt er sein Wissen zu Java, Java EE, Hibernate, Spring, Entwurfsmuster, Micro-Service, Bigdata und Agile.