11/*
2- * Copyright 2002-2016 the original author or authors.
2+ * Copyright 2002-2017 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1717package org .springframework .context .annotation ;
1818
1919import java .lang .reflect .Field ;
20+ import java .lang .reflect .InvocationHandler ;
2021import java .lang .reflect .Method ;
22+ import java .lang .reflect .Modifier ;
23+ import java .lang .reflect .Proxy ;
2124import java .util .Arrays ;
2225
2326import org .apache .commons .logging .Log ;
@@ -330,12 +333,11 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
330333 factoryContainsBean (beanFactory , beanName )) {
331334 Object factoryBean = beanFactory .getBean (BeanFactory .FACTORY_BEAN_PREFIX + beanName );
332335 if (factoryBean instanceof ScopedProxyFactoryBean ) {
333- // Pass through - scoped proxy factory beans are a special case and should not
334- // be further proxied
336+ // Scoped proxy factory beans are a special case and should not be further proxied
335337 }
336338 else {
337339 // It is a candidate FactoryBean - go ahead with enhancement
338- return enhanceFactoryBean (factoryBean , beanFactory , beanName );
340+ return enhanceFactoryBean (factoryBean , beanMethod . getReturnType (), beanFactory , beanName );
339341 }
340342 }
341343
@@ -346,68 +348,88 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
346348 if (logger .isWarnEnabled () &&
347349 BeanFactoryPostProcessor .class .isAssignableFrom (beanMethod .getReturnType ())) {
348350 logger .warn (String .format ("@Bean method %s.%s is non-static and returns an object " +
349- "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
350- "result in a failure to process annotations such as @Autowired, " +
351- "@Resource and @PostConstruct within the method's declaring " +
352- "@Configuration class. Add the 'static' modifier to this method to avoid " +
353- "these container lifecycle issues; see @Bean javadoc for complete details." ,
351+ "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
352+ "result in a failure to process annotations such as @Autowired, " +
353+ "@Resource and @PostConstruct within the method's declaring " +
354+ "@Configuration class. Add the 'static' modifier to this method to avoid " +
355+ "these container lifecycle issues; see @Bean javadoc for complete details." ,
354356 beanMethod .getDeclaringClass ().getSimpleName (), beanMethod .getName ()));
355357 }
356358 return cglibMethodProxy .invokeSuper (enhancedConfigInstance , beanMethodArgs );
357359 }
358- else {
359- // The user (i.e. not the factory) is requesting this bean through a call to
360- // the bean method, direct or indirect. The bean may have already been marked
361- // as 'in creation' in certain autowiring scenarios; if so, temporarily set
362- // the in-creation status to false in order to avoid an exception.
363- boolean alreadyInCreation = beanFactory .isCurrentlyInCreation (beanName );
364- try {
365- if (alreadyInCreation ) {
366- beanFactory .setCurrentlyInCreation (beanName , false );
367- }
368- boolean useArgs = !ObjectUtils .isEmpty (beanMethodArgs );
369- if (useArgs && beanFactory .isSingleton (beanName )) {
370- // Stubbed null arguments just for reference purposes,
371- // expecting them to be autowired for regular singleton references?
372- // A safe assumption since @Bean singleton arguments cannot be optional...
373- for (Object arg : beanMethodArgs ) {
374- if (arg == null ) {
375- useArgs = false ;
376- break ;
377- }
360+
361+ return obtainBeanInstanceFromFactory (beanMethod , beanMethodArgs , beanFactory , beanName );
362+ }
363+
364+ private Object obtainBeanInstanceFromFactory (Method beanMethod , Object [] beanMethodArgs ,
365+ ConfigurableBeanFactory beanFactory , String beanName ) {
366+
367+ // The user (i.e. not the factory) is requesting this bean through a call to
368+ // the bean method, direct or indirect. The bean may have already been marked
369+ // as 'in creation' in certain autowiring scenarios; if so, temporarily set
370+ // the in-creation status to false in order to avoid an exception.
371+ boolean alreadyInCreation = beanFactory .isCurrentlyInCreation (beanName );
372+ try {
373+ if (alreadyInCreation ) {
374+ beanFactory .setCurrentlyInCreation (beanName , false );
375+ }
376+ boolean useArgs = !ObjectUtils .isEmpty (beanMethodArgs );
377+ if (useArgs && beanFactory .isSingleton (beanName )) {
378+ // Stubbed null arguments just for reference purposes,
379+ // expecting them to be autowired for regular singleton references?
380+ // A safe assumption since @Bean singleton arguments cannot be optional...
381+ for (Object arg : beanMethodArgs ) {
382+ if (arg == null ) {
383+ useArgs = false ;
384+ break ;
378385 }
379386 }
380- Object beanInstance = (useArgs ? beanFactory .getBean (beanName , beanMethodArgs ) :
381- beanFactory .getBean (beanName ));
382- if (beanInstance != null && !ClassUtils .isAssignableValue (beanMethod .getReturnType (), beanInstance )) {
383- String msg = String .format ("@Bean method %s.%s called as a bean reference " +
384- "for type [%s] but overridden by non-compatible bean instance of type [%s]." ,
385- beanMethod .getDeclaringClass ().getSimpleName (), beanMethod .getName (),
386- beanMethod .getReturnType ().getName (), beanInstance .getClass ().getName ());
387- try {
388- BeanDefinition beanDefinition = beanFactory .getMergedBeanDefinition (beanName );
389- msg += " Overriding bean of same name declared in: " + beanDefinition .getResourceDescription ();
390- }
391- catch (NoSuchBeanDefinitionException ex ) {
392- // Ignore - simply no detailed message then.
393- }
394- throw new IllegalStateException (msg );
387+ }
388+ Object beanInstance = (useArgs ? beanFactory .getBean (beanName , beanMethodArgs ) :
389+ beanFactory .getBean (beanName ));
390+ if (beanInstance != null && !ClassUtils .isAssignableValue (beanMethod .getReturnType (), beanInstance )) {
391+ String msg = String .format ("@Bean method %s.%s called as a bean reference " +
392+ "for type [%s] but overridden by non-compatible bean instance of type [%s]." ,
393+ beanMethod .getDeclaringClass ().getSimpleName (), beanMethod .getName (),
394+ beanMethod .getReturnType ().getName (), beanInstance .getClass ().getName ());
395+ try {
396+ BeanDefinition beanDefinition = beanFactory .getMergedBeanDefinition (beanName );
397+ msg += " Overriding bean of same name declared in: " + beanDefinition .getResourceDescription ();
395398 }
396- Method currentlyInvoked = SimpleInstantiationStrategy .getCurrentlyInvokedFactoryMethod ();
397- if (currentlyInvoked != null ) {
398- String outerBeanName = BeanAnnotationHelper .determineBeanNameFor (currentlyInvoked );
399- beanFactory .registerDependentBean (beanName , outerBeanName );
399+ catch (NoSuchBeanDefinitionException ex ) {
400+ // Ignore - simply no detailed message then.
400401 }
401- return beanInstance ;
402+ throw new IllegalStateException ( msg ) ;
402403 }
403- finally {
404- if (alreadyInCreation ) {
405- beanFactory .setCurrentlyInCreation (beanName , true );
406- }
404+ Method currentlyInvoked = SimpleInstantiationStrategy .getCurrentlyInvokedFactoryMethod ();
405+ if (currentlyInvoked != null ) {
406+ String outerBeanName = BeanAnnotationHelper .determineBeanNameFor (currentlyInvoked );
407+ beanFactory .registerDependentBean (beanName , outerBeanName );
408+ }
409+ return beanInstance ;
410+ }
411+ finally {
412+ if (alreadyInCreation ) {
413+ beanFactory .setCurrentlyInCreation (beanName , true );
407414 }
408415 }
409416 }
410417
418+ @ Override
419+ public boolean isMatch (Method candidateMethod ) {
420+ return BeanAnnotationHelper .isBeanAnnotated (candidateMethod );
421+ }
422+
423+ private ConfigurableBeanFactory getBeanFactory (Object enhancedConfigInstance ) {
424+ Field field = ReflectionUtils .findField (enhancedConfigInstance .getClass (), BEAN_FACTORY_FIELD );
425+ Assert .state (field != null , "Unable to find generated bean factory field" );
426+ Object beanFactory = ReflectionUtils .getField (field , enhancedConfigInstance );
427+ Assert .state (beanFactory != null , "BeanFactory has not been injected into @Configuration class" );
428+ Assert .state (beanFactory instanceof ConfigurableBeanFactory ,
429+ "Injected BeanFactory is not a ConfigurableBeanFactory" );
430+ return (ConfigurableBeanFactory ) beanFactory ;
431+ }
432+
411433 /**
412434 * Check the BeanFactory to see whether the bean named <var>beanName</var> already
413435 * exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
@@ -444,8 +466,60 @@ private boolean isCurrentlyInvokedFactoryMethod(Method method) {
444466 * instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
445467 * it will not be proxied. This too is aligned with the way XML configuration works.
446468 */
447- private Object enhanceFactoryBean (final Object factoryBean , final ConfigurableBeanFactory beanFactory ,
448- final String beanName ) {
469+ private Object enhanceFactoryBean (final Object factoryBean , Class <?> exposedType ,
470+ final ConfigurableBeanFactory beanFactory , final String beanName ) {
471+
472+ try {
473+ Class <?> clazz = factoryBean .getClass ();
474+ boolean finalClass = Modifier .isFinal (clazz .getModifiers ());
475+ boolean finalMethod = Modifier .isFinal (clazz .getMethod ("getObject" ).getModifiers ());
476+ if (finalClass || finalMethod ) {
477+ if (exposedType .isInterface ()) {
478+ if (logger .isDebugEnabled ()) {
479+ logger .debug ("Creating interface proxy for FactoryBean '" + beanName + "' of type [" +
480+ clazz .getName () + "] for use within another @Bean method because its " +
481+ (finalClass ? "implementation class" : "getObject() method" ) +
482+ " is final: Otherwise a getObject() call would not be routed to the factory." );
483+ }
484+ return createInterfaceProxyForFactoryBean (factoryBean , exposedType , beanFactory , beanName );
485+ }
486+ else {
487+ if (logger .isInfoEnabled ()) {
488+ logger .info ("Unable to proxy FactoryBean '" + beanName + "' of type [" +
489+ clazz .getName () + "] for use within another @Bean method because its " +
490+ (finalClass ? "implementation class" : "getObject() method" ) +
491+ " is final: A getObject() call will NOT be routed to the factory. " +
492+ "Consider declaring the return type as a FactoryBean interface." );
493+ }
494+ return factoryBean ;
495+ }
496+ }
497+ }
498+ catch (NoSuchMethodException ex ) {
499+ // No getObject() method -> shouldn't happen, but as long as nobody is trying to call it...
500+ }
501+
502+ return createCglibProxyForFactoryBean (factoryBean , beanFactory , beanName );
503+ }
504+
505+ private Object createInterfaceProxyForFactoryBean (final Object factoryBean , Class <?> interfaceType ,
506+ final ConfigurableBeanFactory beanFactory , final String beanName ) {
507+
508+ return Proxy .newProxyInstance (
509+ factoryBean .getClass ().getClassLoader (), new Class <?>[] {interfaceType },
510+ new InvocationHandler () {
511+ @ Override
512+ public Object invoke (Object proxy , Method method , Object [] args ) throws Throwable {
513+ if (method .getName ().equals ("getObject" ) && args == null ) {
514+ return beanFactory .getBean (beanName );
515+ }
516+ return ReflectionUtils .invokeMethod (method , factoryBean , args );
517+ }
518+ });
519+ }
520+
521+ private Object createCglibProxyForFactoryBean (final Object factoryBean ,
522+ final ConfigurableBeanFactory beanFactory , final String beanName ) {
449523
450524 Enhancer enhancer = new Enhancer ();
451525 enhancer .setSuperclass (factoryBean .getClass ());
@@ -489,21 +563,6 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
489563
490564 return fbProxy ;
491565 }
492-
493- private ConfigurableBeanFactory getBeanFactory (Object enhancedConfigInstance ) {
494- Field field = ReflectionUtils .findField (enhancedConfigInstance .getClass (), BEAN_FACTORY_FIELD );
495- Assert .state (field != null , "Unable to find generated bean factory field" );
496- Object beanFactory = ReflectionUtils .getField (field , enhancedConfigInstance );
497- Assert .state (beanFactory != null , "BeanFactory has not been injected into @Configuration class" );
498- Assert .state (beanFactory instanceof ConfigurableBeanFactory ,
499- "Injected BeanFactory is not a ConfigurableBeanFactory" );
500- return (ConfigurableBeanFactory ) beanFactory ;
501- }
502-
503- @ Override
504- public boolean isMatch (Method candidateMethod ) {
505- return BeanAnnotationHelper .isBeanAnnotated (candidateMethod );
506- }
507566 }
508567
509568}
0 commit comments