001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.transaction.support; 018 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.Map; 022 023import org.springframework.beans.factory.ObjectFactory; 024import org.springframework.beans.factory.config.Scope; 025import org.springframework.lang.Nullable; 026 027/** 028 * A simple transaction-backed {@link Scope} implementation, delegating to 029 * {@link TransactionSynchronizationManager}'s resource binding mechanism. 030 * 031 * <p><b>NOTE:</b> Like {@link org.springframework.context.support.SimpleThreadScope}, 032 * this transaction scope is not registered by default in common contexts. Instead, 033 * you need to explicitly assign it to a scope key in your setup, either through 034 * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope} 035 * or through a {@link org.springframework.beans.factory.config.CustomScopeConfigurer} bean. 036 * 037 * @author Juergen Hoeller 038 * @since 4.2 039 * @see org.springframework.context.support.SimpleThreadScope 040 * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope 041 * @see org.springframework.beans.factory.config.CustomScopeConfigurer 042 */ 043public class SimpleTransactionScope implements Scope { 044 045 @Override 046 public Object get(String name, ObjectFactory<?> objectFactory) { 047 ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this); 048 if (scopedObjects == null) { 049 scopedObjects = new ScopedObjectsHolder(); 050 TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects)); 051 TransactionSynchronizationManager.bindResource(this, scopedObjects); 052 } 053 // NOTE: Do NOT modify the following to use Map::computeIfAbsent. For details, 054 // see https://github.com/spring-projects/spring-framework/issues/25801. 055 Object scopedObject = scopedObjects.scopedInstances.get(name); 056 if (scopedObject == null) { 057 scopedObject = objectFactory.getObject(); 058 scopedObjects.scopedInstances.put(name, scopedObject); 059 } 060 return scopedObject; 061 } 062 063 @Override 064 @Nullable 065 public Object remove(String name) { 066 ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this); 067 if (scopedObjects != null) { 068 scopedObjects.destructionCallbacks.remove(name); 069 return scopedObjects.scopedInstances.remove(name); 070 } 071 else { 072 return null; 073 } 074 } 075 076 @Override 077 public void registerDestructionCallback(String name, Runnable callback) { 078 ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this); 079 if (scopedObjects != null) { 080 scopedObjects.destructionCallbacks.put(name, callback); 081 } 082 } 083 084 @Override 085 @Nullable 086 public Object resolveContextualObject(String key) { 087 return null; 088 } 089 090 @Override 091 @Nullable 092 public String getConversationId() { 093 return TransactionSynchronizationManager.getCurrentTransactionName(); 094 } 095 096 097 /** 098 * Holder for scoped objects. 099 */ 100 static class ScopedObjectsHolder { 101 102 final Map<String, Object> scopedInstances = new HashMap<>(); 103 104 final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>(); 105 } 106 107 108 private class CleanupSynchronization extends TransactionSynchronizationAdapter { 109 110 private final ScopedObjectsHolder scopedObjects; 111 112 public CleanupSynchronization(ScopedObjectsHolder scopedObjects) { 113 this.scopedObjects = scopedObjects; 114 } 115 116 @Override 117 public void suspend() { 118 TransactionSynchronizationManager.unbindResource(SimpleTransactionScope.this); 119 } 120 121 @Override 122 public void resume() { 123 TransactionSynchronizationManager.bindResource(SimpleTransactionScope.this, this.scopedObjects); 124 } 125 126 @Override 127 public void afterCompletion(int status) { 128 TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this); 129 for (Runnable callback : this.scopedObjects.destructionCallbacks.values()) { 130 callback.run(); 131 } 132 this.scopedObjects.destructionCallbacks.clear(); 133 this.scopedObjects.scopedInstances.clear(); 134 } 135 } 136 137}