• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.61
today-context/src/main/java/infra/context/annotation/ConfigurationClassEnhancer.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.annotation;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.reflect.Constructor;
23
import java.lang.reflect.Field;
24
import java.lang.reflect.Method;
25
import java.lang.reflect.Modifier;
26
import java.lang.reflect.Proxy;
27
import java.util.Arrays;
28

29
import infra.aop.framework.StandardProxy;
30
import infra.aop.scope.ScopedProxyFactoryBean;
31
import infra.aot.AotDetector;
32
import infra.beans.BeanInstantiationException;
33
import infra.beans.factory.BeanDefinitionStoreException;
34
import infra.beans.factory.BeanFactory;
35
import infra.beans.factory.BeanFactoryAware;
36
import infra.beans.factory.NoSuchBeanDefinitionException;
37
import infra.beans.factory.config.BeanDefinition;
38
import infra.beans.factory.config.BeanFactoryPostProcessor;
39
import infra.beans.factory.config.ConfigurableBeanFactory;
40
import infra.beans.factory.support.InstantiationStrategy;
41
import infra.beans.support.BeanInstantiator;
42
import infra.bytecode.Opcodes;
43
import infra.bytecode.Type;
44
import infra.bytecode.core.ClassEmitter;
45
import infra.bytecode.core.ClassGenerator;
46
import infra.bytecode.core.ClassLoaderAwareGeneratorStrategy;
47
import infra.bytecode.core.CodeGenerationException;
48
import infra.bytecode.core.NamingPolicy;
49
import infra.bytecode.proxy.Callback;
50
import infra.bytecode.proxy.CallbackFilter;
51
import infra.bytecode.proxy.Enhancer;
52
import infra.bytecode.proxy.Factory;
53
import infra.bytecode.proxy.MethodInterceptor;
54
import infra.bytecode.proxy.MethodProxy;
55
import infra.bytecode.proxy.NoOp;
56
import infra.bytecode.transform.TransformingClassGenerator;
57
import infra.core.SmartClassLoader;
58
import infra.lang.Assert;
59
import infra.logging.Logger;
60
import infra.logging.LoggerFactory;
61
import infra.stereotype.Component;
62
import infra.util.ClassUtils;
63
import infra.util.ObjectUtils;
64
import infra.util.ReflectionUtils;
65

66
/**
67
 * Enhances {@link Configuration} classes by generating a CGLIB subclass which
68
 * interacts with the IoC to respect bean scoping semantics for
69
 * {@code @Component} methods. Each such {@code @Component} method will be overridden in
70
 * the generated subclass, only delegating to the actual {@code @Component} method
71
 * implementation if the container actually requests the construction of a new
72
 * instance. Otherwise, a call to such an {@code @Component} method serves as a
73
 * reference back to the container, obtaining the corresponding bean by name.
74
 *
75
 * @author Chris Beams
76
 * @author Juergen Hoeller
77
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
78
 * @see #enhance
79
 * @see ConfigurationClassPostProcessor
80
 * @since 4.0
81
 */
82
class ConfigurationClassEnhancer {
3✔
83

84
  // The callbacks to use. Note that these callbacks must be stateless.
85
  static final Callback[] CALLBACKS = new Callback[] {
19✔
86
          new ComponentMethodInterceptor(),
87
          new BeanFactoryAwareMethodInterceptor(),
88
          NoOp.INSTANCE
89
  };
90

91
  private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
5✔
92

93
  private static final String BEAN_FACTORY_FIELD = "$$beanFactory";
94

95
  private static final Logger log = LoggerFactory.getLogger(ConfigurationClassEnhancer.class);
4✔
96

97
  /**
98
   * Loads the specified class and generates a CGLIB subclass of it equipped with
99
   * container-aware callbacks capable of respecting scoping and other bean semantics.
100
   *
101
   * @return the enhanced subclass
102
   */
103
  public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
104
    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
4✔
105
      if (log.isDebugEnabled()) {
3!
106
        log.debug("Ignoring request to enhance {} as it has " +
×
107
                        "already been enhanced. This usually indicates that more than one " +
108
                        "ConfigurationClassPostProcessor has been registered. This is harmless, but you may " +
109
                        "want check your configuration and remove one CCPP if possible",
110
                configClass.getName());
×
111
      }
112
      return configClass;
2✔
113
    }
