스프링

Entity Callbacks 실행 과정

belljun 2025. 4. 10. 22:02

BeforeSaveCallback를 기준으로 작성하였습니다.

Entity Callbacks 공식 문서 정리

동기식, 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());  
}

beforeExecuteafterExecute를 통해 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을 실행하는 과정

callbackEntityCallbacks를 구현한 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));  
    });  
}

R2dbcEntityTemplatedoInsert 과정에서 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만 실행시키고 있는 것을 확인할 수 있습니다.

Webflux, ReactiveTransactionManager 환경에서의 이벤트 발행 차이

 

callback을 실행하는 과정

callbackReactiveEntityCallbacks를 구현한 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을 실행시킵니다.