Entity Callbacks 실행 과정
BeforeSaveCallback
를 기준으로 작성하였습니다.
동기식, JDBC
entityCallbacks.callback 실행까지
JdbcAggregateTemplate
을 사용해 save
를 진행하는 과정은 아래와 같습니다.
// JdbcAggregateTemplate
public <T> T save(T instance) {
Assert.notNull(instance, "Aggregate instance must not be null");
verifyIdProperty(instance);
return performSave(new EntityAndChangeCreator<>(instance, changeCreatorSelectorForSave(instance)));
}
private <T> T performSave(EntityAndChangeCreator<T> instance) {
// noinspection unchecked
BatchingAggregateChange<T, RootAggregateChange<T>> batchingAggregateChange = //
BatchingAggregateChange.forSave((Class<T>) ClassUtils.getUserClass(instance.entity));
batchingAggregateChange.add(beforeExecute(instance));
Iterator<T> afterExecutionIterator = executor.executeSave(batchingAggregateChange).iterator();
Assert.isTrue(afterExecutionIterator.hasNext(), "Instances after execution must not be empty");
return afterExecute(batchingAggregateChange, afterExecutionIterator.next());
}
beforeExecute
과 afterExecute
를 통해 save
수행전후에 필요한 메서드를 실행시킵니다.
private <T> RootAggregateChange<T> beforeExecute(EntityAndChangeCreator<T> instance) {
Assert.notNull(instance.entity, "Aggregate instance must not be null");
T aggregateRoot = triggerBeforeConvert(instance.entity);
RootAggregateChange<T> change = instance.changeCreator.apply(aggregateRoot);
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
change.setRoot(aggregateRoot);
return change;
}
triggerBeforeConvert
를 통해 BeforeConvertCallback
를 실행시키고 triggerBeforeSave
를 통해 BeforeSaveCallback
를 실행시킵니다.
private <T> T triggerBeforeSave(T aggregateRoot, AggregateChange<T> change) {
eventDelegate.publishEvent(() -> new BeforeSaveEvent<>(aggregateRoot, change));
return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, change);
}
triggerBeforeSave
에서는 BeforeSaveCallback
뿐만 아니라 BeforeSaveEvent
이벤트 역시 발행하고 있는 것을 확인할 수 있습니다.
callback을 실행하는 과정
callback
을 EntityCallbacks
를 구현한 DefaultEntityCallbacks
에서 실행되고 있습니다.
DefaultEntityCallbacks(BeanFactory beanFactory) {
this.callbackDiscoverer = new EntityCallbackDiscoverer(
beanFactory instanceof GenericApplicationContext ac ? ac.getBeanFactory() : beanFactory);
}
등록한 콜백 빈을 찾기 위해 beanFactory
를 주입하여 DefaultEntityCallbacks
를 생성합니다.
// DefaultEntityCallbacks
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> T callback(Class<? extends EntityCallback> callbackType, T entity, Object... args) {
Assert.notNull(entity, "Entity must not be null");
Class<T> entityType = (Class<T>) ClassUtils.getUserClass(entity.getClass());
Method callbackMethod = callbackMethodCache.computeIfAbsent(callbackType, it -> {
Method method = EntityCallbackDiscoverer.lookupCallbackMethod(it, entityType, args);
ReflectionUtils.makeAccessible(method);
return method;
});
T value = entity;
for (EntityCallback<T> callback : callbackDiscoverer.getEntityCallbacks(entityType,
ResolvableType.forClass(callbackType))) {
BiFunction<EntityCallback<T>, T, Object> callbackFunction = EntityCallbackDiscoverer
.computeCallbackInvokerFunction(callback, callbackMethod, args);
value = callbackInvoker.invokeCallback(callback, value, callbackFunction);
}
return value;
}
callback
메서드 실행 시에는 Class<? extends EntityCallback> callbackType
와 같이 EntityCallback
의 구체적 타입을 함께 전달받아 callbackDiscoverer
를 통해 원하는 callback
을 조회합니다.
// SimpleEntityCallbackInvoker
@Override
public <T> T invokeCallback(EntityCallback<T> callback, T entity,
BiFunction<EntityCallback<T>, T, Object> callbackInvokerFunction) {
try {
Object value = callbackInvokerFunction.apply(callback, entity);
if (value != null) {
return (T) value;
}
throw new IllegalArgumentException(
"Callback invocation on %s returned null value for %s".formatted(callback.getClass(), entity));
} catch (IllegalArgumentException | ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || EntityCallbackInvoker.matchesClassCastMessage(msg, entity.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message. Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching callback type for entity callback: " + callback, ex);
}
return entity;
} else {
throw ex;
}
}
}
그리고 그렇게 찾은 callback
을 실행시킵니다.
비동기식, R2DBC
entityCallbacks.callback 실행까지
ReactiveCrudRepository
을 사용해 save
를 진행하는 과정은 아래와 같습니다.
// SimpleR2dbcRepository
@Override
@Transactional
public <S extends T> Mono<S> save(S objectToSave) {
Assert.notNull(objectToSave, "Object to save must not be null");
if (this.entity.isNew(objectToSave)) {
return this.entityOperations.insert(objectToSave);
}
return this.entityOperations.update(objectToSave);
}
// R2dbcEntityTemplate
@Override
public <T> Mono<T> insert(T entity) throws DataAccessException {
Assert.notNull(entity, "Entity must not be null");
return doInsert(entity, getRequiredEntity(entity).getQualifiedTableName());
}
<T> Mono<T> doInsert(T entity, SqlIdentifier tableName) {
RelationalPersistentEntity<T> persistentEntity = getRequiredEntity(entity);
return maybeCallBeforeConvert(entity, tableName).flatMap(onBeforeConvert -> {
T initializedEntity = setVersionIfNecessary(persistentEntity, onBeforeConvert);
OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(initializedEntity);
potentiallyRemoveId(persistentEntity, outboundRow);
return maybeCallBeforeSave(initializedEntity, outboundRow, tableName) //
.flatMap(entityToSave -> doInsert(entityToSave, tableName, outboundRow));
});
}
R2dbcEntityTemplate
의 doInsert
과정에서 maybeCallBeforeConvert
를 통해 BeforeConvertCallback
를 실행시키고 maybeCallBeforeSave
를 통해 BeforeSaveCallback
를 실행시킵니다.
protected <T> Mono<T> maybeCallBeforeSave(T object, OutboundRow row, SqlIdentifier table) {
if (entityCallbacks != null) {
return entityCallbacks.callback(BeforeSaveCallback.class, object, row, table);
}
return Mono.just(object);
}
maybeCallBeforeSave
에서는 위의 동기식 경우와 다르게 callback
만 실행시키고 있는 것을 확인할 수 있습니다.
callback을 실행하는 과정
callback
을 ReactiveEntityCallbacks
를 구현한 DefaultReactiveEntityCallbacks
에서 실행되고 있습니다.
DefaultReactiveEntityCallbacks(BeanFactory beanFactory) {
this.callbackDiscoverer = new EntityCallbackDiscoverer(beanFactory);
}
등록한 콜백 빈을 찾기 위해 beanFactory
를 주입하여 EntityCallbackDiscoverer
를 생성합니다.
// DefaultReactiveEntityCallbacks
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> Mono<T> callback(Class<? extends EntityCallback> callbackType, T entity, Object... args) {
Assert.notNull(entity, "Entity must not be null");
Class<T> entityType = (Class<T>) ClassUtils.getUserClass(entity.getClass());
Method callbackMethod = callbackMethodCache.computeIfAbsent(callbackType, it -> {
Method method = EntityCallbackDiscoverer.lookupCallbackMethod(it, entityType, args);
ReflectionUtils.makeAccessible(method);
return method;
});
Mono<T> deferredCallbackChain = Mono.just(entity);
for (EntityCallback<T> callback : callbackDiscoverer.getEntityCallbacks(entityType,
ResolvableType.forClass(callbackType))) {
BiFunction<EntityCallback<T>, T, Object> callbackFunction = EntityCallbackDiscoverer
.computeCallbackInvokerFunction(callback, callbackMethod, args);
deferredCallbackChain = deferredCallbackChain
.flatMap(it -> callbackInvoker.invokeCallback(callback, it, callbackFunction));
}
return deferredCallbackChain;
}
callback
메서드 실행 시에는 Class<? extends EntityCallback> callbackType
와 같이 EntityCallback
의 구체적 타입을 함께 전달받아 callbackDiscoverer
를 통해 원하는 callback
을 조회합니다.
@Override
public <T> Mono<T> invokeCallback(EntityCallback<T> callback, T entity,
BiFunction<EntityCallback<T>, T, Object> callbackInvokerFunction) {
try {
Object value = callbackInvokerFunction.apply(callback, entity);
if (value != null) {
return value instanceof Publisher ? Mono.from((Publisher<T>) value) : Mono.just((T) value);
}
throw new IllegalArgumentException(
"Callback invocation on %s returned null value for %s".formatted(callback.getClass(), entity));
} catch (IllegalArgumentException | ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || EntityCallbackInvoker.matchesClassCastMessage(msg, entity.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message. Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching callback type for entity callback: " + callback, ex);
}
return Mono.just(entity);
} else {
return Mono.error(ex);
}
}
}
그리고 그렇게 찾은 callback
을 실행시킵니다.