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()); } }
