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.orm.hibernate5; 018 019import java.util.Map; 020import java.util.function.Consumer; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.hibernate.resource.beans.container.spi.BeanContainer; 025import org.hibernate.resource.beans.container.spi.ContainedBean; 026import org.hibernate.resource.beans.spi.BeanInstanceProducer; 027 028import org.springframework.beans.BeansException; 029import org.springframework.beans.factory.BeanCreationException; 030import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 031import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034import org.springframework.util.ConcurrentReferenceHashMap; 035 036/** 037 * Spring's implementation of Hibernate 5.3's {@link BeanContainer} SPI, 038 * delegating to a Spring {@link ConfigurableListableBeanFactory}. 039 * 040 * <p>Auto-configured by {@link LocalSessionFactoryBean#setBeanFactory}, 041 * programmatically supported via {@link LocalSessionFactoryBuilder#setBeanContainer}, 042 * and manually configurable through a "hibernate.resource.beans.container" entry 043 * in JPA properties, e.g.: 044 * 045 * <pre class="code"> 046 * <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 047 * ... 048 * <property name="jpaPropertyMap"> 049 * <map> 050 * <entry key="hibernate.resource.beans.container"> 051 * <bean class="org.springframework.orm.hibernate5.SpringBeanContainer"/> 052 * </entry> 053 * </map> 054 * </property> 055 * </bean></pre> 056 * 057 * Or in Java-based JPA configuration: 058 * 059 * <pre class="code"> 060 * LocalContainerEntityManagerFactoryBean emfb = ... 061 * emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)); 062 * </pre> 063 * 064 * Please note that Spring's {@link LocalSessionFactoryBean} is an immediate alternative 065 * to {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean} for common 066 * JPA purposes: In particular with Hibernate 5.3/5.4, the Hibernate {@code SessionFactory} 067 * will natively expose the JPA {@code EntityManagerFactory} interface as well, and 068 * Hibernate {@code BeanContainer} integration will be registered out of the box. 069 * 070 * @author Juergen Hoeller 071 * @since 5.1 072 * @see LocalSessionFactoryBean#setBeanFactory 073 * @see LocalSessionFactoryBuilder#setBeanContainer 074 * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setJpaPropertyMap 075 * @see org.hibernate.cfg.AvailableSettings#BEAN_CONTAINER 076 */ 077public final class SpringBeanContainer implements BeanContainer { 078 079 private static final Log logger = LogFactory.getLog(SpringBeanContainer.class); 080 081 private final ConfigurableListableBeanFactory beanFactory; 082 083 private final Map<Object, SpringContainedBean<?>> beanCache = new ConcurrentReferenceHashMap<>(); 084 085 086 /** 087 * Instantiate a new SpringBeanContainer for the given bean factory. 088 * @param beanFactory the Spring bean factory to delegate to 089 */ 090 public SpringBeanContainer(ConfigurableListableBeanFactory beanFactory) { 091 Assert.notNull(beanFactory, "ConfigurableListableBeanFactory is required"); 092 this.beanFactory = beanFactory; 093 } 094 095 096 @Override 097 @SuppressWarnings("unchecked") 098 public <B> ContainedBean<B> getBean( 099 Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) { 100 101 SpringContainedBean<?> bean; 102 if (lifecycleOptions.canUseCachedReferences()) { 103 bean = this.beanCache.get(beanType); 104 if (bean == null) { 105 bean = createBean(beanType, lifecycleOptions, fallbackProducer); 106 this.beanCache.put(beanType, bean); 107 } 108 } 109 else { 110 bean = createBean(beanType, lifecycleOptions, fallbackProducer); 111 } 112 return (SpringContainedBean<B>) bean; 113 } 114 115 @Override 116 @SuppressWarnings("unchecked") 117 public <B> ContainedBean<B> getBean( 118 String name, Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) { 119 120 SpringContainedBean<?> bean; 121 if (lifecycleOptions.canUseCachedReferences()) { 122 bean = this.beanCache.get(name); 123 if (bean == null) { 124 bean = createBean(name, beanType, lifecycleOptions, fallbackProducer); 125 this.beanCache.put(name, bean); 126 } 127 } 128 else { 129 bean = createBean(name, beanType, lifecycleOptions, fallbackProducer); 130 } 131 return (SpringContainedBean<B>) bean; 132 } 133 134 @Override 135 public void stop() { 136 this.beanCache.values().forEach(SpringContainedBean::destroyIfNecessary); 137 this.beanCache.clear(); 138 } 139 140 141 private SpringContainedBean<?> createBean( 142 Class<?> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) { 143 144 try { 145 if (lifecycleOptions.useJpaCompliantCreation()) { 146 return new SpringContainedBean<>( 147 this.beanFactory.createBean(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false), 148 this.beanFactory::destroyBean); 149 } 150 else { 151 return new SpringContainedBean<>(this.beanFactory.getBean(beanType)); 152 } 153 } 154 catch (BeansException ex) { 155 if (logger.isDebugEnabled()) { 156 logger.debug("Falling back to Hibernate's default producer after bean creation failure for " + 157 beanType + ": " + ex); 158 } 159 try { 160 return new SpringContainedBean<>(fallbackProducer.produceBeanInstance(beanType)); 161 } 162 catch (RuntimeException ex2) { 163 if (ex instanceof BeanCreationException) { 164 if (logger.isDebugEnabled()) { 165 logger.debug("Fallback producer failed for " + beanType + ": " + ex2); 166 } 167 // Rethrow original Spring exception from first attempt. 168 throw ex; 169 } 170 else { 171 // Throw fallback producer exception since original was probably NoSuchBeanDefinitionException. 172 throw ex2; 173 } 174 } 175 } 176 } 177 178 private SpringContainedBean<?> createBean( 179 String name, Class<?> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) { 180 181 try { 182 if (lifecycleOptions.useJpaCompliantCreation()) { 183 Object bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); 184 this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false); 185 this.beanFactory.applyBeanPropertyValues(bean, name); 186 bean = this.beanFactory.initializeBean(bean, name); 187 return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance)); 188 } 189 else { 190 return new SpringContainedBean<>(this.beanFactory.getBean(name, beanType)); 191 } 192 } 193 catch (BeansException ex) { 194 if (logger.isDebugEnabled()) { 195 logger.debug("Falling back to Hibernate's default producer after bean creation failure for " + 196 beanType + " with name '" + name + "': " + ex); 197 } 198 try { 199 return new SpringContainedBean<>(fallbackProducer.produceBeanInstance(name, beanType)); 200 } 201 catch (RuntimeException ex2) { 202 if (ex instanceof BeanCreationException) { 203 if (logger.isDebugEnabled()) { 204 logger.debug("Fallback producer failed for " + beanType + " with name '" + name + "': " + ex2); 205 } 206 // Rethrow original Spring exception from first attempt. 207 throw ex; 208 } 209 else { 210 // Throw fallback producer exception since original was probably NoSuchBeanDefinitionException. 211 throw ex2; 212 } 213 } 214 } 215 } 216 217 218 private static final class SpringContainedBean<B> implements ContainedBean<B> { 219 220 private final B beanInstance; 221 222 @Nullable 223 private Consumer<B> destructionCallback; 224 225 public SpringContainedBean(B beanInstance) { 226 this.beanInstance = beanInstance; 227 } 228 229 public SpringContainedBean(B beanInstance, Consumer<B> destructionCallback) { 230 this.beanInstance = beanInstance; 231 this.destructionCallback = destructionCallback; 232 } 233 234 @Override 235 public B getBeanInstance() { 236 return this.beanInstance; 237 } 238 239 public void destroyIfNecessary() { 240 if (this.destructionCallback != null) { 241 this.destructionCallback.accept(this.beanInstance); 242 } 243 } 244 } 245 246}