SimpleTransactionScope properly suspends and resumes scoped objects

Issue: SPR-14148
This commit is contained in:
Juergen Hoeller 2016-04-12 16:02:36 +02:00
parent 10554a85c9
commit 831f09cf48
2 changed files with 93 additions and 7 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -46,7 +46,7 @@ public class SimpleTransactionScope implements Scope {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
if (scopedObjects == null) {
scopedObjects = new ScopedObjectsHolder();
TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization());
TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
TransactionSynchronizationManager.bindResource(this, scopedObjects);
}
Object scopedObject = scopedObjects.scopedInstances.get(name);
@ -98,13 +98,30 @@ public class SimpleTransactionScope implements Scope {
private class CleanupSynchronization extends TransactionSynchronizationAdapter {
private final ScopedObjectsHolder scopedObjects;
public CleanupSynchronization(ScopedObjectsHolder scopedObjects) {
this.scopedObjects = scopedObjects;
}
@Override
public void suspend() {
TransactionSynchronizationManager.unbindResource(SimpleTransactionScope.this);
}
@Override
public void resume() {
TransactionSynchronizationManager.bindResource(SimpleTransactionScope.this, this.scopedObjects);
}
@Override
public void afterCompletion(int status) {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder)
TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this);
for (Runnable callback : scopedObjects.destructionCallbacks.values()) {
TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this);
for (Runnable callback : this.scopedObjects.destructionCallbacks.values()) {
callback.run();
}
this.scopedObjects.destructionCallbacks.clear();
this.scopedObjects.scopedInstances.clear();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,9 @@
package org.springframework.transaction.support;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
@ -23,6 +26,7 @@ import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.tests.sample.beans.DerivedTestBean;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.tests.transaction.CallCountingTransactionManager;
import static org.junit.Assert.*;
@ -93,7 +97,6 @@ public class SimpleTransactionScopeTests {
bean2b = context.getBean(DerivedTestBean.class);
assertSame(bean2b, context.getBean(DerivedTestBean.class));
assertNotSame(bean2, bean2a);
assertNotSame(bean2, bean2b);
assertNotSame(bean2a, bean2b);
}
@ -125,4 +128,70 @@ public class SimpleTransactionScopeTests {
}
}
@Test
public void getWithTransactionManager() throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
context.getBeanFactory().registerScope("tx", new SimpleTransactionScope());
GenericBeanDefinition bd1 = new GenericBeanDefinition();
bd1.setBeanClass(TestBean.class);
bd1.setScope("tx");
bd1.setPrimary(true);
context.registerBeanDefinition("txScopedObject1", bd1);
GenericBeanDefinition bd2 = new GenericBeanDefinition();
bd2.setBeanClass(DerivedTestBean.class);
bd2.setScope("tx");
context.registerBeanDefinition("txScopedObject2", bd2);
context.refresh();
CallCountingTransactionManager tm = new CallCountingTransactionManager();
TransactionTemplate tt = new TransactionTemplate(tm);
Set<DerivedTestBean> finallyDestroy = new HashSet<DerivedTestBean>();
tt.execute(status -> {
TestBean bean1 = context.getBean(TestBean.class);
assertSame(bean1, context.getBean(TestBean.class));
DerivedTestBean bean2 = context.getBean(DerivedTestBean.class);
assertSame(bean2, context.getBean(DerivedTestBean.class));
context.getBeanFactory().destroyScopedBean("txScopedObject2");
assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2"));
assertTrue(bean2.wasDestroyed());
DerivedTestBean bean2a = context.getBean(DerivedTestBean.class);
assertSame(bean2a, context.getBean(DerivedTestBean.class));
assertNotSame(bean2, bean2a);
context.getBeanFactory().getRegisteredScope("tx").remove("txScopedObject2");
assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2"));
assertFalse(bean2a.wasDestroyed());
DerivedTestBean bean2b = context.getBean(DerivedTestBean.class);
finallyDestroy.add(bean2b);
assertSame(bean2b, context.getBean(DerivedTestBean.class));
assertNotSame(bean2, bean2b);
assertNotSame(bean2a, bean2b);
Set<DerivedTestBean> immediatelyDestroy = new HashSet<DerivedTestBean>();
TransactionTemplate tt2 = new TransactionTemplate(tm);
tt2.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
tt2.execute(status2 -> {
DerivedTestBean bean2c = context.getBean(DerivedTestBean.class);
immediatelyDestroy.add(bean2c);
assertSame(bean2c, context.getBean(DerivedTestBean.class));
assertNotSame(bean2, bean2c);
assertNotSame(bean2a, bean2c);
assertNotSame(bean2b, bean2c);
return null;
});
assertTrue(immediatelyDestroy.iterator().next().wasDestroyed());
assertFalse(bean2b.wasDestroyed());
return null;
});
assertTrue(finallyDestroy.iterator().next().wasDestroyed());
}
}