114
    try {
115
      // Use original ClassLoader if config class not locally loaded in overriding class loader
116
      boolean classLoaderMismatch = (classLoader != null && classLoader != configClass.getClassLoader());
10✔
117
      if (classLoaderMismatch && classLoader instanceof SmartClassLoader smartClassLoader) {
8✔
118
        classLoader = smartClassLoader.getOriginalClassLoader();
3✔
119
        classLoaderMismatch = (classLoader != configClass.getClassLoader());
8✔
120
      }
121
      // Use original ClassLoader if config class relies on package visibility
122
      if (classLoaderMismatch && reliesOnPackageVisibility(configClass)) {
6✔
123
        classLoader = configClass.getClassLoader();
3✔
124
        classLoaderMismatch = false;
2✔
125
      }
126
      Enhancer enhancer = newEnhancer(configClass, classLoader);
5✔
127
      Class<?> enhancedClass = createClass(enhancer, classLoaderMismatch);
5✔
128
      if (log.isTraceEnabled()) {
3!
129
        log.trace("Successfully enhanced {}; enhanced class name is: {}",
×
130
                configClass.getName(), enhancedClass.getName());
×
131
      }
132
      return enhancedClass;
2✔
133
    }
134
    catch (CodeGenerationException ex) {
1✔
135
      throw new BeanDefinitionStoreException("Could not enhance configuration class [" + configClass.getName() +
8✔
136
              "]. Consider declaring @Configuration(proxyBeanMethods=false) without inter-bean references " +
137
              "between @Bean methods on the configuration class, avoiding the need for CGLIB enhancement.", ex);
138
    }
139
  }
140

141
  /**
142
   * Checks whether the given config class relies on package visibility, either for
143
   * the class and any of its constructors or for any of its {@code @Bean} methods.
144
   */
145
  private boolean reliesOnPackageVisibility(Class<?> configSuperClass) {
146
    int mod = configSuperClass.getModifiers();
3✔
147
    if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
6!
148
      return true;
2✔
149
    }
150
    for (Constructor<?> ctor : configSuperClass.getDeclaredConstructors()) {
17✔
151
      mod = ctor.getModifiers();
3✔
152
      if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
6!
153
        return true;
2✔
154
      }
155
    }
156
    for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) {
17✔
157
      if (BeanAnnotationHelper.isBeanAnnotated(method)) {
3✔
158
        mod = method.getModifiers();
3✔
159
        if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
6!
160
          return true;
2✔
161
        }
162
      }
163
    }
164
    return false;
2✔
165
  }
166

167
  /**
168
   * Creates a new CGLIB {@link Enhancer} instance.
169
   */
170
  private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
171
    Enhancer enhancer = new Enhancer();
4✔
172
    if (classLoader != null) {
2✔
173
      enhancer.setClassLoader(classLoader);
3✔
174
      if (classLoader instanceof SmartClassLoader scl && scl.isClassReloadable(configSuperClass)) {
10✔
175
        enhancer.setUseCache(false);
3✔
176
      }
177
    }
178
    enhancer.setUseFactory(false);
3✔
179
    enhancer.setSuperclass(configSuperClass);
3✔
180
    enhancer.setInterfaces(EnhancedConfiguration.class);
8✔
181
    enhancer.setNamingPolicy(NamingPolicy.forInfrastructure());
3✔
182
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
6✔
183
    enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
8!
184
    enhancer.setCallbackFilter(CALLBACK_FILTER);
3✔
185
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
4✔
186
    return enhancer;
2✔
187
  }
188

189
  /**
190
   * Uses enhancer to generate a subclass of superclass,
191
   * ensuring that callbacks are registered for the new subclass.
192
   */
193
  private Class<?> createClass(Enhancer enhancer, boolean fallback) {
194
    Class<?> subclass;
195
    try {
196
      subclass = enhancer.createClass();
3✔
197
    }
198
    catch (Throwable ex) {
1✔
199
      if (!fallback) {
2✔
200
        throw (ex instanceof CodeGenerationException cgex ? cgex : new CodeGenerationException(ex));
8!
201
      }
202
      // Possibly a package-visible @Bean method declaration not accessible
203
      // in the given ClassLoader -> retry with original ClassLoader
204
      enhancer.setClassLoader(null);
3✔
205
      subclass = enhancer.createClass();
3✔
206
    }
1✔
207

208
    // Registering callbacks statically (as opposed to thread-local)
209
    Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
3✔
210
    return subclass;
2✔
211
  }
