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