定制Spring和Hibernate系统的审计Audit日志

  如果你希望能够对所有数据库操作自动审计,推荐使用  Envers 或 spring data jpa auditing,但是如果因为一些原因不使用Envers,你可以使用Hibernate的事件监听器和Spring事务同步机制完成类似审计日志功能,本文主要演示这方面的技术。

使用事件监听器,你能截获所有的新增 修改和删除操作,但是有一点技巧在里面,如果你需要flush会话,你不能在事件监听器中和数据库交互,你应该存储事件以便日后处理,你能注册监听器作为一个Spring bean如下:

@Component
public class AuditLogEventListener
        implements PostUpdateEventListener, PostInsertEventListener, PostDeleteEventListener {
    @Override
    public void onPostDelete(PostDeleteEvent event) {
        AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class);
        if (audited != null) {
            AuditLogServiceData.getHibernateEvents().add(event);
        }
    }
    @Override
    public void onPostInsert(PostInsertEvent event) {
        AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class);
        if (audited != null) {
            AuditLogServiceData.getHibernateEvents().add(event);
        }
    }
    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class);
        if (audited != null) {
            AuditLogServiceData.getHibernateEvents().add(event);
        }
    }
      
    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return true; // Envers sets this to true only if the entity is versioned. So figure out for yourself if that's needed
    }
}

注意到AuditedEntity是一个定制的marker元注释(retention=runtime, target=type),你可以将它作为元注释用在你的实体上面。

在下面AuditServiceData类使用Spring的事务机制进行持久化:

public class AuditLogServiceData {
    private static final String HIBERNATE_EVENTS = "hibernateEvents";
    @SuppressWarnings("unchecked")
    public static List<Object> getHibernateEvents() {
        if (!TransactionSynchronizationManager.hasResource(HIBERNATE_EVENTS)) {
            TransactionSynchronizationManager.bindResource(HIBERNATE_EVENTS, new ArrayList<>());
        }
        return (List<Object>) TransactionSynchronizationManager.getResource(HIBERNATE_EVENTS);
    }
    public static Long getActorId() {
        return (Long) TransactionSynchronizationManager.getResource(AUDIT_LOG_ACTOR);
    }
    public static void setActor(Long value) {
        if (value != null) {
            TransactionSynchronizationManager.bindResource(AUDIT_LOG_ACTOR, value);
        }
    }
}

为了存储事件,我们也要存储用户执行的动作,为了能获得这些,我们需要提供一个方法参数级别的元注释来设计参数,这个元注释称为AuditLogActor(retention=runtime, type=parameter) 。

现在剩余的是处理事件的代码,我们要在提交当前事务之前实现事件存储,如果事务失败,审计输入也会失败,这里使用AOP:

@Aspect
@Component
class AuditLogStoringAspect extends TransactionSynchronizationAdapter {
    @Autowired
    private ApplicationContext ctx; 
    @Before("execution(* *.*(..)) && @annotation(transactional)")
    public void registerTransactionSyncrhonization(JoinPoint jp, Transactional transactional) {
        Logger.log(this).debug("Registering audit log tx callback");
        TransactionSynchronizationManager.registerSynchronization(this);
        MethodSignature signature = (MethodSignature) jp.getSignature();
        int paramIdx = 0;
        for (Parameter param : signature.getMethod().getParameters()) {
            if (param.isAnnotationPresent(AuditLogActor.class)) {
                AuditLogServiceData.setActor((Long) jp.getArgs()[paramIdx]);
            }
            paramIdx ++;
        }
    }
    @Override
    public void beforeCommit(boolean readOnly) {
        Logger.log(this).debug("tx callback invoked. Readonly= " + readOnly);
        if (readOnly) {
            return;
        }
        for (Object event : AuditLogServiceData.getHibernateEvents()) {
           // handle events, possibly using instanceof
        }
    }

这里注入另外的服务,需要激活Auto-scanning或显式注册到XML或Java-config中。

调用这个审计如下代码:

@Transactional
public void saveFoo(FooRequest request, @AuditLogActor Long actorId) { .. }

总结:Hibernate的事件监听器存储所有插入 更新和删除事件作为Spring事务同步资源,一个aspect注册器是带有事务回调的Spring类,能够在事务提交之前被调用,当有操作事件发生时,响应的审计日志会新增一个记录。

 

EventSourcing

CQRS

Spring主题