212

213
  /**
214
   * Marker interface to be implemented by all @Configuration CGLIB subclasses.
215
   * Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance}
216
   * through checking to see if candidate classes are already assignable to it, e.g.
217
   * have already been enhanced.
218
   * <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}
219
   * classes require access to the {@link BeanFactory} that created them.
220
   * <p>Note that this interface is intended for framework-internal use only, however
221
   * must remain public in order to allow access to subclasses generated from other
222
   * packages (i.e. user code).
223
   */
224
  public interface EnhancedConfiguration extends BeanFactoryAware, StandardProxy { }
225

226
  /**
227
   * Conditional {@link Callback}.
228
   *
229
   * @see ConditionalCallbackFilter
230
   */
231
  private interface ConditionalCallback extends Callback {
232

233
    boolean isMatch(Method candidateMethod);
234
  }
235

236
  /**
237
   * A {@link CallbackFilter} that works by interrogating {@link Callback Callbacks} in the order
238
   * that they are defined via {@link ConditionalCallback}.
239
   */
240
  private static class ConditionalCallbackFilter implements CallbackFilter {
241

242
    private final Callback[] callbacks;
243

244
    private final Class<?>[] callbackTypes;
245

246
    public ConditionalCallbackFilter(Callback[] callbacks) {
2✔
247
      this.callbacks = callbacks;
3✔
248
      this.callbackTypes = new Class<?>[callbacks.length];
5✔
249
      for (int i = 0; i < callbacks.length; i++) {
8✔
250
        this.callbackTypes[i] = callbacks[i].getClass();
8✔
251
      }
252
    }
1✔
253

254
    @Override
255
    public int accept(Method method) {
256
      for (int i = 0; i < this.callbacks.length; i++) {
9!
257
        Callback callback = this.callbacks[i];
5✔
258
        if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
8✔
259
          return i;
2✔
260
        }
261
      }
262
      throw new IllegalStateException("No callback available for method " + method.getName());
×
263
    }
264

265
    public Class<?>[] getCallbackTypes() {
266
      return this.callbackTypes;
3✔
267
    }
268
  }
269

270
  /**
271
   * Custom extension of CGLIB's DefaultGeneratorStrategy, introducing a {@link BeanFactory} field.
272
   * Also exposes the application ClassLoader as thread context ClassLoader for the time of
273
   * class generation (in order for ASM to pick it up when doing common superclass resolution).
274
   */
275
  private static final class BeanFactoryAwareGeneratorStrategy extends ClassLoaderAwareGeneratorStrategy {
276

277
    public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) {
278
      super(classLoader);
3✔
279
    }
1✔
280

281
    @Override
282
    protected ClassGenerator transform(ClassGenerator cg) {
283
      ClassEmitter transformer = new ClassEmitter() {
11✔
284
        @Override
285
        public void endClass() {
286
          declare_field(Opcodes.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.forClass(BeanFactory.class), null);
7✔
287
          super.endClass();
2✔
288
        }
1✔
289
      };
290
      return new TransformingClassGenerator(cg, transformer);
6✔
291
    }
292

293
  }
294

295
  /**
296
   * Intercepts the invocation of any {@link BeanFactoryAware#setBeanFactory(BeanFactory)} on
297
   * {@code @Configuration} class instances for the purpose of recording the {@link BeanFactory}.
298
   *
299
   * @see EnhancedConfiguration
300
   */
301
  private static final class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
302

303
    @Override
304
    @Nullable
305
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
306
      Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
5✔
307
      Assert.state(field != null, "Unable to find generated BeanFactory field");
6!
308
      field.set(obj, args[0]);
6✔
309

310
      // Does the actual (non-CGLIB) superclass implement BeanFactoryAware?
311
      // If so, call its setBeanFactory() method. If not, just exit.
312
      if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
7✔
313
        return proxy.invokeSuper(obj, args);
5✔
314
      }
315
      return null;
2✔
316
    }
317

318
    @Override
