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.test.context.support; 018 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021 022import org.springframework.beans.factory.support.BeanDefinitionReader; 023import org.springframework.beans.factory.support.DefaultListableBeanFactory; 024import org.springframework.context.ApplicationContext; 025import org.springframework.context.ConfigurableApplicationContext; 026import org.springframework.context.annotation.AnnotationConfigUtils; 027import org.springframework.context.support.GenericApplicationContext; 028import org.springframework.test.context.MergedContextConfiguration; 029import org.springframework.util.StringUtils; 030 031/** 032 * Abstract, generic extension of {@link AbstractContextLoader} that loads a 033 * {@link GenericApplicationContext}. 034 * 035 * <ul> 036 * <li>If instances of concrete subclasses are invoked via the 037 * {@link org.springframework.test.context.ContextLoader ContextLoader} SPI, the 038 * context will be loaded from the <em>locations</em> provided to 039 * {@link #loadContext(String...)}.</li> 040 * <li>If instances of concrete subclasses are invoked via the 041 * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader} 042 * SPI, the context will be loaded from the {@link MergedContextConfiguration} 043 * provided to {@link #loadContext(MergedContextConfiguration)}. In such cases, a 044 * {@code SmartContextLoader} will decide whether to load the context from 045 * <em>locations</em> or <em>annotated classes</em>.</li> 046 * </ul> 047 * 048 * <p>Concrete subclasses must provide an appropriate implementation of 049 * {@link #createBeanDefinitionReader createBeanDefinitionReader()}, 050 * potentially overriding {@link #loadBeanDefinitions loadBeanDefinitions()} 051 * as well. 052 * 053 * @author Sam Brannen 054 * @author Juergen Hoeller 055 * @author Phillip Webb 056 * @since 2.5 057 * @see #loadContext(MergedContextConfiguration) 058 * @see #loadContext(String...) 059 */ 060public abstract class AbstractGenericContextLoader extends AbstractContextLoader { 061 062 protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class); 063 064 065 /** 066 * Load a Spring ApplicationContext from the supplied {@link MergedContextConfiguration}. 067 * <p>Implementation details: 068 * <ul> 069 * <li>Calls {@link #validateMergedContextConfiguration(MergedContextConfiguration)} 070 * to allow subclasses to validate the supplied configuration before proceeding.</li> 071 * <li>Creates a {@link GenericApplicationContext} instance.</li> 072 * <li>If the supplied {@code MergedContextConfiguration} references a 073 * {@linkplain MergedContextConfiguration#getParent() parent configuration}, 074 * the corresponding {@link MergedContextConfiguration#getParentApplicationContext() 075 * ApplicationContext} will be retrieved and 076 * {@linkplain GenericApplicationContext#setParent(ApplicationContext) set as the parent} 077 * for the context created by this method.</li> 078 * <li>Calls {@link #prepareContext(GenericApplicationContext)} for backwards 079 * compatibility with the {@link org.springframework.test.context.ContextLoader 080 * ContextLoader} SPI.</li> 081 * <li>Calls {@link #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)} 082 * to allow for customizing the context before bean definitions are loaded.</li> 083 * <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the 084 * context's {@code DefaultListableBeanFactory}.</li> 085 * <li>Delegates to {@link #loadBeanDefinitions(GenericApplicationContext, MergedContextConfiguration)} 086 * to populate the context from the locations or classes in the supplied 087 * {@code MergedContextConfiguration}.</li> 088 * <li>Delegates to {@link AnnotationConfigUtils} for 089 * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering} 090 * annotation configuration processors.</li> 091 * <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context 092 * before it is refreshed.</li> 093 * <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to 094 * allow for customizing the context before it is refreshed.</li> 095 * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the 096 * context and registers a JVM shutdown hook for it.</li> 097 * </ul> 098 * @return a new application context 099 * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration) 100 * @see GenericApplicationContext 101 * @since 3.1 102 */ 103 @Override 104 public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { 105 if (logger.isDebugEnabled()) { 106 logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].", 107 mergedConfig)); 108 } 109 110 validateMergedContextConfiguration(mergedConfig); 111 112 GenericApplicationContext context = new GenericApplicationContext(); 113 114 ApplicationContext parent = mergedConfig.getParentApplicationContext(); 115 if (parent != null) { 116 context.setParent(parent); 117 } 118 119 prepareContext(context); 120 prepareContext(context, mergedConfig); 121 customizeBeanFactory(context.getDefaultListableBeanFactory()); 122 loadBeanDefinitions(context, mergedConfig); 123 AnnotationConfigUtils.registerAnnotationConfigProcessors(context); 124 customizeContext(context); 125 customizeContext(context, mergedConfig); 126 127 context.refresh(); 128 context.registerShutdownHook(); 129 130 return context; 131 } 132 133 /** 134 * Validate the supplied {@link MergedContextConfiguration} with respect to 135 * what this context loader supports. 136 * <p>The default implementation is a <em>no-op</em> but can be overridden by 137 * subclasses as appropriate. 138 * @param mergedConfig the merged configuration to validate 139 * @throws IllegalStateException if the supplied configuration is not valid 140 * for this context loader 141 * @since 4.0.4 142 */ 143 protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) { 144 /* no-op */ 145 } 146 147 /** 148 * Load a Spring ApplicationContext from the supplied {@code locations}. 149 * <p>Implementation details: 150 * <ul> 151 * <li>Creates a {@link GenericApplicationContext} instance.</li> 152 * <li>Calls {@link #prepareContext(GenericApplicationContext)} to allow for customizing the context 153 * before bean definitions are loaded.</li> 154 * <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the 155 * context's {@code DefaultListableBeanFactory}.</li> 156 * <li>Delegates to {@link #createBeanDefinitionReader(GenericApplicationContext)} to create a 157 * {@link BeanDefinitionReader} which is then used to populate the context 158 * from the specified locations.</li> 159 * <li>Delegates to {@link AnnotationConfigUtils} for 160 * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering} 161 * annotation configuration processors.</li> 162 * <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context 163 * before it is refreshed.</li> 164 * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the 165 * context and registers a JVM shutdown hook for it.</li> 166 * </ul> 167 * <p><b>Note</b>: this method does not provide a means to set active bean definition 168 * profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration)} 169 * and {@link AbstractContextLoader#prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)} 170 * for an alternative. 171 * @return a new application context 172 * @see org.springframework.test.context.ContextLoader#loadContext 173 * @see GenericApplicationContext 174 * @see #loadContext(MergedContextConfiguration) 175 * @since 2.5 176 */ 177 @Override 178 public final ConfigurableApplicationContext loadContext(String... locations) throws Exception { 179 if (logger.isDebugEnabled()) { 180 logger.debug(String.format("Loading ApplicationContext for locations [%s].", 181 StringUtils.arrayToCommaDelimitedString(locations))); 182 } 183 184 GenericApplicationContext context = new GenericApplicationContext(); 185 186 prepareContext(context); 187 customizeBeanFactory(context.getDefaultListableBeanFactory()); 188 createBeanDefinitionReader(context).loadBeanDefinitions(locations); 189 AnnotationConfigUtils.registerAnnotationConfigProcessors(context); 190 customizeContext(context); 191 192 context.refresh(); 193 context.registerShutdownHook(); 194 195 return context; 196 } 197 198 /** 199 * Prepare the {@link GenericApplicationContext} created by this {@code ContextLoader}. 200 * Called <i>before</i> bean definitions are read. 201 * <p>The default implementation is empty. Can be overridden in subclasses to 202 * customize {@code GenericApplicationContext}'s standard settings. 203 * @param context the context that should be prepared 204 * @see #loadContext(MergedContextConfiguration) 205 * @see #loadContext(String...) 206 * @see GenericApplicationContext#setAllowBeanDefinitionOverriding 207 * @see GenericApplicationContext#setResourceLoader 208 * @see GenericApplicationContext#setId 209 * @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration) 210 * @since 2.5 211 */ 212 protected void prepareContext(GenericApplicationContext context) { 213 } 214 215 /** 216 * Customize the internal bean factory of the ApplicationContext created by 217 * this {@code ContextLoader}. 218 * <p>The default implementation is empty but can be overridden in subclasses 219 * to customize {@code DefaultListableBeanFactory}'s standard settings. 220 * @param beanFactory the bean factory created by this {@code ContextLoader} 221 * @see #loadContext(MergedContextConfiguration) 222 * @see #loadContext(String...) 223 * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding 224 * @see DefaultListableBeanFactory#setAllowEagerClassLoading 225 * @see DefaultListableBeanFactory#setAllowCircularReferences 226 * @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping 227 * @since 2.5 228 */ 229 protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { 230 } 231 232 /** 233 * Load bean definitions into the supplied {@link GenericApplicationContext context} 234 * from the locations or classes in the supplied {@code MergedContextConfiguration}. 235 * <p>The default implementation delegates to the {@link BeanDefinitionReader} 236 * returned by {@link #createBeanDefinitionReader(GenericApplicationContext)} to 237 * {@link BeanDefinitionReader#loadBeanDefinitions(String) load} the 238 * bean definitions. 239 * <p>Subclasses must provide an appropriate implementation of 240 * {@link #createBeanDefinitionReader(GenericApplicationContext)}. Alternatively subclasses 241 * may provide a <em>no-op</em> implementation of {@code createBeanDefinitionReader()} 242 * and override this method to provide a custom strategy for loading or 243 * registering bean definitions. 244 * @param context the context into which the bean definitions should be loaded 245 * @param mergedConfig the merged context configuration 246 * @see #loadContext(MergedContextConfiguration) 247 * @since 3.1 248 */ 249 protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) { 250 createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations()); 251 } 252 253 /** 254 * Factory method for creating a new {@link BeanDefinitionReader} for loading 255 * bean definitions into the supplied {@link GenericApplicationContext context}. 256 * @param context the context for which the {@code BeanDefinitionReader} 257 * should be created 258 * @return a {@code BeanDefinitionReader} for the supplied context 259 * @see #loadContext(String...) 260 * @see #loadBeanDefinitions 261 * @see BeanDefinitionReader 262 * @since 2.5 263 */ 264 protected abstract BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context); 265 266 /** 267 * Customize the {@link GenericApplicationContext} created by this 268 * {@code ContextLoader} <i>after</i> bean definitions have been 269 * loaded into the context but <i>before</i> the context is refreshed. 270 * <p>The default implementation is empty but can be overridden in subclasses 271 * to customize the application context. 272 * @param context the newly created application context 273 * @see #loadContext(MergedContextConfiguration) 274 * @see #loadContext(String...) 275 * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) 276 * @since 2.5 277 */ 278 protected void customizeContext(GenericApplicationContext context) { 279 } 280 281}