1
1
/*
2
- * Copyright 2002-2016 the original author or authors.
2
+ * Copyright 2002-2017 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .context .annotation ;
18
18
19
19
import java .lang .reflect .Field ;
20
+ import java .lang .reflect .InvocationHandler ;
20
21
import java .lang .reflect .Method ;
22
+ import java .lang .reflect .Modifier ;
23
+ import java .lang .reflect .Proxy ;
21
24
import java .util .Arrays ;
22
25
23
26
import org .apache .commons .logging .Log ;
@@ -330,12 +333,11 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
330
333
factoryContainsBean (beanFactory , beanName )) {
331
334
Object factoryBean = beanFactory .getBean (BeanFactory .FACTORY_BEAN_PREFIX + beanName );
332
335
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
335
337
}
336
338
else {
337
339
// It is a candidate FactoryBean - go ahead with enhancement
338
- return enhanceFactoryBean (factoryBean , beanFactory , beanName );
340
+ return enhanceFactoryBean (factoryBean , beanMethod . getReturnType (), beanFactory , beanName );
339
341
}
340
342
}
341
343
@@ -346,68 +348,88 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
346
348
if (logger .isWarnEnabled () &&
347
349
BeanFactoryPostProcessor .class .isAssignableFrom (beanMethod .getReturnType ())) {
348
350
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." ,
354
356
beanMethod .getDeclaringClass ().getSimpleName (), beanMethod .getName ()));
355
357
}
356
358
return cglibMethodProxy .invokeSuper (enhancedConfigInstance , beanMethodArgs );
357
359
}
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 ;
378
385
}
379
386
}
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 ();
395
398
}
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.
400
401
}
401
- return beanInstance ;
402
+ throw new IllegalStateException ( msg ) ;
402
403
}
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 );
407
414
}
408
415
}
409
416
}
410
417
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
+
411
433
/**
412
434
* Check the BeanFactory to see whether the bean named <var>beanName</var> already
413
435
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
@@ -444,8 +466,60 @@ private boolean isCurrentlyInvokedFactoryMethod(Method method) {
444
466
* instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
445
467
* it will not be proxied. This too is aligned with the way XML configuration works.
446
468
*/
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 ) {
449
523
450
524
Enhancer enhancer = new Enhancer ();
451
525
enhancer .setSuperclass (factoryBean .getClass ());
@@ -489,21 +563,6 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
489
563
490
564
return fbProxy ;
491
565
}
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
- }
507
566
}
508
567
509
568
}
0 commit comments