Das Spring Framework ist eine Sammlung von Java-Klassen und Methoden zur Unterstützung von Applications. Hier sind einige Lösungen aufgezeigt.

Application Context#

Spring verwendet eine XML-Datei um einen Application Context zu konfigurieren. Dieser kann weitere Application Contexts einbinden, sodass eine Hierarchie entstehen kann.

System Properties#

Im Application Context können Bean-Werte durch System-Properties definiert werden:
    <bean id="testBean" class="com.intersult.test.TestBean">
    	<property name="testProperty" value="some-value"/>
    </bean>

Im Tomcat werden die catalina.properties zwar auch als System Properties geladen, allerdings anschließend durch den Web Application Class Loader überschrieben. Möchte man sie im Application Context zugreifbar machen, braucht man folgenden Eintrag:

    <context:property-placeholder system-properties-mode="OVERRIDE"/>

Login Filter#

Spring Security bietet sehr umfangreiche Konfigurationsmöglichkeiten, ebenso groß kann die Zeit werden, Verständnis für das Framework zu entwickeln. Bei kleinen Aufgaben kann es so hilfreich sein, Musterlösungen zu haben. Wie zum Beispiel für einen eigenen Single-Sign-On, Remember-Me oder anderen automatisierten Login:
	<sec:http auto-config="true">
		<sec:anonymous enabled="false"/>
		<sec:intercept-url pattern="/faces/page/**" access="ROLE_TEMPORARY"/>
		<sec:form-login login-page="/login"/>
		<sec:custom-filter ref="temporaryLoginFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
		<sec:logout logout-success-url="/faces/security/login.xhtml"/>
	</sec:http>
@Component
public class TemporaryLoginFilter extends GenericFilterBean {
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
			ServletException {
		if (...)
		    throw new AuthenticationCredentialsNotFoundException("Authentication required");
		if (...)
		    SecurityContextHolder.getContext().setAuthentication(authentication);
		chain.doFilter(request, response);
	}
}

Erklärung: Der Kunstgriff besteht darin, einen Custom-Filter vor dem FilterSecurityInterceptor in die Filter-Chain einzuhängen. Der Grund besteht darin, dass an dieser Stelle Security-Exceptions vom ExceptionTranslationFilter gefangen werden und in einem Redirect auf die Login-Seite resultieren bzw. je nachdem, welchen Login-Mechanismus man standardmäßig konfiguriert hat. Andernfalls kann man den Benutzer durch setzen des Security-Context programmatisch einloggen.

ResponseBodyEmitter#

Fragen und Antworten:

Welche Bedeutung hat timeout in ResponseBodyEmitter von Spring Framework?#

Das Timeout in ResponseBodyEmitter von Spring Framework gibt an, wie lange der Server auf die Generierung von Daten für den HTTP-Response warten soll, bevor er den Response abschließt und sendet. Wenn innerhalb des Timeouts keine Daten verfügbar sind, wird der Response mit einem leeren Body gesendet.

Dieses Timeout ist nützlich, um sicherzustellen, dass der Server nicht zu lange auf Daten wartet und dass der Client nicht zu lange auf eine Antwort warten muss. Es ermöglicht auch die Steuerung der Serverressourcen, da der Server nicht unnötig Ressourcen für einen inaktiven Request verschwendet.

Der Standardwert für das Timeout in ResponseBodyEmitter beträgt -1, was bedeutet, dass kein Timeout festgelegt ist und der Server so lange auf Daten wartet, bis sie verfügbar sind oder der Request abgebrochen wird.

Heißt dass, wenn ich einen länger dauernden Stream senden möchte (z.B. 10 min.) muss ich diesen timeout entsprechend hochsetzen?#

Ja, wenn Sie einen längeren Stream senden möchten, müssen Sie den Timeout entsprechend hochsetzen. Standardmäßig ist kein Timeout festgelegt, was bedeutet, dass der Server so lange auf Daten wartet, bis sie verfügbar sind oder der Request abgebrochen wird.

