Browse Source

Introduced SimpleTransactionScope (analogous to SimpleThreadScope)

Issue: SPR-13085
pull/820/head
Juergen Hoeller 10 years ago
parent
commit
f1c7dc4f4b
  1. 13
      spring-context/src/main/java/org/springframework/context/support/SimpleThreadScope.java
  2. 111
      spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java
  3. 127
      spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java

13
spring-context/src/main/java/org/springframework/context/support/SimpleThreadScope.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@ -29,9 +29,14 @@ import org.springframework.core.NamedThreadLocal; @@ -29,9 +29,14 @@ import org.springframework.core.NamedThreadLocal;
/**
* A simple thread-backed {@link Scope} implementation.
*
* <p><strong>Note:</strong> {@code SimpleThreadScope} <em>does not clean up
* any objects</em> associated with it. As such, it is typically preferable to
* use {@link org.springframework.web.context.request.RequestScope RequestScope}
* <p><b>NOTE:</b> This thread scope is not registered by default in common contexts.
* Instead, you need to explicitly assign it to a scope key in your setup, either through
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope}
* or through a {@link org.springframework.beans.factory.config.CustomScopeConfigurer} bean.
*
* <p>{@code SimpleThreadScope} <em>does not clean up any objects</em> associated with it.
* As such, it is typically preferable to use
* {@link org.springframework.web.context.request.RequestScope RequestScope}
* in web environments.
*
* <p>For an implementation of a thread-based {@code Scope} with support for

111
spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java

@ -0,0 +1,111 @@ @@ -0,0 +1,111 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.support;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
/**
* A simple transaction-backed {@link Scope} implementation, delegating to
* {@link TransactionSynchronizationManager}'s resource binding mechanism.
*
* <p><b>NOTE:</b> Like {@link org.springframework.context.support.SimpleThreadScope},
* this transaction scope is not registered by default in common contexts. Instead,
* you need to explicitly assign it to a scope key in your setup, either through
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope}
* or through a {@link org.springframework.beans.factory.config.CustomScopeConfigurer} bean.
*
* @author Juergen Hoeller
* @since 4.2
* @see org.springframework.context.support.SimpleThreadScope
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope
* @see org.springframework.beans.factory.config.CustomScopeConfigurer
*/
public class SimpleTransactionScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
if (scopedObjects == null) {
scopedObjects = new ScopedObjectsHolder();
TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization());
TransactionSynchronizationManager.bindResource(this, scopedObjects);
}
Object scopedObject = scopedObjects.scopedInstances.get(name);
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
scopedObjects.scopedInstances.put(name, scopedObject);
}
return scopedObject;
}
@Override
public Object remove(String name) {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
if (scopedObjects != null) {
scopedObjects.destructionCallbacks.remove(name);
return scopedObjects.scopedInstances.remove(name);
}
else {
return null;
}
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
if (scopedObjects != null) {
scopedObjects.destructionCallbacks.put(name, callback);
}
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return TransactionSynchronizationManager.getCurrentTransactionName();
}
static class ScopedObjectsHolder {
final Map<String, Object> scopedInstances = new HashMap<String, Object>();
final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<String, Runnable>();
}
private class CleanupSynchronization extends TransactionSynchronizationAdapter {
@Override
public void afterCompletion(int status) {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder)
TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this);
for (Runnable callback : scopedObjects.destructionCallbacks.values()) {
callback.run();
}
}
}
}

127
spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.support;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
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 static org.junit.Assert.*;
/**
* @author Juergen Hoeller
*/
public class SimpleTransactionScopeTests {
@Test
public void getFromScope() 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();
try {
context.getBean(TestBean.class);
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
// expected - no synchronization active
assertTrue(ex.getCause() instanceof IllegalStateException);
}
try {
context.getBean(DerivedTestBean.class);
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
// expected - no synchronization active
assertTrue(ex.getCause() instanceof IllegalStateException);
}
TestBean bean1 = null;
DerivedTestBean bean2 = null;
DerivedTestBean bean2a = null;
DerivedTestBean bean2b = null;
TransactionSynchronizationManager.initSynchronization();
try {
bean1 = context.getBean(TestBean.class);
assertSame(bean1, context.getBean(TestBean.class));
bean2 = context.getBean(DerivedTestBean.class);
assertSame(bean2, context.getBean(DerivedTestBean.class));
context.getBeanFactory().destroyScopedBean("txScopedObject2");
assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2"));
assertTrue(bean2.wasDestroyed());
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());
bean2b = context.getBean(DerivedTestBean.class);
assertSame(bean2b, context.getBean(DerivedTestBean.class));
assertNotSame(bean2, bean2a);
assertNotSame(bean2, bean2b);
assertNotSame(bean2a, bean2b);
}
finally {
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED);
TransactionSynchronizationManager.clearSynchronization();
}
assertFalse(bean2a.wasDestroyed());
assertTrue(bean2b.wasDestroyed());
assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
try {
context.getBean(TestBean.class);
fail("Should have thrown IllegalStateException");
}
catch (BeanCreationException ex) {
// expected - no synchronization active
assertTrue(ex.getCause() instanceof IllegalStateException);
}
try {
context.getBean(DerivedTestBean.class);
fail("Should have thrown IllegalStateException");
}
catch (BeanCreationException ex) {
// expected - no synchronization active
assertTrue(ex.getCause() instanceof IllegalStateException);
}
}
}
Loading…
Cancel
Save