319
    public boolean isMatch(Method candidateMethod) {
320
      return isSetBeanFactory(candidateMethod);
3✔
321
    }
322

323
    public static boolean isSetBeanFactory(Method candidateMethod) {
324
      return candidateMethod.getName().equals("setBeanFactory")
7✔
325
              && candidateMethod.getParameterCount() == 1
5!
326
              && BeanFactory.class == candidateMethod.getParameterTypes()[0]
6!
327
              && BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass());
6!
328
    }
329
  }
330

331
  /**
332
   * Intercepts the invocation of any {@link Component}-annotated methods in order to ensure proper
333
   * handling of bean semantics such as scoping and AOP proxying.
334
   *
335
   * @see Component
336
   * @see ConfigurationClassEnhancer
337
   */
338
  private static final class ComponentMethodInterceptor implements MethodInterceptor, ConditionalCallback {
339

340
    /**
341
     * Enhance a {@link Component @Component} method to check the supplied BeanFactory for the
342
     * existence of this bean object.
343
     *
344
     * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
345
     * super implementation of the proxied method i.e., the actual {@code @Component} method
346
     */
347
    @Override
348
    @Nullable
349
    public Object intercept(Object enhancedConfigInstance, Method beanMethod,
350
            Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable {
351

352
      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
4✔
353
      String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod, beanFactory);
4✔
354

355
      // Determine whether this bean is a scoped-proxy
356
      if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
3✔
357
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
3✔
358
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
4✔
359
          beanName = scopedBeanName;
2✔
360
        }
361
      }
362

363
      // To handle the case of an inter-bean method reference, we must explicitly check the
364
      // container for already cached instances.
365

366
      // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
367
      // proxy that intercepts calls to getObject() and returns any cached bean instance.
368
      // This ensures that the semantics of calling a FactoryBean from within @Component methods
369
      String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + beanName;
3✔
370
      if (factoryContainsBean(beanFactory, factoryBeanName) && factoryContainsBean(beanFactory, beanName)) {
10✔
371
        Object factoryBean = beanFactory.getBean(factoryBeanName);
4✔
372
        if (factoryBean instanceof ScopedProxyFactoryBean) {
4✔
373
          // Scoped proxy factory beans are a special case and should not be further proxied
374
        }
375
        else if (factoryBean != null) {
2!
376
          // It is a candidate FactoryBean - go ahead with enhancement
377
          return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
8✔
378
        }
379
      }
380

381
      // fix circular bean creation
382
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
4✔
383
        // The factory is calling the bean method in order to instantiate and register the bean
384
        // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
385
        // create the bean instance.
386
        if (log.isInfoEnabled() &&
5✔
387
                BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
3✔
388
          log.info("@Component method {}.{} is non-static and returns an object " +
4✔
389
                          "assignable to Framework's BeanFactoryPostProcessor interface. This will " +
390
                          "result in a failure to process annotations such as @Autowired, " +
391
                          "@Resource and @PostConstruct within the method's declaring " +
392
                          "@Configuration class. Add the 'static' modifier to this method to avoid " +
393
                          "these container lifecycle issues; see @Component javadoc for complete details.",
394
                  beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName());
4✔
395
        }
396
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
5✔
397
      }
398

399
      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
7✔
400
    }
401

402
    @Nullable
403
    private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
