Tipps zu Spring Async – Teil 1

Gastbeitrag von | 30.03.2019

Viele Entwickler folgen dem Trend, Spring Boot als Allzweckwaffe in der Softwareentwicklung zu nutzen. Ein wesentlicher Grund liegt darin, dass es als entwicklerfreundlich gilt und durch das Prinzip “Convention over Configuration” Entwicklern – vom Junior bis zum Senior – hilft, sich nur auf die Geschäftslogik zu konzentrieren. Entwickler müssen nicht die inneren Details von Spring kennen – einfach ein paar Anmerkungen einfügen, den Geschäftscode schreiben und voila! Und wer sich nicht sicher ist, wie Spring funktioniert, schnappt sich einfach ein Spring Boot Tutorial und schon kann die Arbeit beginnen – so einfach ist das.

So weit, so gut. Doch wer Sprint wie ein Experte nutzen möchte, der sollte manchmal wissen, wie es funktioniert. Je größer das Wissen ist, desto besser lassen sich die Vorteile nutzen. In diesem Artikel möchte ich versuchen, Ihnen die asynchrone Verarbeitung in Spring näherzubringen.

Alle logischen Elemente, die nicht direkt mit der Geschäftslogik verbunden sind (Querschnittsthemen), oder die Antworten bereitstellen, die jedoch nicht benötigt werden, um den nächsten Ablauf oder eine Geschäftsberechnung zu bestimmen, sind ideale Kandidaten für asynchrones Arbeiten. Auch bei der Integration in ein verteiltes System wird zur Entkoppelung die Technik zur Asynchronisierung verwendet.

Zur Asynchronisation wird in Spring die sogenannte @Async-Annotation verwendet. Sollten Sie zufällig @Async auf einer Methode verwenden und denken, dass Ihre Methode asynchron in einem separaten Thread aufgerufen wird, liegen Sie falsch. Es ist wichtig zu wissen, wie @Async funktioniert und welche Einschränkungen es gibt, denn nur so können Sie das Verhalten von Async richtig einordnen.

Wie funktioniert @Async?

Wenn Sie eine @Async-Annotation auf eine ihr zugrunde liegende Methode anwenden, erstellt diese basierend auf der Eigenschaft proxyTargetClass einen Proxy für das Objekt, in dem Async definiert ist (JDK Proxy/CGlib). Dann versucht Spring, einen dem Kontext zugeordneten Thread-Pool zu finden, um die Logik dieser Methode als separaten Ausführungspfad zu übergeben. Um genau zu sein, sucht es eine eindeutige TaskExecutor-Bean oder eine Bean, die taskExecutor benannt ist. Klappt dies nicht, wird stattdessen SimpleAsyncTaskExecutor verwendet.

Während der Proxy erstellt und der Auftrag an den TaskExecutor-Thread-Pool gesendet wird, gibt es einige Einschränkungen, die es zu beachten gilt. Werfen wir einen Blick darauf:

Einschränkungen von @Async

1. Angenommen, Sie schreiben eine Klasse und identifizieren eine Methode, die als Async fungiert und setzen @Async auf diese Methode. Wenn Sie nun diese Klasse aus einer anderen Klasse verwenden wollen, indem Sie eine lokale Instanz erstellen, dann wird der Async nicht ausgelöst. Es muss von der Spring @ComponentScan Annotation übernommen oder innerhalb einer Klasse mit der Bezeichnung @Configuration erstellt werden.

Async Annotation in einer Klasse

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 senMail(Map<String,String> properties) {
System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
properties.forEach((K,V)->System.out.println("Key::" + K + " Value ::" + V));
}

}

Caller Klasse

package com.example.ask2shamik.springAsync.demo;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AsyncCaller {

@Autowired
AsyncMailTrigger asyncMailTriggerObject;

public void rightWayToCall() {
System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
asyncMailTriggerObject.senMail(populateMap());

}

public void wrongWayToCall() {
System.out.println("Calling From wrongWayToCall Thread " + Thread.currentThread().getName());
AsyncMailTrigger asyncMailTriggerObject = new AsyncMailTrigger();
asyncMailTriggerObject.senMail(populateMap());

}

private Map<String,String> populateMap(){
Map<String,String> mailMap= new HashMap<String,String>();
mailMap.put("body", "A Ask2Shamik Article");
return mailMap;

}
}

Hier habe ich zwei Methoden erstellt – eine benutzt die @Autowired-Version von AsyncMailtrigger, die von @ComponentScan ausgewählt wird. Mit der WrongWayToCall-Methode erstelle ich das Objekt lokal, so dass es nicht von @ComponentScan aufgenommen und kein neuer Thread erzeugt wird, sondern innerhalb des Hauptthreads zur Ausführung kommt.

Das Ergebnis

Calling From rightWayToCall Thread main
2019-03-09 14:08:28.893  INFO 8468 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
Trigger mail in a New Thread :: task-1
Key::body Value ::A Ask2Shamik Article
++++++++++++++++
Calling From wrongWayToCall Thread main
Trigger mail in a New Thread :: main
Key::body Value ::A Ask2Shamik Article

2. Verwenden Sie @Async niemals auf einer privaten Methode, denn zur Laufzeit kann es keinen Proxy anlegen und funktioniert daher nicht.

@Async
private void senMail() {
System.out.println("A proxy on Private method "  + Thread.currentThread().getName());

}

3. Schreiben Sie niemals eine Async-Methode in die gleiche Klasse, in der die aufrufende Methode die gleiche Async-Methode aufruft. Denken Sie immer daran, dass Async bei der Verwendung dieser Referenz nicht funktioniert, denn obwohl es einen Proxy erstellt, umgeht der Aufruf den Proxy und ruft die Methode direkt auf. Achten Sie also beim Aufruf der Async-Methode auf die Verwendung in einer anderen Klasse.

Aufruf der Async-Methode in einer anderen Klasse

Ein Beispiel

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() {
System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
asyncMailTriggerObject.senMail(populateMap());

}

public void wrongWayToCall() {
System.out.println("Calling From wrongWayToCall Thread " + Thread.currentThread().getName());
this.senMail(populateMap());
}

private Map<String,String> populateMap(){
Map<String,String> mailMap= new HashMap<String,String>();
mailMap.put("body", "A Ask2Shamik Article");
return mailMap;

}

@Async
public void senMail(Map<String,String> properties) {
System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
properties.forEach((K,V)->System.out.println("Key::" + K + " Value ::" + V));
}

}

Führen Sie die Anwendung einfach aus. Bitte beachten Sie, dass wir die Annotation @EnableAsync verwenden. Damit überträgt Spring @Async-Methoden in einen Hintergrund-Thread-Pool. Diese Klasse kann den verwendeten Executor anpassen, indem sie eine neue Bean definiert. Wie dies funktioniert, können Sie im zweiten Teil des Beitrags sehen.

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
@EnableAsync
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();
        Thread.sleep(1000);
        System.out.println("++++++++++++++++");
        Thread.sleep(1000);
        caller.wrongWayToCall();

        };
    }
}

Ausblick auf Teil 2

Ich hoffe, Sie konnten sehen, wie Async intern funktioniert und welche Einschränkungen es gibt. Im zweiten Teil des Beitrags werfe ich einen Blick auf den Exception Handler in Async. Stay tuned!

 

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-part-1 erschienen. Mit Zustimmung von Shamik Mitra übersetzen wir verschiedene Beiträge von ihm hier in unserem Blog vom Englischen ins Deutsche.

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.