committed by
Christoph Strobl
parent
dde9a651ba
commit
687222d1fa
@@ -3,23 +3,25 @@
|
||||
|
||||
The Spring Data infrastructure provides hooks for modifying an entity before and/or after certain methods are invoked.
|
||||
Those so called ``EntityCallback``s provide a convenient way to check and potentially modify an entity in a callback fashioned style. +
|
||||
An `EntityCallback` looks pretty much like a specialized `ApplicationListener`, with which you might be familiar.
|
||||
Some Spring Data implementations publish store specific events, like a `BeforeSaveEvent` that allow to modify the given entity, which in some cases, eg. immutable types, can cause somme trouble.
|
||||
Plus, the event publishing relies on the `ApplicationEventMulticaster` which can be configured with an asynchronous `TaskExecutor` leading to unpredictable outcome.
|
||||
An `EntityCallback` looks pretty much like a specialized `ApplicationListener`, with which you might be already familiar.
|
||||
Some Spring Data modules publish store specific events, like a `BeforeSaveEvent` that allow modifying the given entity which in some cases, eg. immutable types, can cause trouble.
|
||||
Another aspects is that, event publishing relies on `ApplicationEventMulticaster` that can be configured with an asynchronous `TaskExecutor` leading to unpredictable outcome as event processing can be forked onto a `Thread`.
|
||||
|
||||
``EntityCallback``s offer integration points with both sync and reactive APIs guaranteeing an in order invocation at fixed checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
|
||||
Entity callbacks provide integration points with both, synchronous and reactive, APIs guaranteeing an in-order execution at well-defined checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
|
||||
|
||||
Entity callbacks are typically separated by API type. This separation means that a synchronous API only considers synchronous entity callbacks and a reactive implementation considers only reactive entity callbacks.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The entity callback API has been introduced with Spring Data Commons 2.2 and is the recommended way of dealing with entity modifications. +
|
||||
Existing store specific `ApplicationEvents` are still be published *before* the potentially registered ``EntityCallback``s are called.
|
||||
The Entity Callback API has been introduced with Spring Data Commons 2.2. It is the recommended way of applying entity modifications.
|
||||
Existing store specific `ApplicationEvents` are still published *before* the invoking potentially registered ``EntityCallback``s.
|
||||
====
|
||||
|
||||
[entity-callbacks.implement]
|
||||
== Implementing Entity Callbacks
|
||||
|
||||
The `EntityCallback` is, via its generic type argument, directly associated with the domain type it is meant for.
|
||||
Each store typically uses a set of predefined entity callbacks covering the lifecycle of an entity.
|
||||
An `EntityCallback` is directly associated with its domain type through its generic type argument.
|
||||
Each Spring Data module typically ships a set of predefined `EntityCallback` interfaces covering the entity lifecycle.
|
||||
|
||||
.Anatomy of an `EntityCallback`
|
||||
====
|
||||
@@ -38,7 +40,7 @@ public interface BeforeSaveCallback<T> extends EntityCallback<T> {
|
||||
}
|
||||
----
|
||||
<1> `BeforeSaveCallback` specific method to be called before an entity is saved. Returns a potentially modifed instance.
|
||||
<2> The entity right bevore presisted.
|
||||
<2> The entity right before persisting.
|
||||
<3> A number of store specific arguments like the _collection_ the entity is persisted to.
|
||||
====
|
||||
|
||||
@@ -59,20 +61,22 @@ public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
|
||||
}
|
||||
----
|
||||
<1> `BeforeSaveCallback` specific method to be called on subscription, before an entity is saved. Emits a potentially modifed instance.
|
||||
<2> The entity right bevore presisted.
|
||||
<2> The entity right before persisting.
|
||||
<3> A number of store specific arguments like the _collection_ the entity is persisted to.
|
||||
====
|
||||
|
||||
Implement the interface suiting your application needs like shown in the example below.
|
||||
NOTE: Optional entity callback parameters are defined by the implementing Spring Data module and inferred from call site of `EntityCallback.callback()`.
|
||||
|
||||
Implement the interface suiting your application needs like shown in the example below:
|
||||
|
||||
.Example `BeforeSaveCallback`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered <2> {
|
||||
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered <2> {
|
||||
|
||||
@Override
|
||||
public Object onBeforeSave(Person entity, String collection) { <1>
|
||||
public Object onBeforeSave(Person entity, String collection) { <1>
|
||||
|
||||
if(collection == "user) {
|
||||
return // ...
|
||||
@@ -83,74 +87,77 @@ public class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ord
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 100; <2>
|
||||
return 100; <2>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Implement logic according to application requirements.
|
||||
<1> Callback implementation according to your requirements.
|
||||
<2> Potentially order the entity callback if multiple ones for the same domain type exist. Ordering follows lowest precedence.
|
||||
====
|
||||
|
||||
[entity-callbacks.register]
|
||||
== Registering Entity Callbacks
|
||||
|
||||
``EntityCallback``s get picked up by the store specific implementations in case they are provided with an `ApplicationContext`.
|
||||
Most template APIs already implement `ApplicationContextAware` an therefore have a context at hand if registered as a Bean.
|
||||
``EntityCallback`` beans are picked up by the store specific implementations in case they are registered in the `ApplicationContext`.
|
||||
Most template APIs already implement `ApplicationContextAware` and therefore have access to the `ApplicationContext`
|
||||
|
||||
The following example provides a collection of valid entity callback registrations.
|
||||
The following example explains a collection of valid entity callback registrations:
|
||||
|
||||
.Example `EntityCallback` Bean registration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Order(1) <1>
|
||||
@Component
|
||||
class First implements BeforeSaveCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onBeforeSave(Person person) {
|
||||
return // ...
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
|
||||
Ordered <2> {
|
||||
|
||||
@Override
|
||||
public Object onBeforeSave(Person entity, String collection) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 100; <2>
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public class EntityCallbackConfiguration {
|
||||
|
||||
@Bean
|
||||
BeforeSaveCallback<Person> annotationOrderedCallback() { <1>
|
||||
return new First();
|
||||
}
|
||||
|
||||
@Bean
|
||||
BeforeSaveCallback<Person> interfaceOrderedCallback() { <2>
|
||||
return new DefaultingEntityCallback();
|
||||
}
|
||||
|
||||
@Bean
|
||||
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() { <3>
|
||||
return (BeforeSaveCallback<Person>) it -> // ...
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserCallbacks multipleCallbacksInOneImplementationClass() { <4>
|
||||
return new UserCallbacks();
|
||||
}
|
||||
@Component
|
||||
class UserCallbacks implements BeforeConvertCallback<User>,
|
||||
BeforeSaveCallback<User> { <4>
|
||||
|
||||
@Order(1) <1>
|
||||
static class First implements BeforeSaveCallback<Person> {
|
||||
@Override
|
||||
public Person onBeforeConvert(User user) {
|
||||
return // ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Person onBeforeSave(Person person) {
|
||||
return // ...
|
||||
}
|
||||
}
|
||||
|
||||
static class UserCallbacks implements BeforeConvertCallback<User>, BeforeSaveCallback<User> { <4>
|
||||
|
||||
@Override
|
||||
public Person onBeforeConvert(User user) {
|
||||
return // ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Person onBeforeSave(User user) {
|
||||
return // ...
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Person onBeforeSave(User user) {
|
||||
return // ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `BeforeSaveCallback` receiving its order from the `@Order` annotation.
|
||||
<2> `BeforeSaveCallback` receiving its order via the `Ordered` interface implementation.
|
||||
<3> `BeforeSaveCallback` using a lambda expression. Unordered by default and invoked last.
|
||||
<4> Combine multiple entity callback interfaces in one implementation class.
|
||||
<4> Combine multiple entity callback interfaces in a single implementation class.
|
||||
====
|
||||
|
||||
@@ -16,34 +16,47 @@
|
||||
package org.springframework.data.mapping.callback;
|
||||
|
||||
/**
|
||||
* Marker interface for entity callbacks to be implemented in specific callback subtypes intended for internal usage
|
||||
* within store specific implementations. <br />
|
||||
* Marker interface for entity callbacks to be implemented in specific callback subtypes. Intended for internal usage
|
||||
* within store specific implementations.
|
||||
* <h3>Ordering {@link EntityCallback}</h3>
|
||||
* <p/>
|
||||
* Multiple entity callbacks are invoked sequentially with the result of the previous callback. Callbacks are unordered
|
||||
* by default. It is possible to define the order in which listeners for a certain domain type are to be invoked. To do
|
||||
* so, add Spring's common {@link org.springframework.core.annotation.Order @Order} annotation or implement
|
||||
* {@link org.springframework.core.Ordered}.
|
||||
* <h3>Exception Handling</h3>
|
||||
* <p />
|
||||
* Multiple entity callbacks are invoked sequentially with the result of the previous callback. Those callbacks do by
|
||||
* default not follow an explicit order of invocation. It is strongly recommended to enforce ordering for callbacks of
|
||||
* the same by implementing {@link org.springframework.core.Ordered} or following the annotation driven approach using
|
||||
* {@link org.springframework.core.annotation.Order}.
|
||||
* While it is possible for a {@link EntityCallback} to declare that it throws arbitrary exception types, any checked
|
||||
* exceptions thrown from a {@link EntityCallback} are wrapped in an
|
||||
* {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException} since the callback mechanism can
|
||||
* only handle runtime exceptions. Entity callback processing is stopped on the {@link EntityCallback} that raised an
|
||||
* exception and the caused exception is propagated to the caller.
|
||||
* <h3>Domain Type Binding</h3>
|
||||
* <p />
|
||||
* Entity callbacks are invoked after publishing {@link org.springframework.context.ApplicationEvent events}.
|
||||
* An {@link EntityCallback} can generically declare the domain type that it is able to process by specifying the
|
||||
* generic type parameter {@code <T>}. When registered with a Spring
|
||||
* {@link org.springframework.context.ApplicationContext}, callbacks are filtered accordingly, with the callback getting
|
||||
* invoked for assignable domain objects only.
|
||||
* <p/>
|
||||
* Typically, entity callbacks are invoked after publishing {@link org.springframework.context.ApplicationEvent events}.
|
||||
* <p/>
|
||||
* <h3>Defining {@link EntityCallback} Interfaces</h3>
|
||||
* <p />
|
||||
* A store specific {@link EntityCallback} needs to define a callback method accepting an object of the parameterized
|
||||
* type as its first argument followed by additional <i>optional</i> arguments.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* A {@link EntityCallback} interface needs to define a callback method accepting an object of the parameterized type as
|
||||
* its first argument followed by additional <i>optional</i> arguments.
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* public interface BeforeSaveCallback<T> extends EntityCallback<T> {
|
||||
*
|
||||
* T onBeforeSave(T entity, String collection);
|
||||
* T onBeforeSave(T entity, String collection);
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* The
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @param <T> Entity type. Used to detect {@link EntityCallback callbacks} to invoke via their generic type signature.
|
||||
* @param <T> Entity type used to detect {@link EntityCallback callbacks} to invoke via their generic type signature.
|
||||
* @since 2.2
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.Order
|
||||
*/
|
||||
|
||||
@@ -19,8 +19,12 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by objects that can manage a number of {@link EntityCallback} objects and invoke these
|
||||
* with a specific entity.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
* @see EntityCallback
|
||||
*/
|
||||
public interface EntityCallbacks {
|
||||
|
||||
@@ -39,7 +43,7 @@ public interface EntityCallbacks {
|
||||
* @param callbackType must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param args optional arguments.
|
||||
* @param <T>
|
||||
* @param <T> Entity type.
|
||||
* @return never {@literal null}.
|
||||
* @throws IllegalArgumentException if a required argument is {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -21,8 +21,12 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by objects that can manage a number of {@link EntityCallback} objects and invoke these
|
||||
* with a specific entity.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
* @see EntityCallback
|
||||
*/
|
||||
public interface ReactiveEntityCallbacks {
|
||||
|
||||
@@ -42,7 +46,7 @@ public interface ReactiveEntityCallbacks {
|
||||
* @param callbackType must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param args optional arguments.
|
||||
* @param <T>
|
||||
* @param <T> Entity type.
|
||||
* @return a {@link Mono} emitting the result after invoking the callbacks.
|
||||
* @throws IllegalArgumentException if a required argument is {@literal null}.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user