404
            ConfigurableBeanFactory beanFactory, String beanName) {
405

406
      // The user (i.e. not the factory) is requesting this bean through a call to
407
      // the bean method, direct or indirect. The bean may have already been marked
408
      // as 'in creation' in certain autowiring scenarios; if so, temporarily set
409
      // the in-creation status to false in order to avoid an exception.
410
      boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
4✔
411
      try {
412
        if (alreadyInCreation) {
2✔
413
          beanFactory.setCurrentlyInCreation(beanName, false);
4✔
414
        }
415
        boolean useArgs = ObjectUtils.isNotEmpty(beanMethodArgs);
3✔
416
        if (useArgs && beanFactory.isSingleton(beanName)) {
6✔
417
          // Stubbed null arguments just for reference purposes,
418
          // expecting them to be autowired for regular singleton references?
419
          // A safe assumption since @Component singleton arguments cannot be optional...
420
          for (Object arg : beanMethodArgs) {
16✔
421
            if (arg == null) {
2✔
422
              useArgs = false;
2✔
423
              break;
1✔
424
            }
425
          }
426
        }
427
        Object beanInstance = useArgs ? beanFactory.getBean(beanName, beanMethodArgs)
7✔
428
                : beanFactory.getBean(beanName);
4✔
429
        if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
5✔
430
          if (beanInstance == null) { // maybe null from factory-method
2!
431
            if (log.isDebugEnabled()) {
×
432
              log.debug(String.format("@Component method %s.%s called as bean reference " +
×
433
                              "for type [%s] returned null bean; resolving to null value.",
434
                      beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
×
435
                      beanMethod.getReturnType().getName()));
×
436
            }
437
          }
438
          else {
439
            String msg = String.format("@Component method %s.%s called as bean reference " +
8✔
440
                            "for type [%s] but overridden by non-compatible bean instance of type [%s].",
441
                    beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
11✔
442
                    beanMethod.getReturnType().getName(), beanInstance.getClass().getName());
9✔
443
            try {
444
              BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
4✔
445
              msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();
5✔
446
            }
447
            catch (NoSuchBeanDefinitionException ex) {
×
448
              // Ignore - simply no detailed message then.
449
            }
1✔
450
            throw new IllegalStateException(msg);
5✔
451
          }
452
        }
453
        Method currentlyInvoked = InstantiationStrategy.getCurrentlyInvokedFactoryMethod();
2✔
454
        if (currentlyInvoked != null) {
2✔
455
          String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked, beanFactory);
4✔
456
          beanFactory.registerDependentBean(beanName, outerBeanName);
4✔
457
        }
458
        return beanInstance;
4✔
459
      }
460
      finally {
461
        if (alreadyInCreation) {
2✔
462
          beanFactory.setCurrentlyInCreation(beanName, true);
4✔
463
        }
464
      }
465
    }
466

467
    @Override
468
    public boolean isMatch(Method candidateMethod) {
469
      return candidateMethod.getDeclaringClass() != Object.class
6✔
470
              && !BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod)
3✔
471
              && BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
5✔
472
    }
473

474
    private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
475
      Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
5✔
476
      Assert.state(field != null, "Unable to find generated bean factory field");
6!
477
      Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
4✔
478
      Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
7✔
479
      Assert.state(beanFactory instanceof ConfigurableBeanFactory,
4✔
480
              "Injected BeanFactory is not a ConfigurableBeanFactory");
481
      return (ConfigurableBeanFactory) beanFactory;
3✔
482
    }
483

484
    /**
485
     * Check the BeanFactory to see whether the bean named <var>beanName</var> already
486
     * exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
487
     * we're in the middle of servicing the initial request for this bean. From an enhanced
488
     * factory method's perspective, this means that the bean does not actually yet exist,
489
     * and that it is now our job to create it for the first time by executing the logic
490
     * in the corresponding factory method.
491
     * <p>Said another way, this check repurposes
492
     * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
493
     * the container is calling this method or the user is calling this method.
494
     *
495
     * @param beanName name of bean to check for
496
     * @return whether <var>beanName</var> already exists in the factory
497
     */
498
    private boolean factoryContainsBean(ConfigurableBeanFactory beanFactory, String beanName) {
499
      return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName);
12✔
500
    }
501

502
    /**
503
     * Check whether the given method corresponds to the container's currently invoked
504
     * factory method. Compares method name and parameter types only in order to work
505
     * around a potential problem with covariant return types (currently only known
506
     * to happen on Groovy classes).
507
     */
508
    private boolean isCurrentlyInvokedFactoryMethod(Method method) {
509
      Method currentlyInvoked = InstantiationStrategy.getCurrentlyInvokedFactoryMethod();
2✔
510
      return currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName())
10✔
511
              && Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes());
8!
512
    }
513

514
    /**
515
     * Create a subclass proxy that intercepts calls to getObject(), delegating to the current BeanFactory
516
     * instead of creating a new instance. These proxies are created only when calling a FactoryBean from
517
     * within a Bean method, allowing for proper scoping semantics even when working against the FactoryBean
518
     * instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
519
     * it will not be proxied. This too is aligned with the way XML configuration works.
520
     */
