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>Calls {@link #createContext()} to create a {@link GenericApplicationContext} 072 * instance.</li> 073 * <li>If the supplied {@code MergedContextConfiguration} references a 074 * {@linkplain MergedContextConfiguration#getParent() parent configuration}, 075 * the corresponding {@link MergedContextConfiguration#getParentApplicationContext() 076 * ApplicationContext} will be retrieved and 077 * {@linkplain GenericApplicationContext#setParent(ApplicationContext) set as the parent} 078 * for the context created by this method.</li> 079 * <li>Calls {@link #prepareContext(GenericApplicationContext)} for backwards 080 * compatibility with the {@link org.springframework.test.context.ContextLoader 081 * ContextLoader} SPI.</li> 082 * <li>Calls {@link #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)} 083 * to allow for customizing the context before bean definitions are loaded.</li> 084 * <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the 085 * context's {@code DefaultListableBeanFactory}.</li> 086 * <li>Delegates to {@link #loadBeanDefinitions(GenericApplicationContext, MergedContextConfiguration)} 087 * to populate the context from the locations or classes in the supplied 088 * {@code MergedContextConfiguration}.</li> 089 * <li>Delegates to {@link AnnotationConfigUtils} for 090 * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering} 091 * annotation configuration processors.</li> 092 * <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context 093 * before it is refreshed.</li> 094 * <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to 095 * allow for customizing the context before it is refreshed.</li> 096 * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the 097 * context and registers a JVM shutdown hook for it.</li> 098 * </ul> 099 * @return a new application context 100 * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration) 101 * @see GenericApplicationContext 102 * @since 3.1 103 */ 104 @Override 105 public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { 106 if (logger.isDebugEnabled()) { 107 logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].", 108 mergedConfig)); 109 } 110 111 validateMergedContextConfiguration(mergedConfig); 112 113 GenericApplicationContext context = createContext(); 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>Calls {@link #createContext()} to create a {@link GenericApplicationContext} 152 * instance.</li> 153 * <li>Calls {@link #prepareContext(GenericApplicationContext)} to allow for customizing the context 154 * before bean definitions are loaded.</li> 155 * <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the 156 * context's {@code DefaultListableBeanFactory}.</li> 157 * <li>Delegates to {@link #createBeanDefinitionReader(GenericApplicationContext)} to create a 158 * {@link BeanDefinitionReader} which is then used to populate the context 159 * from the specified locations.</li> 160 * <li>Delegates to {@link AnnotationConfigUtils} for 161 * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering} 162 * annotation configuration processors.</li> 163 * <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context 164 * before it is refreshed.</li> 165 * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the 166 * context and registers a JVM shutdown hook for it.</li> 167 * </ul> 168 * <p><b>Note</b>: this method does not provide a means to set active bean definition 169 * profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration)} 170 * and {@link AbstractContextLoader#prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)} 171 * for an alternative. 172 * @return a new application context 173 * @see org.springframework.test.context.ContextLoader#loadContext 174 * @see GenericApplicationContext 175 * @see #loadContext(MergedContextConfiguration) 176 * @since 2.5 177 */ 178 @Override 179 public final ConfigurableApplicationContext loadContext(String... locations) throws Exception { 180 if (logger.isDebugEnabled()) { 181 logger.debug(String.format("Loading ApplicationContext for locations [%s].", 182 StringUtils.arrayToCommaDelimitedString(locations))); 183 } 184 185 GenericApplicationContext context = createContext(); 186 187 prepareContext(context); 188 customizeBeanFactory(context.getDefaultListableBeanFactory()); 189 createBeanDefinitionReader(context).loadBeanDefinitions(locations); 190 AnnotationConfigUtils.registerAnnotationConfigProcessors(context); 191 customizeContext(context); 192 193 context.refresh(); 194 context.registerShutdownHook(); 195 196 return context; 197 } 198 199 /** 200 * Factory method for creating the {@link GenericApplicationContext} used by 201 * this {@code ContextLoader}. 202 * <p>The default implementation creates a {@code GenericApplicationContext} 203 * using the default constructor. This method may get overridden e.g. to use 204 * a custom context subclass or to create a {@code GenericApplicationContext} 205 * with a custom {@link DefaultListableBeanFactory} implementation. 206 * @return a newly instantiated {@code GenericApplicationContext} 207 * @since 5.2.9 208 */ 209 protected GenericApplicationContext createContext() { 210 return new GenericApplicationContext(); 211 } 212 213 /** 214 * Prepare the {@link GenericApplicationContext} created by this {@code ContextLoader}. 215 * Called <i>before</i> bean definitions are read. 216 * <p>The default implementation is empty. Can be overridden in subclasses to 217 * customize {@code GenericApplicationContext}'s standard settings. 218 * @param context the context that should be prepared 219 * @since 2.5 220 * @see #loadContext(MergedContextConfiguration) 221 * @see #loadContext(String...) 222 * @see GenericApplicationContext#setAllowBeanDefinitionOverriding 223 * @see GenericApplicationContext#setResourceLoader 224 * @see GenericApplicationContext#setId 225 * @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration) 226 */ 227 protected void prepareContext(GenericApplicationContext context) { 228 } 229 230 /** 231 * Customize the internal bean factory of the ApplicationContext created by 232 * this {@code ContextLoader}. 233 * <p>The default implementation is empty but can be overridden in subclasses 234 * to customize {@code DefaultListableBeanFactory}'s standard settings. 235 * @param beanFactory the bean factory created by this {@code ContextLoader} 236 * @since 2.5 237 * @see #loadContext(MergedContextConfiguration) 238 * @see #loadContext(String...) 239 * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding 240 * @see DefaultListableBeanFactory#setAllowEagerClassLoading 241 * @see DefaultListableBeanFactory#setAllowCircularReferences 242 * @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping 243 */ 244 protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { 245 } 246 247 /** 248 * Load bean definitions into the supplied {@link GenericApplicationContext context} 249 * from the locations or classes in the supplied {@code MergedContextConfiguration}. 250 * <p>The default implementation delegates to the {@link BeanDefinitionReader} 251 * returned by {@link #createBeanDefinitionReader(GenericApplicationContext)} to 252 * {@link BeanDefinitionReader#loadBeanDefinitions(String) load} the 253 * bean definitions. 254 * <p>Subclasses must provide an appropriate implementation of 255 * {@link #createBeanDefinitionReader(GenericApplicationContext)}. Alternatively subclasses 256 * may provide a <em>no-op</em> implementation of {@code createBeanDefinitionReader()} 257 * and override this method to provide a custom strategy for loading or 258 * registering bean definitions. 259 * @param context the context into which the bean definitions should be loaded 260 * @param mergedConfig the merged context configuration 261 * @since 3.1 262 * @see #loadContext(MergedContextConfiguration) 263 */ 264 protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) { 265 createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations()); 266 } 267 268 /** 269 * Factory method for creating a new {@link BeanDefinitionReader} for loading 270 * bean definitions into the supplied {@link GenericApplicationContext context}. 271 * @param context the context for which the {@code BeanDefinitionReader} 272 * should be created 273 * @return a {@code BeanDefinitionReader} for the supplied context 274 * @since 2.5 275 * @see #loadContext(String...) 276 * @see #loadBeanDefinitions 277 * @see BeanDefinitionReader 278 */ 279 protected abstract BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context); 280 281 /** 282 * Customize the {@link GenericApplicationContext} created by this 283 * {@code ContextLoader} <i>after</i> bean definitions have been 284 * loaded into the context but <i>before</i> the context is refreshed. 285 * <p>The default implementation is empty but can be overridden in subclasses 286 * to customize the application context. 287 * @param context the newly created application context 288 * @since 2.5 289 * @see #loadContext(MergedContextConfiguration) 290 * @see #loadContext(String...) 291 * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) 292 */ 293 protected void customizeContext(GenericApplicationContext context) { 294 } 295 296}