1 关于 Auditing 

Auditing 翻译过来就是审计和审核,在实际的业务中,需要记录一张表的操作时间及操作者,并方便地记录操作日志,Spring Data JPA 为我们提供了审计的架构实现,并提供了4个注解专门实现这些功能

  1. @CreatedBy:由哪个用户创建
  2. @CreatedDate:创建的时间
  3. @LastModifiedBy:最近一次修改由哪个用户发起
  4. @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_FILTERCREATED_DATE_FILTERLAST_MODIFIED_BY_FILTERLAST_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.
This call is synchronous with the persist operation.

新增之前
@PreRemove

Excecuted before the entity manager remove operation is actually executed or cascaded.
This call is synchronous with the remove operation.

删除之前
@PostPersist

Excecuted after the entity manager persist operation is actually executed or cascaded.
This call is invoked after the database INSERT is executed.

新增之后
@PostRemove

Excecuted after the entity manager remove operation is actually executed or cascaded.
This call is synchronous with the remove operation.

删除之后
@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());
    }
}

 

posted on 2019-10-31 22:15  四维胖次  阅读(5111)  评论(0编辑  收藏  举报