Wenn Sie jedoch sicherstellen möchten, dass der Server für eine bestimmte Zeit auf Daten wartet, bevor er den Response abschließt und sendet, können Sie den Timeout-Wert erhöhen. Sie können dies tun, indem Sie den Timeout-Wert entweder beim Erstellen des ResponseBodyEmitter-Objekts festlegen oder indem Sie die entsprechende Methode verwenden, um den Timeout-Wert nachträglich zu setzen.

Was passiert wenn keine Daten gesendet werden, weil der generierende Thread mit einer Exception endet?#

Wenn keine Daten gesendet werden, weil der generierende Thread mit einer Exception endet, wird der Response mit einem leeren Body gesendet. Das bedeutet, dass der Client einen leeren Response erhält, ohne Daten.

Wenn der generierende Thread eine Exception wirft, während er Daten für den Response generiert, wird diese Exception normalerweise nicht an den Client weitergeleitet. Stattdessen wird die Exception intern behandelt und der Response wird mit einem leeren Body gesendet.

Es liegt dann in der Verantwortung des Entwicklers, die entsprechende Fehlerbehandlung zu implementieren, um mit dieser Exception umzugehen. Dies kann beispielsweise das Loggen der Exception, das Zurückgeben einer Fehlermeldung im Response oder das Senden eines speziellen HTTP-Statuscodes beinhalten.

Es ist wichtig zu beachten, dass der generierende Thread, der die Daten für den Response generiert, auf mögliche Exceptions überwacht werden sollte, um sicherzustellen, dass etwaige Fehler behandelt werden und der Server nicht in einem undefinierten Zustand bleibt.

Besteht bei timeout = -1 die Möglichkeit dass mehr Ressourcen (Threads) allokiert werden als nötig oder werden die generierenden, asynchronen Threads beim Abbrechen der HTTP-Verbindung trotzdem beendet?#

Wenn der Timeout auf -1 gesetzt ist, besteht die Möglichkeit, dass mehr Ressourcen (Threads) allokiert werden als nötig.

Wenn der ResponseBodyEmitter-Stream nicht innerhalb des Timeouts abgeschlossen wird, bleibt der generierende Thread aktiv und wartet weiterhin auf Daten, bis sie verfügbar sind oder der Request abgebrochen wird. Wenn der Client jedoch die HTTP-Verbindung abbricht, bevor der Stream abgeschlossen ist, kann es sein, dass der generierende Thread weiterhin aktiv bleibt, auch wenn keine Daten mehr generiert werden.

Dies kann dazu führen, dass Ressourcen (Threads) unnötig belegt werden und der Server eine höhere Last erfährt. Es ist wichtig, sicherzustellen, dass der Stream korrekt abgeschlossen wird, entweder indem der generierende Thread beendet wird, wenn keine Daten mehr generiert werden, oder indem der Stream manuell abgebrochen wird.

Um sicherzustellen, dass die generierenden Threads beendet werden, wenn die HTTP-Verbindung abgebrochen wird, können Sie die Methode `onCompletion` des ResponseBodyEmitter verwenden, um den Stream zu überwachen und entsprechende Aufräumarbeiten durchzuführen:

```java emitter.onCompletion(() -> { // Aufräumarbeiten, wenn der Stream abgeschlossen ist }); ```

Dadurch können Sie sicherstellen, dass die generierenden Threads korrekt beendet werden, wenn die Verbindung abgebrochen wird, und dass keine Ressourcen verschwendet werden.

Optimistic Locking#

Spring Data implementiert Optimistic Locking nur mit JPA. Mit folgendem Code kann man das Verhalten simulieren:
package com.intersult.r2dbc_test;

import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Component;

import reactor.core.publisher.Mono;

@Component
public class PersonOptimisticLocking implements BeforeConvertCallback<Person> {
	@Autowired
	private DatabaseClient client;

	@Override
	public Publisher<Person> onBeforeConvert(Person person, SqlIdentifier table) {
		if (person.getId() == null)
			return Mono.just(person);
		return client.sql("SELECT version FROM person WHERE id = :id")
			.bind("id", person.getId())
			.map(r -> r.get("version", Integer.class))
			.first()
			.map(version -> {
				if (version != person.getVersion())
					throw new OptimisticLockingFailureException("Optimistic locking conflict: your version: " + person.getVersion() + ", database version: " + version);
				person.setVersion(version + 1);
				return person;
			});
	}
}