Tipps zu Spring Async – Teil 3

Gastbeitrag von | 04.05.2019

Im dritten und letzen Teil der Beitragsreihe zum Spring Async Konzept möchte ich heute auf die Arbeit im Web eingehen. Ich freue mich sehr, Ihnen meine Erfahrungen mit Spring Async und http-Requests zu beschreiben, und hoffe, dass Sie so in ihren Vorhaben wertvolle Zeit sparen können. In meinen vorherigen Artikeln habe ich die effektive Nutzung des Konzepts beschrieben:

In Teil 1 ging es um die interne Funktionsweise und das Zuweisen von Aufgaben zu Threads.

In Teil 2 stand die Ausführung von Aufgaben und insbesondere das Exception Handling im Vordergrund.

Stellen Sie sich bitte folgendes Szenario vor: Informationen einer Benutzeroberfläche sollen an einen Backend-Controller weitergegeben werden, der einen asynchronen Maildienst aufruft, um eine Mail auszulösen. Nachfolgend sehen Sie einen Codeschnipsel zu einem Controller, der per HTTP-Servelet-Request Informationen sammelt, einige Operationen ausführt, und die Informationen an einen Async Mail Service weiterleitet.

Sehen Sie das Problem?

package com.example.demo; 

import javax.servlet.http.HttpServletRequest; 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class GreetController {


@Autowired
private AsyncMailTrigger greeter;
@RequestMapping(value = "/greet", method = RequestMethod.GET)
public String greet(HttpServletRequest request) throws Exception {
String name = request.getParameter("name");
greeter.asyncGreet(request);
System.out.println(Thread.currentThread() + " Says Name is " + name);
System.out.println(Thread.currentThread().getName() + " Hashcode" + request.hashCode());
return name;

}
}

Den Async Mail Service habe ich als @Component markiert; Sie können ihn leicht in @Service ändern. Verwendet wird eine Methode namens asyncGreet, die Informationen per Http-Request holt und die Mail auslöst (dieser Teil wird zur Vereinfachung weggelassen). Beachten Sie bitte, dass ein Thread.sleep() verwendet wird; auf die Gründe gehe ich später ein.

package com.example.demo;

import java.util.Map; 
import javax.servlet.http.HttpServletRequest; 
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncMailTrigger {

@Async
public void asyncGreet(HttpServletRequest request) throws Exception {
System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + " greets before sleep" + request.getParameter("name"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " greets" + request.getParameter("name"));
System.out.println(Thread.currentThread().getName() + " Hashcode" + request.hashCode());
} 

}

Und die Hauptklasse:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringAsyncWebApplication {

public static void main(String[] args) {
SpringApplication.run(SpringAsyncWebApplication.class, args);
}

}

Wenn Sie dieses Programm nun ausführen, dürfte die Ausgabe dieser hier ähneln:

Thread[http-nio-8080-exec-1,5,main] Says Name is Shamik
http-nio-8080-exec-1 Hashcode 821691136
Trigger mail in a New Thread:: task-1
task-1 greets before sleep Shamik
task-1 greets null task-1 Hashcode 821691136
Fällt es Ihnen auf? Per Request stehen die benötigten Information zur Verfügung, doch auf fast magische Weise verschwinden sie wieder. Seltsam, den der Hashcode beweist, dass es sich um dasselbe Objekt handelt. Was ist passiert? Was ist der Grund für das Verschwinden der Informationen aus der Anfrage?

Keine Sorge, wenn es Ihnen nicht direkt auffällt. Machen wir uns gemeinsam auf die Suche wie einst Sherlock Holmes und untersuchen das Problem. An sich ist es nicht unüblich, dass es Probleme mit Requests gibt. Um das Verhalten zu verstehen, sollten wir einen Blick auf den Lebenszyklus des Requests werfen:

Der Request wird vom Servlet-Container unmittelbar vor dem Aufruf der Servicemethode erstellt. In Spring durchläuft der Request ein Dispatcher-Servlet. Dort identifiziert Spring den Controller mittels Request-Mapping und ruft die gewünschte Methode im Controller auf. Sobald der Request bedient wird, löscht der Servlet-Container entweder das Request-Objekt oder setzt den Zustand des Objekts zurück. (Dies hängt von der Container-Implementierung ab, denn sie verwaltet einen Pool von Requests; in diesem Beitrag werde ich aber nicht näher darauf eingehen).

Und was macht Spring Async? Es wählt einen Thread aus dem Thread-Pool aus und weist ihm die Aufgabe zu. In unserem Fall wird das Request-Objekt an den Async-Thread übergeben, und die Informationen direkt aus dem Request durch die AsyncGreet-Methode extrahiert. Dies funktioniert asynchron und der Hauptthread wartet nicht darauf, dass der Thread abgeschlossen wird, sondern führt die Anweisung aus, überträgt die Antwort und aktualisiert den Zustand des Objekts. Ironischerweise übergeben wir also das Request-Objekt direkt an den asynchronen Thread, und während die Antwort nicht im Hauptthread übertragen wird, enthält das Objekt die Daten.

Was lernen Sie daraus? Geben Sie niemals ein Request-Objekt oder ein Objekt im Zusammenhang mit Request/Response (Header) direkt bei der Verwendung von Async weiter; Sie wissen nie, wann Ihre Antwort committed und der Status aktualisiert wird. Andernfalls wird ein intermittierender Fehler auftreten.

Was können Sie stattdessen tun? Jetzt kommen wir zur Sleep-Anweisung zum Tragen. Sie sorgt dafür, dass im Hauptthread die Antwort committed und der Request-Status aktualisiert werden kann. Wenn Sie Informationen aus einem Request übergeben müssen, können Sie ein Wertobjekt anlegen und dieses an Spring Async übergeben. Eine Lösung könnte wie folgt aussehen:

package com.example.demo; 

public class RequestVO {

String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
} 

}

Und der Async Mail Service:

package com.example.demo;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncMailTrigger {

@Async
public void asyncGreet(RequestVO reqVO) throws Exception {
System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + " greets before sleep" + reqVO.getName());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " greets" + reqVO.getName());

} 

}

Der Greet-Controller:

package com.example.demo; 

import javax.servlet.http.HttpServletRequest; 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetController {

@Autowired
private AsyncMailTrigger greeter;
@RequestMapping(value = "/greet", method = RequestMethod.GET)
public String greet(HttpServletRequest request) throws Exception {
String name = request.getParameter("name");
RequestVO vo = new RequestVO();
vo.setName(name);
//greeter.asyncGreet(request);
greeter.asyncGreet(vo);
System.out.println(Thread.currentThread() + " Says Name is " + name);
System.out.println(Thread.currentThread().getName() + " Hashcode" + request.hashCode());
return name;
}
}

Der Output:

Thread[http-nio-8080-exec-1,5,main] Says Name is Shamik
http-nio-8080-exec-1 Hashcode 1669579896
Trigger mail in a New Thread:: task-1
task-1 greets before sleep Shamik
task-1 greets Shamik

Ich hoffe, die Reihe zu Spring Async hat Ihnen gefallen. Gerne stehe ich natürlich für Fragen zur Verfügung und freue mich über Kommentare.

 

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-final-part-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.

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.