diff --git a/src/main/antora/modules/ROOT/pages/auditing.adoc b/src/main/antora/modules/ROOT/pages/auditing.adoc index 57da96964..a15034577 100644 --- a/src/main/antora/modules/ROOT/pages/auditing.adoc +++ b/src/main/antora/modules/ROOT/pages/auditing.adoc @@ -3,13 +3,16 @@ [[auditing.basics]] == Basics -Spring Data provides sophisticated support to transparently keep track of who created or changed an entity and when the change happened.To benefit from that functionality, you have to equip your entity classes with auditing metadata that can be defined either using annotations or by implementing an interface. + +Spring Data provides sophisticated support to transparently keep track of who created or changed an entity and when the change happened. +To benefit from that functionality, you have to equip your entity classes with auditing metadata that can be defined either using annotations or by implementing an interface. Additionally, auditing has to be enabled either through Annotation configuration or XML configuration to register the required infrastructure components. Please refer to the store-specific section for configuration samples. [NOTE] ==== Applications that only track creation and modification dates are not required do make their entities implement <>. +If no `AuditorAware` or `DateTimeProvider` bean is configured, `AuditingHandler` will use Spring's autowiring to detect a matching bean if beans of the corresponding type are available in the application context. ==== [[auditing.annotations]] diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 3454690a7..9ac524fcb 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -19,6 +19,8 @@ import java.util.Optional; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.InitializingBean; import org.springframework.data.domain.AuditorAware; import org.springframework.data.mapping.context.MappingContext; @@ -66,12 +68,10 @@ public class AuditingHandler extends AuditingHandlerSupport implements Initializ /** * Setter to inject a {@code AuditorAware} component to retrieve the current auditor. * - * @param auditorAware must not be {@literal null}. + * @param auditorAware can be {@literal null} if no auditor-aware is available. */ - public void setAuditorAware(AuditorAware auditorAware) { - - Assert.notNull(auditorAware, "AuditorAware must not be null"); - this.auditorAware = Optional.of(auditorAware); + public void setAuditorAware(@Nullable AuditorAware auditorAware) { + this.auditorAware = Optional.ofNullable(auditorAware); } /** diff --git a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java index 6b2b7da58..90616d104 100644 --- a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java @@ -17,6 +17,8 @@ package org.springframework.data.auditing; import reactor.core.publisher.Mono; +import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.ReactiveAuditorAware; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.PersistentEntities; @@ -56,12 +58,10 @@ public class ReactiveAuditingHandler extends AuditingHandlerSupport { /** * Setter to inject a {@link ReactiveAuditorAware} component to retrieve the current auditor. * - * @param auditorAware must not be {@literal null}. + * @param auditorAware can be {@literal null}. */ - public void setAuditorAware(ReactiveAuditorAware auditorAware) { - - Assert.notNull(auditorAware, "AuditorAware must not be null"); - this.auditorAware = auditorAware; + public void setAuditorAware(@Nullable ReactiveAuditorAware auditorAware) { + this.auditorAware = auditorAware == null ? Mono::empty : auditorAware; } /** diff --git a/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java b/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java index a8572dad7..b0f398ac1 100644 --- a/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java +++ b/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java @@ -116,12 +116,13 @@ public abstract class AuditingBeanDefinitionRegistrarSupport implements ImportBe protected BeanDefinitionBuilder configureDefaultAuditHandlerAttributes(AuditingConfiguration configuration, BeanDefinitionBuilder builder) { - builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); - if (StringUtils.hasText(configuration.getAuditorAwareRef())) { builder.addPropertyValue(AUDITOR_AWARE, createLazyInitTargetSourceBeanDefinition(configuration.getAuditorAwareRef())); } + else { + builder.addAutowiredProperty(AUDITOR_AWARE); + } builder.addPropertyValue(SET_DATES, configuration.isSetDates()); builder.addPropertyValue(MODIFY_ON_CREATE, configuration.isModifyOnCreate()); @@ -129,6 +130,9 @@ public abstract class AuditingBeanDefinitionRegistrarSupport implements ImportBe if (StringUtils.hasText(configuration.getDateTimeProviderRef())) { builder.addPropertyReference(DATE_TIME_PROVIDER, configuration.getDateTimeProviderRef()); } + else { + builder.addAutowiredProperty(DATE_TIME_PROVIDER); + } builder.setRole(AbstractBeanDefinition.ROLE_INFRASTRUCTURE); diff --git a/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java b/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java index 00180d8e3..69879fb37 100755 --- a/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java @@ -20,19 +20,23 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.annotation.Annotation; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.config.AutowiredPropertyMarker; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.auditing.EnableAuditing; +import org.springframework.data.mapping.context.PersistentEntities; /** * Unit tests for {@link AuditingBeanDefinitionRegistrarSupport}. @@ -42,6 +46,7 @@ import org.springframework.data.auditing.EnableAuditing; * @author Oliver Gierke * @author Francisco Soler * @author Jaeyeon Kim + * @author Mark Paluch */ @ExtendWith(MockitoExtension.class) class AuditingBeanDefinitionRegistrarSupportUnitTests { @@ -77,13 +82,28 @@ class AuditingBeanDefinitionRegistrarSupportUnitTests { .isThrownBy(() -> registrar.registerBeanDefinitions(metadata, null)); } - @Test // DATACMNS-3177 + @Test // GH-3177 void setsAuditorAwareAndDateTimeProviderIfConfigured() { + AuditingConfiguration configuration = new DummyAuditingBeanDefinitionRegistrarSupport().getConfiguration(null); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class); + DummyAuditingBeanDefinitionRegistrarSupport registrar = new DummyAuditingBeanDefinitionRegistrarSupport(); + + BeanDefinitionBuilder result = registrar.configureDefaultAuditHandlerAttributes(configuration, builder); + AbstractBeanDefinition beanDefinition = result.getBeanDefinition(); + + assertThat(beanDefinition.getPropertyValues().contains("auditorAware")).isTrue(); + assertThat(beanDefinition.getPropertyValues().contains("dateTimeProvider")).isTrue(); + } + + @Test // GH-3177 + void doesNotSetAuditorAwareAndDateTimeProviderIfNotConfigured() { + AuditingConfiguration configuration = new AuditingConfiguration() { @Override public String getAuditorAwareRef() { - return "auditorAwareBean"; + return ""; } @Override @@ -93,7 +113,7 @@ class AuditingBeanDefinitionRegistrarSupportUnitTests { @Override public String getDateTimeProviderRef() { - return "dateTimeProviderBean"; + return ""; } @Override @@ -108,13 +128,12 @@ class AuditingBeanDefinitionRegistrarSupportUnitTests { BeanDefinitionBuilder result = registrar.configureDefaultAuditHandlerAttributes(configuration, builder); AbstractBeanDefinition beanDefinition = result.getBeanDefinition(); - assertThat(beanDefinition.getAutowireMode()).isEqualTo(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); - assertThat(beanDefinition.getPropertyValues().contains("auditorAware")).isTrue(); - assertThat(beanDefinition.getPropertyValues().contains("dateTimeProvider")).isTrue(); + assertThat(beanDefinition.getPropertyValues().get("auditorAware")).isEqualTo(AutowiredPropertyMarker.INSTANCE); + assertThat(beanDefinition.getPropertyValues().get("dateTimeProvider")).isEqualTo(AutowiredPropertyMarker.INSTANCE); } - @Test // DATACMNS-3177 - void doesNotSetAuditorAwareAndDateTimeProviderIfNotConfigured() { + @Test // GH-3177 + void optionalAutowiringShouldConsiderOptionalProperties() { AuditingConfiguration configuration = new AuditingConfiguration() { @Override @@ -133,18 +152,24 @@ class AuditingBeanDefinitionRegistrarSupportUnitTests { } @Override - public boolean isModifyOnCreate() { return true; } + public boolean isModifyOnCreate() { + return true; + } }; BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class); + builder.addConstructorArgValue(PersistentEntities.of()); DummyAuditingBeanDefinitionRegistrarSupport registrar = new DummyAuditingBeanDefinitionRegistrarSupport(); BeanDefinitionBuilder result = registrar.configureDefaultAuditHandlerAttributes(configuration, builder); - AbstractBeanDefinition beanDefinition = result.getBeanDefinition(); - assertThat(beanDefinition.getAutowireMode()).isEqualTo(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); - assertThat(beanDefinition.getPropertyValues().contains("auditorAware")).isFalse(); - assertThat(beanDefinition.getPropertyValues().contains("dateTimeProvider")).isFalse(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("auditingHandler", result.getBeanDefinition()); + context.refresh(); + + AuditingHandler handler = context.getBean(AuditingHandler.class); + + assertThat(handler).extracting("auditorAware").isEqualTo(Optional.empty()); } static class SampleConfig {}