1 关于 Auditing
Auditing 翻译过来就是审计和审核,在实际的业务中,需要记录一张表的操作时间及操作者,并方便地记录操作日志,Spring Data JPA 为我们提供了审计的架构实现,并提供了4个注解专门实现这些功能
- @CreatedBy:由哪个用户创建
- @CreatedDate:创建的时间
- @LastModifiedBy:最近一次修改由哪个用户发起
- @LastModifiedDate:最近一次修改的时间
阅读指引:
使用参见 1,2,4;
基本了解参见 3.1,3.2, 3.3;
深入了解工作原理参见 3.4
2 Auditing 配置
2.1 Customer 里的 Auditing 配置
2.1.1 添加4个注解
1 @CreatedDate 2 private Date createdDate; 3 @CreatedBy 4 private Long createdByCustomerId; 5 6 @LastModifiedDate 7 private Date lastModifiedDate; 8 @LastModifiedBy 9 private Long lastModifiedByCustomerId;
2.1.2 在实体上添加 @EntityListeners(value = {AuditingEntityListener.class})
最终的实体类应该是这个样子
1 @Entity 2 @Data 3 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 4 @EntityListeners(value = {AuditingEntityListener.class}) 5 public class Customer { 6 @Id 7 @GeneratedValue 8 @EqualsAndHashCode.Include 9 private Long id; 10 @EqualsAndHashCode.Include 11 private String name; 12 13 14 @CreatedDate 15 private Date createdDate; 16 @CreatedBy 17 private Long createdByCustomerId; 18 19 @LastModifiedDate 20 private Date lastModifiedDate; 21 @LastModifiedBy 22 private Long lastModifiedByCustomerId; 23 24 }
2.1.3 实现 AuditorAware 告诉 JPA 当前用户
第一种:可以从 request 或者 session 中取
1 @Component 2 public class AuditorAwareImpl implements AuditorAware<Long> { 3 @Override 4 public Optional<Long> getCurrentAuditor() { 5 try { 6 // IllegalStateException 7 RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); 8 Object userIdObject = requestAttributes.getAttribute("userId", RequestAttributes.SCOPE_SESSION); 9 if (userIdObject == null) { 10 return Optional.empty(); 11 } 12 // ClassCastException 13 Long userId = (Long) userIdObject; 14 return Optional.of(userId); 15 } catch (IllegalStateException | ClassCastException e) { 16 return Optional.empty(); 17 } 18 } 19 }
第二种:当集成了 Security 的时候,可以从 SecurityContextHolder 取得
1 @Component 2 public class AuditorAwareSecurityImpl implements AuditorAware<Long> { 3 @Override 4 public Optional<Long> getCurrentAuditor() { 5 try { 6 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 7 Customer customer = (Customer) authentication.getPrincipal(); 8 return Optional.of(customer.getId()); 9 } catch (ClassCastException e) { 10 return Optional.empty(); 11 } 12 13 } 14 }
2.1.4 启动类中加入 @EnableJpaAuditing 开启 JPA 的 Auditing 功能
当执行 created retrieve update 操作时,AuditingEntityListener 会自动更新四个字段的值
2.2 @MappedSuperClass
在实际工作中, 我们需要对实体进行改进,可以将多个字段抽取到一个抽象基类中
1 @MappedSuperclass 2 @EntityListeners(AuditingEntityListener.class) 3 @NoArgsConstructor 4 @Getter 5 @Setter 6 public abstract class AbstractAuditable { 7 @Id 8 @GeneratedValue 9 protected Long id; 10 11 @CreatedDate 12 protected Date createdDate; 13 @CreatedBy 14 protected Long createdByCustomerId; 15 16 @LastModifiedDate 17 protected Date lastModifiedDate; 18 @LastModifiedBy 19 protected Long lastModifiedByCustomerId; 20 }
使用时, 直接继承 AbstractAuditable 即可
@Entity @Getter @Setter @NoArgsConstructor public class CityAuditable extends AbstractAuditable{ private String name; }
3 AuditingEntityListener 源码解析
3.1 源码
1 @Configurable 2 public class AuditingEntityListener { 3 4 private @Nullable ObjectFactory<AuditingHandler> handler; 5 6 /** 7 * Configures the {@link AuditingHandler} to be used to set the current auditor on the domain types touched. 8 * 9 * @param auditingHandler must not be {@literal null}. 10 */ 11 public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) { 12 13 Assert.notNull(auditingHandler, "AuditingHandler must not be null!"); 14 this.handler = auditingHandler; 15 } 16 17 /** 18 * Sets modification and creation date and auditor on the target object in case it implements {@link Auditable} on 19 * persist events. 20 * 21 * @param target 22 */ 23 @PrePersist 24 public void touchForCreate(Object target) { 25 26 Assert.notNull(target, "Entity must not be null!"); 27 28 if (handler != null) { 29 30 AuditingHandler object = handler.getObject(); 31 if (object != null) { 32 object.markCreated(target); 33 } 34 } 35 } 36 37 /** 38 * Sets modification and creation date and auditor on the target object in case it implements {@link Auditable} on 39 * update events. 40 * 41 * @param target 42 */ 43 @PreUpdate 44 public void touchForUpdate(Object target) { 45 46 Assert.notNull(target, "Entity must not be null!"); 47 48 if (handler != null) { 49 50 AuditingHandler object = handler.getObject(); 51 if (object != null) { 52 object.markModified(target); 53 } 54 } 55 } 56 }
3.2 主类解析
进入 AuditingEntityListener,我们发现
- 这是个被 @Configurable 标注的非 Spring 管理的 Bean;
- 它使用了委托代理模式将具体工作交付给了 AuditingHandler 进行;
- 它的具体方法 touchForCreate 上有 javax.persistence.PrePersist 注解
- 它的具体方法 touchForUpdate 上有 javax.persistence.PreUpdate 注解
3.3 流程解析
点进主方法 touchForCreate(Object target) 方法,我们发现该方法尝试从 ObjectFactory 获取 AuditingHandler, 并调用 AuditingHandler#markCreated(T source) 方法标注这个类为新建;
点进 AuditingHandler#markCreated(T source) 方法,我们发现该方法调用了 AuditingHandler#touch(T target, boolean isNew) 方法;
点进 AuditingHandler#touch(T target, boolean isNew) 方法,我们发现该方法尝试使用 DefaultAuditableBeanWrapperFactory#getBeanWrapperFor(T source) 获取一个包装好 Auditable 类型 Bean 实体的 AuditableBeanWrapper 实例;然后依次调用 AuditingHandler#touchAuditor(AuditableBeanWrapper<?> wrapper, boolean isNew) 设置创建者,调用 AuditingHandler#touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) 设置创建时间
3.4 相关类及方法解析
3.4.1 DefaultAuditableBeanWrapperFactory#getBeanWrapperFor
如果当前实体类目标实现了 Auditable 接口 ,则直接返回一个 Auditable 的包装类 AuditableInterfaceBeanWrapper,
否则调用 AnnotationAuditingMetadata.getMetadata(target.getClass()) 获取目标类的元数据,根据元素据判断 target 是否是一个 Auditable (可 Auditing)实例(至少含有四个 Auditing 基本标签中的一个),如果是则返回一个包装类 ReflectionAuditingBeanWrapper
1 /** 2 * Returns an {@link AuditableBeanWrapper} if the given object is capable of being equipped with auditing information. 3 * 4 * @param source the auditing candidate. 5 * @return 6 */ 7 @SuppressWarnings("unchecked") 8 public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) { 9 10 Assert.notNull(source, "Source must not be null!"); 11 12 return Optional.of(source).map(it -> { 13 14 if (it instanceof Auditable) { 15 return (AuditableBeanWrapper<T>) new AuditableInterfaceBeanWrapper((Auditable<Object, ?, TemporalAccessor>) it); 16 } 17 18 AnnotationAuditingMetadata metadata = AnnotationAuditingMetadata.getMetadata(it.getClass()); 19 20 if (metadata.isAuditable()) { 21 return new ReflectionAuditingBeanWrapper<T>(it); 22 } 23 24 return null; 25 }); 26 }
3.4.2 AnnotationAuditingMetadata#getMetadata(Class<?> type)
该方法 从 AnnotationAuditingMetadata类 中的 ConcurrentHashMap类型的缓存 METADATA_CACHE 中获取或者保存一个新的 AnnotationAuditingMetadata 实例
3.4.3 AnnotationAuditingMetadata
该类的主要作用是根据 四个 Auditing 注解,构成被给予的类的元数据,并提供 isAuditable() 方法供调用者检查被给予类是否为 Auditable(可 Auditing) 实例。
3.4.3.1 类成员1:
CREATED_BY_FILTER,CREATED_DATE_FILTER,LAST_MODIFIED_BY_FILTER,LAST_MODIFIED_DATE_FILTER:四个 Filter 字段从 Auditing 四个基本注解类构成;
1 private static final AnnotationFieldFilter CREATED_BY_FILTER = new AnnotationFieldFilter(CreatedBy.class); 2 private static final AnnotationFieldFilter CREATED_DATE_FILTER = new AnnotationFieldFilter(CreatedDate.class); 3 private static final AnnotationFieldFilter LAST_MODIFIED_BY_FILTER = new AnnotationFieldFilter(LastModifiedBy.class); 4 private static final AnnotationFieldFilter LAST_MODIFIED_DATE_FILTER = new AnnotationFieldFilter( 5 LastModifiedDate.class);
3.4.3.2 类成员2:
四个 Optional<Field> 与 四个基本注解标注的实体属性字段相对应;
private final Optional<Field> createdByField; private final Optional<Field> createdDateField; private final Optional<Field> lastModifiedByField; private final Optional<Field> lastModifiedDateField;
3.4.3.3 类成员3:
SUPPORTED_DATE_TYPES:存放支持的时间类型,供后边检查 createdDateField 和 lastModifiedDateField 两个时间域声明的 class 正确与否;
1 static final List<String> SUPPORTED_DATE_TYPES; 2 3 static { 4 5 List<String> types = new ArrayList<>(5); 6 types.add("org.joda.time.DateTime"); 7 types.add("org.joda.time.LocalDateTime"); 8 types.add(Date.class.getName()); 9 types.add(Long.class.getName()); 10 types.add(long.class.getName()); 11 12 SUPPORTED_DATE_TYPES = Collections.unmodifiableList(types); 13 }
3.4.3.4 构造方法:
使用反射与四个 Filter 在目标实体类上获取四个参与 Auditing 的 Field 域,并包装成 Optional;
检查两个时间域的类型是否正确;
1 private AnnotationAuditingMetadata(Class<?> type) { 2 3 Assert.notNull(type, "Given type must not be null!"); 4 5 this.createdByField = Optional.ofNullable(ReflectionUtils.findField(type, CREATED_BY_FILTER)); 6 this.createdDateField = Optional.ofNullable(ReflectionUtils.findField(type, CREATED_DATE_FILTER)); 7 this.lastModifiedByField = Optional.ofNullable(ReflectionUtils.findField(type, LAST_MODIFIED_BY_FILTER)); 8 this.lastModifiedDateField = Optional.ofNullable(ReflectionUtils.findField(type, LAST_MODIFIED_DATE_FILTER)); 9 10 assertValidDateFieldType(createdDateField); 11 assertValidDateFieldType(lastModifiedDateField); 12 }
3.4.3.5 方法 assertValidDateFieldType
该方法首先检查成员 SUPPORTED_DATE_TYPES 中是否包含 filed 所指示的时间类型,否则检查是否是 Jsr310(JAVA 8) 支持的时间类型,或者是 org.threeten.bp.LocalDateTime 类型;
因此该方法覆盖的时间类型为12种,分别为:
- org.joda.time.DateTime
- org.joda.time.LocalDateTime
- java.util.Date
- java.lang.Long
- PrimitiveType.long
- java.time.LocalDateTime(JDK>=1.8)
- java.time.LocalDate(JDK>=1.8)
- java.time.LocalTime(JDK>=1.8)
- java.time.Instant(JDK>=1.8)
- org.threeten.bp.Instant;("org.threeten.bp.LocalDateTime" exists in classPath)
- org.threeten.bp.LocalDate;("org.threeten.bp.LocalDateTime" exists in classPath)
- org.threeten.bp.LocalDateTime;("org.threeten.bp.LocalDateTime" exists in classPath)
- org.threeten.bp.LocalTime;("org.threeten.bp.LocalDateTime" exists in classPath)
1 private void assertValidDateFieldType(Optional<Field> field) { 2 3 field.ifPresent(it -> { 4 5 if (SUPPORTED_DATE_TYPES.contains(it.getType().getName())) { 6 return; 7 } 8 9 Class<?> type = it.getType(); 10 11 if (Jsr310Converters.supports(type) || ThreeTenBackPortConverters.supports(type)) { 12 return; 13 } 14 15 throw new IllegalStateException(String.format( 16 "Found created/modified date field with type %s but only %s as well as java.time types are supported!", type, 17 SUPPORTED_DATE_TYPES)); 18 }); 19 }
3.4.3.6 方法 isAuditable()
该方法只检查存在任意一个或者以上的四个基本 Auditing 注解, 因此我们的审计可以只用到一个注解
1 public boolean isAuditable() { 2 return Optionals.isAnyPresent(createdByField, createdDateField, lastModifiedByField, lastModifiedDateField); 3 }
3.4.2 AuditingHandler#touchAuditor
该方法调用 AuditorAware#getCurrentAuditor 从 AuditorAware 的实例中获取当前的创建/修改者 Auditor ,并设置到 包装类包含的 实体属性中
该方法中使用的 AuditorAware 即为我们在 2.1.3 中的实现 !!
1 private Optional<Object> touchAuditor(AuditableBeanWrapper<?> wrapper, boolean isNew) { 2 3 Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); 4 5 return auditorAware.map(it -> { 6 7 Optional<?> auditor = it.getCurrentAuditor(); 8 9 Assert.notNull(auditor, 10 () -> String.format("Auditor must not be null! Returned by: %s!", AopUtils.getTargetClass(it))); 11 12 auditor.filter(__ -> isNew).ifPresent(foo -> wrapper.setCreatedBy(foo)); 13 auditor.filter(__ -> !isNew || modifyOnCreation).ifPresent(foo -> wrapper.setLastModifiedBy(foo)); 14 15 return auditor; 16 }); 17 }
3.4.2.1 方法 AuditableBeanWrapper#setCreatedBy
该方法对应 3.4.1中获取到的包装实例分别对应:
I. 当 target 是 Auditable 的实现,则调用: AuditableInterfaceBeanWrapper#setCreatedBy, 直接调用 Auditable#setter
1 @Override 2 public Object setCreatedBy(Object value) { 3 4 auditable.setCreatedBy(value); 5 return value; 6 }
II. 当 target 是 可 Auditing(有注解),则调用:ReflectionAuditingBeanWrapper#setCreatedBy,根据包装实例提供的元数据(见 3.4.3.2 与 该反射类的构造方法),使用反射在对应的 Field 域设置值
1 @Override 2 public Object setCreatedBy(Object value) { 3 return setField(metadata.getCreatedByField(), value); 4 } 5 6 public Optional<Field> getCreatedByField() { 7 return createdByField; 8 } 9 10 11 12 private <S> S setField(Optional<Field> field, S value) { 13 14 field.ifPresent(it -> ReflectionUtils.setField(it, target, value)); 15 16 return value; 17 }
3.4.3 AuditingHandler#touchDate
该方法调用 DateTimeProvider#getNow 从 DateTimeProvider (默认为: CurrentDateTimeProvider)中获得 TemporalAccessor 实例,并设置到 包装类包含的 实体属性中
1 private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) { 2 3 Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); 4 5 Optional<TemporalAccessor> now = dateTimeProvider.getNow(); 6 7 Assert.notNull(now, () -> String.format("Now must not be null! Returned by: %s!", dateTimeProvider.getClass())); 8 9 now.filter(__ -> isNew).ifPresent(it -> wrapper.setCreatedDate(it)); 10 now.filter(__ -> !isNew || modifyOnCreation).ifPresent(it -> wrapper.setLastModifiedDate(it)); 11 12 return now; 13 }
3.4.3.1 方法 AuditableBeanWrapper#setCreatedDate
该方法对应 3.4.1中获取到的包装实例分别对应:
I. 当 target 是 Auditable 的实现,则调用: AuditableInterfaceBeanWrapper#setCreatedDate, 直接调用 Auditable#setter
1 @Override 2 public TemporalAccessor setCreatedDate(TemporalAccessor value) { 3 4 auditable.setCreatedDate(getAsTemporalAccessor(Optional.of(value), type).orElseThrow(IllegalStateException::new)); 5 6 return value; 7 }
II. 当 target 是 可 Auditing(有注解),则调用:ReflectionAuditingBeanWrapper#setCreatedDate,根据包装实例提供的元数据(见 3.4.3.2 与 该反射类的构造方法),使用反射在对应的 Field 域设置值
1 @Override 2 public TemporalAccessor setCreatedDate(TemporalAccessor value) { 3 4 return setDateField(metadata.getCreatedDateField(), value); 5 }
3.4.3.2 TemporalAccessor
I. 当 EnableJpaAuditing#dateTimeProviderRef 未配置时, 默认使用 LocalDateTime
protected BeanDefinitionBuilder configureDefaultAuditHandlerAttributes(AuditingConfiguration configuration,
BeanDefinitionBuilder builder) {
if (StringUtils.hasText(configuration.getAuditorAwareRef())) {
builder.addPropertyValue(AUDITOR_AWARE,
createLazyInitTargetSourceBeanDefinition(configuration.getAuditorAwareRef()));
} else {
builder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE);
}
builder.addPropertyValue(SET_DATES, configuration.isSetDates());
builder.addPropertyValue(MODIFY_ON_CREATE, configuration.isModifyOnCreate());
if (StringUtils.hasText(configuration.getDateTimeProviderRef())) {
builder.addPropertyReference(DATE_TIME_PROVIDER, configuration.getDateTimeProviderRef());// 使用已配置的 provider
} else {
builder.addPropertyValue(DATE_TIME_PROVIDER, CurrentDateTimeProvider.INSTANCE);//使用默认的 LocalDateTime
}
builder.setRole(AbstractBeanDefinition.ROLE_INFRASTRUCTURE);
return builder;
}
II. 当 EnableJpaAuditing#dateTimeProviderRef 已配置时,使用配置的 Provider (见3.4.3.5 中的种时间类型,它们都实现了该接口),如何实现该 Provider 参考 CurrentDateTimeProvider
4 Listener 事件扩展
4.1 Java Persistence API
通过对源码的分析与调试,我们可以自定义自己的 Listener,需要注意的是 Java Persistence API 底层又为我们提供了如下的 Callbacks 注解
Type | Description | 简要描述 |
@PrePersist |
Excecuted before the entity manager persist operation is actually executed or cascaded. |
新增之前 |
@PreRemove |
Excecuted before the entity manager remove operation is actually executed or cascaded. |
删除之前 |
@PostPersist |
Excecuted after the entity manager persist operation is actually executed or cascaded. |
新增之后 |
@PostRemove |
Excecuted after the entity manager remove operation is actually executed or cascaded. |
删除之后 |
@PreUpdate | Invoked before the database UPDATE is executed. | 更新之前 |
@PostUpdate | Invoked after the database UPDATE is executed. | 更新之后 |
@PostLoad | Invoked after an entity has been loaded into the current persistence context or an entity has been refreshed. | 加载之后 |
4.2 自定义 EntityListener
1. 新建一个 EntityListener ActionsLogsListener
1 @CommonsLog 2 public class ActionsLogsListener<E extends Serializable> { 3 @PostPersist 4 public void postPersist(E entity) { 5 this.notice(entity, NoticeType.create); 6 } 7 8 @PostRemove 9 public void postRemove(E entity) { 10 this.notice(entity, NoticeType.remove); 11 } 12 13 @PostUpdate 14 public void postUpdate(E entity) { 15 this.notice(entity, NoticeType.update); 16 } 17 18 @PostLoad 19 public void postLoad(E entity) { 20 this.notice(entity, NoticeType.load); 21 } 22 23 private void notice(E e, NoticeType type) { 24 Preconditions.checkArgument(e != null && type != null); 25 log.info(String.format("%s 执行了 %s 操作", e, type.getDescription())); 26 } 27 28 @Getter 29 @RequiredArgsConstructor 30 private enum NoticeType { 31 create("创建"), 32 remove("删除"), 33 update("更新"), 34 load("查询"); 35 private final String description; 36 } 37 }
2. 添加该 CustomActLogsListener 到 EntityListeners#value 中
1 @Entity 2 @Data 3 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 4 @EntityListeners(value = {AuditingEntityListener.class,ActionsLogsListener.class}) 5 public class Customer { 6 @Id 7 @GeneratedValue 8 @EqualsAndHashCode.Include 9 private Long id; 10 @EqualsAndHashCode.Include 11 private String name; 12 13 14 @CreatedDate 15 private Date createdDate; 16 @CreatedBy 17 private Long createdByCustomerId; 18 19 @LastModifiedDate 20 private Date lastModifiedDate; 21 @LastModifiedBy 22 private Long lastModifiedByCustomerId; 23 24 }
5 附
5.1 判断当前使用的 JDK>=1.8?
public static final boolean IS_JDK_8 = org.springframework.util.ClassUtils.isPresent("java.time.Clock", AnnotationAuditingMetadata.class.getClassLoader());
5.2 使用枚举类型的单例
public interface DateTimeProvider { /** * Returns the current time to be used as modification or creation date. * * @return */ Optional<TemporalAccessor> getNow(); } public enum CurrentDateTimeProvider implements DateTimeProvider { INSTANCE; /* * (non-Javadoc) * @see org.springframework.data.auditing.DateTimeProvider#getNow() */ @Override public Optional<TemporalAccessor> getNow() { return Optional.of(LocalDateTime.now()); } }