定制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类,能够在事务提交之前被调用,当有操作事件发生时,响应的审计日志会新增一个记录。

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

相关推荐