View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.context.annotation;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.util.Arrays;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import org.springframework.aop.scope.ScopedProxyFactoryBean;
27  import org.springframework.asm.Type;
28  import org.springframework.beans.factory.BeanFactory;
29  import org.springframework.beans.factory.BeanFactoryAware;
30  import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
31  import org.springframework.beans.factory.config.ConfigurableBeanFactory;
32  import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
33  import org.springframework.cglib.core.ClassGenerator;
34  import org.springframework.cglib.core.Constants;
35  import org.springframework.cglib.core.DefaultGeneratorStrategy;
36  import org.springframework.cglib.core.SpringNamingPolicy;
37  import org.springframework.cglib.proxy.Callback;
38  import org.springframework.cglib.proxy.CallbackFilter;
39  import org.springframework.cglib.proxy.Enhancer;
40  import org.springframework.cglib.proxy.MethodInterceptor;
41  import org.springframework.cglib.proxy.MethodProxy;
42  import org.springframework.cglib.proxy.NoOp;
43  import org.springframework.cglib.transform.ClassEmitterTransformer;
44  import org.springframework.cglib.transform.TransformingClassGenerator;
45  import org.springframework.core.annotation.AnnotationUtils;
46  import org.springframework.util.Assert;
47  import org.springframework.util.ObjectUtils;
48  import org.springframework.util.ReflectionUtils;
49  
50  /**
51   * Enhances {@link Configuration} classes by generating a CGLIB subclass which
52   * interacts with the Spring container to respect bean scoping semantics for
53   * {@code @Bean} methods. Each such {@code @Bean} method will be overridden in
54   * the generated subclass, only delegating to the actual {@code @Bean} method
55   * implementation if the container actually requests the construction of a new
56   * instance. Otherwise, a call to such an {@code @Bean} method serves as a
57   * reference back to the container, obtaining the corresponding bean by name.
58   *
59   * @author Chris Beams
60   * @author Juergen Hoeller
61   * @since 3.0
62   * @see #enhance
63   * @see ConfigurationClassPostProcessor
64   */
65  class ConfigurationClassEnhancer {
66  
67  	// The callbacks to use. Note that these callbacks must be stateless.
68  	private static final Callback[] CALLBACKS = new Callback[] {
69  			new BeanMethodInterceptor(),
70  			new BeanFactoryAwareMethodInterceptor(),
71  			NoOp.INSTANCE
72  	};
73  
74  	private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
75  
76  	private static final DefaultGeneratorStrategy GENERATOR_STRATEGY = new BeanFactoryAwareGeneratorStrategy();
77  
78  	private static final String BEAN_FACTORY_FIELD = "$$beanFactory";
79  
80  	private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
81  
82  
83  	/**
84  	 * Loads the specified class and generates a CGLIB subclass of it equipped with
85  	 * container-aware callbacks capable of respecting scoping and other bean semantics.
86  	 * @return the enhanced subclass
87  	 */
88  	public Class<?> enhance(Class<?> configClass) {
89  		if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
90  			if (logger.isDebugEnabled()) {
91  				logger.debug(String.format("Ignoring request to enhance %s as it has " +
92  						"already been enhanced. This usually indicates that more than one " +
93  						"ConfigurationClassPostProcessor has been registered (e.g. via " +
94  						"<context:annotation-config>). This is harmless, but you may " +
95  						"want check your configuration and remove one CCPP if possible",
96  						configClass.getName()));
97  			}
98  			return configClass;
99  		}
100 		Class<?> enhancedClass = createClass(newEnhancer(configClass));
101 		if (logger.isDebugEnabled()) {
102 			logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
103 					configClass.getName(), enhancedClass.getName()));
104 		}
105 		return enhancedClass;
106 	}
107 
108 	/**
109 	 * Creates a new CGLIB {@link Enhancer} instance.
110 	 */
111 	private Enhancer newEnhancer(Class<?> superclass) {
112 		Enhancer enhancer = new Enhancer();
113 		enhancer.setSuperclass(superclass);
114 		enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
115 		enhancer.setUseFactory(false);
116 		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
117 		enhancer.setStrategy(GENERATOR_STRATEGY);
118 		enhancer.setCallbackFilter(CALLBACK_FILTER);
119 		enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
120 		return enhancer;
121 	}
122 
123 	/**
124 	 * Uses enhancer to generate a subclass of superclass,
125 	 * ensuring that callbacks are registered for the new subclass.
126 	 */
127 	private Class<?> createClass(Enhancer enhancer) {
128 		Class<?> subclass = enhancer.createClass();
129 		// Registering callbacks statically (as opposed to thread-local)
130 		// is critical for usage in an OSGi environment (SPR-5932)...
131 		Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
132 		return subclass;
133 	}
134 
135 
136 	/**
137 	 * Marker interface to be implemented by all @Configuration CGLIB subclasses.
138 	 * Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance(Class)}
139 	 * through checking to see if candidate classes are already assignable to it, e.g.
140 	 * have already been enhanced.
141 	 * <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}
142 	 * classes require access to the {@link BeanFactory} that created them.
143 	 * <p>Note that this interface is intended for framework-internal use only, however
144 	 * must remain public in order to allow access to subclasses generated from other
145 	 * packages (i.e. user code).
146 	 */
147 	public interface EnhancedConfiguration extends BeanFactoryAware {
148 	}
149 
150 
151 	/**
152 	 * Conditional {@link Callback}.
153 	 * @see ConditionalCallbackFilter
154 	 */
155 	private static interface ConditionalCallback extends Callback {
156 
157 		boolean isMatch(Method candidateMethod);
158 	}
159 
160 
161 	/**
162 	 * A {@link CallbackFilter} that works by interrogating {@link Callback}s in the order
163 	 * that they are defined via {@link ConditionalCallback}.
164 	 */
165 	private static class ConditionalCallbackFilter implements CallbackFilter {
166 
167 		private final Callback[] callbacks;
168 
169 		private final Class<?>[] callbackTypes;
170 
171 		public ConditionalCallbackFilter(Callback[] callbacks) {
172 			this.callbacks = callbacks;
173 			this.callbackTypes = new Class<?>[callbacks.length];
174 			for (int i = 0; i < callbacks.length; i++) {
175 				this.callbackTypes[i] = callbacks[i].getClass();
176 			}
177 		}
178 
179 		@Override
180 		public int accept(Method method) {
181 			for (int i = 0; i < this.callbacks.length; i++) {
182 				if (!(this.callbacks[i] instanceof ConditionalCallback) ||
183 						((ConditionalCallback) this.callbacks[i]).isMatch(method)) {
184 					return i;
185 				}
186 			}
187 			throw new IllegalStateException("No callback available for method " + method.getName());
188 		}
189 
190 		public Class<?>[] getCallbackTypes() {
191 			return this.callbackTypes;
192 		}
193 	}
194 
195 
196 	/**
197 	 * Custom extension of CGLIB's DefaultGeneratorStrategy, introducing a {@link BeanFactory} field.
198 	 */
199 	private static class BeanFactoryAwareGeneratorStrategy extends DefaultGeneratorStrategy {
200 
201 		@Override
202 		protected ClassGenerator transform(ClassGenerator cg) throws Exception {
203 			ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
204 				@Override
205 				public void end_class() {
206 					declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
207 					super.end_class();
208 				}
209 			};
210 			return new TransformingClassGenerator(cg, transformer);
211 		}
212 	}
213 
214 
215 	/**
216 	 * Intercepts the invocation of any {@link BeanFactoryAware#setBeanFactory(BeanFactory)} on
217 	 * {@code @Configuration} class instances for the purpose of recording the {@link BeanFactory}.
218 	 * @see EnhancedConfiguration
219 	 */
220 	private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
221 
222 		@Override
223 		public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
224 			Field field = obj.getClass().getDeclaredField(BEAN_FACTORY_FIELD);
225 			Assert.state(field != null, "Unable to find generated BeanFactory field");
226 			field.set(obj, args[0]);
227 
228 			// Does the actual (non-CGLIB) superclass actually implement BeanFactoryAware?
229 			// If so, call its setBeanFactory() method. If not, just exit.
230 			if (BeanFactoryAware.class.isAssignableFrom(obj.getClass().getSuperclass())) {
231 				return proxy.invokeSuper(obj, args);
232 			}
233 			return null;
234 		}
235 
236 		@Override
237 		public boolean isMatch(Method candidateMethod) {
238 			return (candidateMethod.getName().equals("setBeanFactory") &&
239 					candidateMethod.getParameterTypes().length == 1 &&
240 					candidateMethod.getParameterTypes()[0].equals(BeanFactory.class) &&
241 					BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
242 		}
243 	}
244 
245 
246 	/**
247 	 * Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
248 	 * handling of bean semantics such as scoping and AOP proxying.
249 	 * @see Bean
250 	 * @see ConfigurationClassEnhancer
251 	 */
252 	private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
253 
254 		/**
255 		 * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
256 		 * existence of this bean object.
257 		 * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
258 		 * super implementation of the proxied method i.e., the actual {@code @Bean} method
259 		 */
260 		@Override
261 		public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
262 					MethodProxy cglibMethodProxy) throws Throwable {
263 
264 			ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
265 			String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
266 
267 			// Determine whether this bean is a scoped-proxy
268 			Scope scope = AnnotationUtils.findAnnotation(beanMethod, Scope.class);
269 			if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
270 				String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
271 				if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
272 					beanName = scopedBeanName;
273 				}
274 			}
275 
276 			// To handle the case of an inter-bean method reference, we must explicitly check the
277 			// container for already cached instances.
278 
279 			// First, check to see if the requested bean is a FactoryBean. If so, create a subclass
280 			// proxy that intercepts calls to getObject() and returns any cached bean instance.
281 			// This ensures that the semantics of calling a FactoryBean from within @Bean methods
282 			// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
283 			if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
284 					factoryContainsBean(beanFactory, beanName)) {
285 				Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
286 				if (factoryBean instanceof ScopedProxyFactoryBean) {
287 					// Pass through - scoped proxy factory beans are a special case and should not
288 					// be further proxied
289 				}
290 				else {
291 					// It is a candidate FactoryBean - go ahead with enhancement
292 					return enhanceFactoryBean(factoryBean.getClass(), beanFactory, beanName);
293 				}
294 			}
295 
296 			if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
297 				// The factory is calling the bean method in order to instantiate and register the bean
298 				// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
299 				// create the bean instance.
300 				if (BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
301 					logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
302 							"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
303 							"result in a failure to process annotations such as @Autowired, " +
304 							"@Resource and @PostConstruct within the method's declaring " +
305 							"@Configuration class. Add the 'static' modifier to this method to avoid " +
306 							"these container lifecycle issues; see @Bean javadoc for complete details",
307 							beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
308 				}
309 				return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
310 			}
311 			else {
312 				// The user (i.e. not the factory) is requesting this bean through a
313 				// call to the bean method, direct or indirect. The bean may have already been
314 				// marked as 'in creation' in certain autowiring scenarios; if so, temporarily
315 				// set the in-creation status to false in order to avoid an exception.
316 				boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
317 				try {
318 					if (alreadyInCreation) {
319 						beanFactory.setCurrentlyInCreation(beanName, false);
320 					}
321 					return (!ObjectUtils.isEmpty(beanMethodArgs) ?
322 							beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName));
323 				}
324 				finally {
325 					if (alreadyInCreation) {
326 						beanFactory.setCurrentlyInCreation(beanName, true);
327 					}
328 				}
329 			}
330 		}
331 
332 		/**
333 		 * Check the BeanFactory to see whether the bean named <var>beanName</var> already
334 		 * exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
335 		 * we're in the middle of servicing the initial request for this bean. From an enhanced
336 		 * factory method's perspective, this means that the bean does not actually yet exist,
337 		 * and that it is now our job to create it for the first time by executing the logic
338 		 * in the corresponding factory method.
339 		 * <p>Said another way, this check repurposes
340 		 * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
341 		 * the container is calling this method or the user is calling this method.
342 		 * @param beanName name of bean to check for
343 		 * @return whether <var>beanName</var> already exists in the factory
344 		 */
345 		private boolean factoryContainsBean(ConfigurableBeanFactory beanFactory, String beanName) {
346 			return (beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName));
347 		}
348 
349 		/**
350 		 * Check whether the given method corresponds to the container's currently invoked
351 		 * factory method. Compares method name and parameter types only in order to work
352 		 * around a potential problem with covariant return types (currently only known
353 		 * to happen on Groovy classes).
354 		 */
355 		private boolean isCurrentlyInvokedFactoryMethod(Method method) {
356 			Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
357 			return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
358 					Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
359 		}
360 
361 		/**
362 		 * Create a subclass proxy that intercepts calls to getObject(), delegating to the current BeanFactory
363 		 * instead of creating a new instance. These proxies are created only when calling a FactoryBean from
364 		 * within a Bean method, allowing for proper scoping semantics even when working against the FactoryBean
365 		 * instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
366 		 * it will not be proxied. This too is aligned with the way XML configuration works.
367 		 */
368 		private Object enhanceFactoryBean(Class<?> fbClass, final ConfigurableBeanFactory beanFactory,
369 				final String beanName) throws InstantiationException, IllegalAccessException {
370 
371 			Enhancer enhancer = new Enhancer();
372 			enhancer.setSuperclass(fbClass);
373 			enhancer.setUseFactory(false);
374 			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
375 			enhancer.setCallback(new MethodInterceptor() {
376 				@Override
377 				public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
378 					if (method.getName().equals("getObject") && args.length == 0) {
379 						return beanFactory.getBean(beanName);
380 					}
381 					return proxy.invokeSuper(obj, args);
382 				}
383 			});
384 			return enhancer.create();
385 		}
386 
387 		private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
388 			Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
389 			Assert.state(field != null, "Unable to find generated bean factory field");
390 			Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
391 			Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
392 			Assert.state(beanFactory instanceof ConfigurableBeanFactory,
393 					"Injected BeanFactory is not a ConfigurableBeanFactory");
394 			return (ConfigurableBeanFactory) beanFactory;
395 		}
396 
397 		@Override
398 		public boolean isMatch(Method candidateMethod) {
399 			return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
400 		}
401 	}
402 
403 }