521
    private Object enhanceFactoryBean(Object factoryBean, Class<?> exposedType,
522
            ConfigurableBeanFactory beanFactory, String beanName) {
523

524
      try {
525
        Class<?> clazz = factoryBean.getClass();
3✔
526
        boolean finalClass = Modifier.isFinal(clazz.getModifiers());
4✔
527
        boolean finalMethod = Modifier.isFinal(clazz.getMethod("getObject").getModifiers());
8✔
528
        if (finalClass || finalMethod) {
4✔
529
          if (exposedType.isInterface()) {
3✔
530
            if (log.isTraceEnabled()) {
3!
531
              log.trace("Creating interface proxy for FactoryBean '{}' of type [{}] for use within another @Component " +
×
532
                              "method because its {} is final: Otherwise a getObject() call would not be routed to the factory.",
533
                      beanName, clazz.getName(), (finalClass ? "implementation class" : "getObject() method"));
×
534
            }
535
            return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);
7✔
536
          }
537
          else {
538
            if (log.isDebugEnabled()) {
3!
539
              log.debug("Unable to proxy FactoryBean '{}' of type [{}] for use within another @Component method " +
×
540
                              "because its {} is final: A getObject() call will NOT be routed to the factory. " +
541
                              "Consider declaring the return type as a FactoryBean interface.",
542
                      beanName, clazz.getName(), finalClass ? "implementation class" : "getObject() method");
×
543
            }
544
            return factoryBean;
2✔
545
          }
546
        }
547
      }
548
      catch (NoSuchMethodException ex) {
×
549
        // No getObject() method -> shouldn't happen, but as long as nobody is trying to call it...
550
      }
1✔
551

552
      return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
6✔
553
    }
554

555
    private Object createInterfaceProxyForFactoryBean(
556
            Object factoryBean, Class<?> interfaceType,
557
            ConfigurableBeanFactory beanFactory, String beanName) {
558

559
      return Proxy.newProxyInstance(
3✔
560
              factoryBean.getClass().getClassLoader(), new Class<?>[] { interfaceType },
12✔
561
              (proxy, method, args) -> {
562
                if (method.getName().equals("getObject") && args == null) {
7!
563
                  return beanFactory.getBean(beanName);
4✔
564
                }
565
                return ReflectionUtils.invokeMethod(method, factoryBean, args);
5✔
566
              });
567
    }
568

569
    private Object createCglibProxyForFactoryBean(
570
            Object factoryBean, ConfigurableBeanFactory beanFactory, String beanName) {
571

572
      Enhancer enhancer = new Enhancer();
4✔
573
      enhancer.setAttemptLoad(true);
3✔
574
      enhancer.setSuperclass(factoryBean.getClass());
4✔
575
      enhancer.setCallbackType(MethodInterceptor.class);
3✔
576
      enhancer.setNamingPolicy(NamingPolicy.forInfrastructure());
3✔
577

578
      // Ideally create enhanced FactoryBean proxy without constructor side effects,
579
      // analogous to AOP proxy creation in ObjenesisCglibAopProxy...
580
      Class<?> fbClass = enhancer.createClass();
3✔
581
      Object fbProxy;
582
      try {
583
        fbProxy = ReflectionUtils.accessibleConstructor(fbClass).newInstance();
8✔
584
      }
585
      catch (Throwable ex) {
1✔
586
        try {
587
          // fallback using BeanInstantiator.forSerialization
588
          fbProxy = BeanInstantiator.forSerialization(fbClass).instantiate();
4✔
589
        }
590
        catch (BeanInstantiationException ignored) {
×
591
          throw new IllegalStateException("Unable to instantiate enhanced FactoryBean using constructor, " +
×
592
                  "and regular FactoryBean instantiation via default constructor fails as well", ex);
593
        }
1✔
594
      }
1✔
595

596
      ((Factory) fbProxy).setCallback(0, (MethodInterceptor) (obj, method, args, proxy) -> {
8✔
597
        if (method.getName().equals("getObject") && args.length == 0) {
8!
598
          return beanFactory.getBean(beanName);
4✔
599
        }
600
        return proxy.invoke(factoryBean, args);
5✔
601
      });
602

603
      return fbProxy;
2✔
604
    }
605
  }
606

607
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc