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

TAKETODAY / today-infrastructure / 19225086188

10 Nov 2025 08:15AM UTC coverage: 84.13%. Remained the same
19225086188

push

github

TAKETODAY
:white_check_mark:

61359 of 78029 branches covered (78.64%)

Branch coverage included in aggregate %.

145112 of 167391 relevant lines covered (86.69%)

3.69 hits per line

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

80.9
today-context/src/main/java/infra/context/annotation/CommonAnnotationBeanPostProcessor.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.beans.PropertyDescriptor;
23
import java.io.Serial;
24
import java.io.Serializable;
25
import java.lang.annotation.Annotation;
26
import java.lang.reflect.AnnotatedElement;
27
import java.lang.reflect.Field;
28
import java.lang.reflect.Member;
29
import java.lang.reflect.Method;
30
import java.lang.reflect.Modifier;
31
import java.util.ArrayList;
32
import java.util.Collection;
33
import java.util.Collections;
34
import java.util.HashSet;
35
import java.util.LinkedHashSet;
36
import java.util.List;
37
import java.util.Set;
38
import java.util.concurrent.ConcurrentHashMap;
39

40
import infra.aop.TargetSource;
41
import infra.aop.framework.ProxyFactory;
42
import infra.aot.generate.AccessControl;
43
import infra.aot.generate.GeneratedClass;
44
import infra.aot.generate.GeneratedMethod;
45
import infra.aot.generate.GenerationContext;
46
import infra.aot.hint.ExecutableMode;
47
import infra.aot.hint.RuntimeHints;
48
import infra.aot.hint.support.ClassHintUtils;
49
import infra.beans.BeanUtils;
50
import infra.beans.PropertyValues;
51
import infra.beans.factory.BeanCreationException;
52
import infra.beans.factory.BeanFactory;
53
import infra.beans.factory.BeanFactoryAware;
54
import infra.beans.factory.DependenciesBeanPostProcessor;
55
import infra.beans.factory.NoSuchBeanDefinitionException;
56
import infra.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
57
import infra.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
58
import infra.beans.factory.annotation.InjectionMetadata;
59
import infra.beans.factory.aot.BeanRegistrationAotContribution;
60
import infra.beans.factory.aot.BeanRegistrationCode;
61
import infra.beans.factory.config.AutowireCapableBeanFactory;
62
import infra.beans.factory.config.BeanPostProcessor;
63
import infra.beans.factory.config.ConfigurableBeanFactory;
64
import infra.beans.factory.config.DependencyDescriptor;
65
import infra.beans.factory.config.EmbeddedValueResolver;
66
import infra.beans.factory.support.AutowireCandidateResolver;
67
import infra.beans.factory.support.RegisteredBean;
68
import infra.beans.factory.support.RootBeanDefinition;
69
import infra.beans.factory.support.StandardBeanFactory;
70
import infra.core.BridgeMethodResolver;
71
import infra.core.MethodParameter;
72
import infra.core.StringValueResolver;
73
import infra.core.annotation.AnnotationUtils;
74
import infra.javapoet.ClassName;
75
import infra.javapoet.CodeBlock;
76
import infra.jndi.support.SimpleJndiBeanFactory;
77
import infra.lang.Assert;
78
import infra.util.ClassUtils;
79
import infra.util.ObjectUtils;
80
import infra.util.ReflectionUtils;
81
import infra.util.StringUtils;
82
import jakarta.annotation.Resource;
83
import jakarta.ejb.EJB;
84

85
/**
86
 * {@link BeanPostProcessor} implementation
87
 * that supports common Java annotations out of the box, in particular the common
88
 * annotations in the {@code jakarta.annotation} package. These common Java
89
 * annotations are supported in many Jakarta EE technologies (e.g. JSF and JAX-RS).
90
 *
91
 * <p>This post-processor includes support for the {@link jakarta.annotation.PostConstruct}
92
 * and {@link jakarta.annotation.PreDestroy} annotations - as init annotation
93
 * and destroy annotation, respectively - through inheriting from
94
 * {@link InitDestroyAnnotationBeanPostProcessor} with pre-configured annotation types.
95
 *
96
 * <p>The central element is the {@link jakarta.annotation.Resource} annotation
97
 * for annotation-driven injection of named beans, by default from the containing
98
 * Framework BeanFactory, with only {@code mappedName} references resolved in JNDI.
99
 * The {@link #setAlwaysUseJndiLookup "alwaysUseJndiLookup" flag} enforces JNDI lookups
100
 * equivalent to standard Jakarta EE resource injection for {@code name} references
101
 * and default names as well. The target beans can be simple POJOs, with no special
102
 * requirements other than the type having to match.
103
 *
104
 * <p>This post-processor also supports the EJB 3 {@link jakarta.ejb.EJB} annotation,
105
 * analogous to {@link jakarta.annotation.Resource}, with the capability to
106
 * specify both a local bean name and a global JNDI name for fallback retrieval.
107
 * The target beans can be plain POJOs as well as EJB 3 Session Beans in this case.
108
 *
109
 * <p>For default usage, resolving resource names as Framework bean names,
110
 * simply define the following in your application context:
111
 *
112
 * <pre class="code">
113
 * &lt;bean class="infra.context.annotation.CommonAnnotationBeanPostProcessor"/&gt;</pre>
114
 *
115
 * For direct JNDI access, resolving resource names as JNDI resource references
116
 * within the Jakarta EE application's "java:comp/env/" namespace, use the following:
117
 *
118
 * <pre class="code">
119
 * &lt;bean class="infra.context.annotation.CommonAnnotationBeanPostProcessor"&gt;
120
 *   &lt;property name="alwaysUseJndiLookup" value="true"/&gt;
121
 * &lt;/bean&gt;</pre>
122
 *
123
 * {@code mappedName} references will always be resolved in JNDI,
124
 * allowing for global JNDI names (including "java:" prefix) as well. The
125
 * "alwaysUseJndiLookup" flag just affects {@code name} references and
126
 * default names (inferred from the field name / property name).
127
 *
128
 * <p><b>NOTE:</b> A default CommonAnnotationBeanPostProcessor will be registered
129
 * by the "context:annotation-config" and "context:component-scan" XML tags.
130
 * Remove or turn off the default annotation configuration there if you intend
131
 * to specify a custom CommonAnnotationBeanPostProcessor bean definition!
132
 * <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus
133
 * the latter configuration will override the former for properties wired through
134
 * both approaches.
135
 *
136
 * @author Juergen Hoeller
137
 * @author Sam Brannen
138
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
139
 * @see #setAlwaysUseJndiLookup
140
 * @see #setResourceFactory
141
 * @see InitDestroyAnnotationBeanPostProcessor
142
 * @see AutowiredAnnotationBeanPostProcessor
143
 * @since 4.0 2022/3/5 12:09
144
 */
145
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
146
        implements DependenciesBeanPostProcessor, BeanFactoryAware, Serializable {
147

148
  @Serial
149
  private static final long serialVersionUID = 1L;
150

151
  // Defensive reference to JNDI API for JDK 9+ (optional java.naming module)
152
  private static final boolean jndiPresent = ClassUtils.isPresent(
4✔
153
          "javax.naming.InitialContext", CommonAnnotationBeanPostProcessor.class);
154

155
  private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4);
5✔
156

157
  @Nullable
158
  private static final Class<? extends Annotation> jakartaResourceType;
159

160
  @Nullable
161
  private static final Class<? extends Annotation> javaxResourceType;
162

163
  @Nullable
164
  private static final Class<? extends Annotation> ejbAnnotationType;
165

166
  static {
167
    jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
3✔
168
    if (jakartaResourceType != null) {
2!
169
      resourceAnnotationTypes.add(jakartaResourceType);
4✔
170
    }
171

172
    javaxResourceType = loadAnnotationType("javax.annotation.Resource");
3✔
173
    if (javaxResourceType != null) {
2✔
174
      resourceAnnotationTypes.add(javaxResourceType);
4✔
175
    }
176

177
    ejbAnnotationType = loadAnnotationType("jakarta.ejb.EJB");
3✔
178
    if (ejbAnnotationType != null) {
2✔
179
      resourceAnnotationTypes.add(ejbAnnotationType);
4✔
180
    }
181
  }
1✔
182

183
  private final HashSet<String> ignoredResourceTypes = new HashSet<>(1);
6✔
184

185
  private boolean fallbackToDefaultTypeMatch = true;
3✔
186

187
  private boolean alwaysUseJndiLookup = false;
3✔
188

189
  @Nullable
190
  private transient BeanFactory jndiFactory;
191

192
  @Nullable
193
  private transient BeanFactory resourceFactory;
194

195
  @Nullable
196
  private transient BeanFactory beanFactory;
197

198
  @Nullable
199
  private transient StringValueResolver embeddedValueResolver;
200

201
  private final transient ConcurrentHashMap<String, InjectionMetadata>
6✔
202
          injectionMetadataCache = new ConcurrentHashMap<>(256);
203

204
  /**
205
   * Create a new CommonAnnotationBeanPostProcessor,
206
   * with the init and destroy annotation types set to
207
   * {@link jakarta.annotation.PostConstruct} and {@link jakarta.annotation.PreDestroy},
208
   * respectively.
209
   */
210
  public CommonAnnotationBeanPostProcessor() {
2✔
211
    setOrder(LOWEST_PRECEDENCE - 3);
3✔
212

213
    // Jakarta EE 9 set of annotations in jakarta.annotation package
214
    addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
4✔
215
    addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));
4✔
216

217
    // Tolerate legacy JSR-250 annotations in javax.annotation package
218
    addInitAnnotationType(loadAnnotationType("javax.annotation.PostConstruct"));
4✔
219
    addDestroyAnnotationType(loadAnnotationType("javax.annotation.PreDestroy"));
4✔
220

221
    // java.naming module present on JDK 9+?
222
    if (jndiPresent) {
2!
223
      this.jndiFactory = new SimpleJndiBeanFactory();
5✔
224
    }
225
  }
1✔
226

227
  /**
228
   * Ignore the given resource type when resolving {@code @Resource} annotations.
229
   *
230
   * @param resourceType the resource type to ignore
231
   */
232
  public void ignoreResourceType(String resourceType) {
233
    Assert.notNull(resourceType, "Ignored resource type is required");
×
234
    this.ignoredResourceTypes.add(resourceType);
×
235
  }
×
236

237
  /**
238
   * Set whether to allow a fallback to a type match if no explicit name has been
239
   * specified. The default name (i.e. the field name or bean property name) will
240
   * still be checked first; if a bean of that name exists, it will be taken.
241
   * However, if no bean of that name exists, a by-type resolution of the
242
   * dependency will be attempted if this flag is "true".
243
   * <p>Default is "true". Switch this flag to "false" in order to enforce a
244
   * by-name lookup in all cases, throwing an exception in case of no name match.
245
   *
246
   * @see AutowireCapableBeanFactory#resolveDependency
247
   */
248
  public void setFallbackToDefaultTypeMatch(boolean fallbackToDefaultTypeMatch) {
249
    this.fallbackToDefaultTypeMatch = fallbackToDefaultTypeMatch;
×
250
  }
×
251

252
  /**
253
   * Set whether to always use JNDI lookups equivalent to standard Jakarta EE resource
254
   * injection, <b>even for {@code name} attributes and default names</b>.
255
   * <p>Default is "false": Resource names are used for Framework bean lookups in the
256
   * containing BeanFactory; only {@code mappedName} attributes point directly
257
   * into JNDI. Switch this flag to "true" for enforcing Jakarta EE style JNDI lookups
258
   * in any case, even for {@code name} attributes and default names.
259
   *
260
   * @see #setJndiFactory
261
   * @see #setResourceFactory
262
   */
263
  public void setAlwaysUseJndiLookup(boolean alwaysUseJndiLookup) {
264
    this.alwaysUseJndiLookup = alwaysUseJndiLookup;
×
265
  }
×
266

267
  /**
268
   * Specify the factory for objects to be injected into {@code @Resource} /
269
   * {@code @EJB} annotated fields and setter methods,
270
   * <b>for {@code mappedName} attributes that point directly into JNDI</b>.
271
   * This factory will also be used if "alwaysUseJndiLookup" is set to "true" in order
272
   * to enforce JNDI lookups even for {@code name} attributes and default names.
273
   * <p>The default is a {@link SimpleJndiBeanFactory}
274
   * for JNDI lookup behavior equivalent to standard Jakarta EE resource injection.
275
   *
276
   * @see #setResourceFactory
277
   * @see #setAlwaysUseJndiLookup
278
   */
279
  public void setJndiFactory(BeanFactory jndiFactory) {
280
    Assert.notNull(jndiFactory, "jndiFactory is required");
×
281
    this.jndiFactory = jndiFactory;
×
282
  }
×
283

284
  /**
285
   * Specify the factory for objects to be injected into {@code @Resource} /
286
   * {@code @EJB} annotated fields and setter methods,
287
   * <b>for {@code name} attributes and default names</b>.
288
   * <p>The default is the BeanFactory that this post-processor is defined in,
289
   * if any, looking up resource names as Framework bean names. Specify the resource
290
   * factory explicitly for programmatic usage of this post-processor.
291
   * <p>Specifying Framework's {@link SimpleJndiBeanFactory}
292
   * leads to JNDI lookup behavior equivalent to standard Jakarta EE resource injection,
293
   * even for {@code name} attributes and default names. This is the same behavior
294
   * that the "alwaysUseJndiLookup" flag enables.
295
   *
296
   * @see #setAlwaysUseJndiLookup
297
   */
298
  public void setResourceFactory(BeanFactory resourceFactory) {
299
    Assert.notNull(resourceFactory, "resourceFactory is required");
3✔
300
    this.resourceFactory = resourceFactory;
3✔
301
  }
1✔
302

303
  @Override
304
  public void setBeanFactory(BeanFactory beanFactory) {
305
    super.setBeanFactory(beanFactory);
3✔
306
    this.beanFactory = beanFactory;
3✔
307
    if (this.resourceFactory == null) {
3!
308
      this.resourceFactory = beanFactory;
3✔
309
    }
310
    if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
6!
311
      this.embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory);
6✔
312
    }
313
  }
1✔
314

315
  @Override
316
  public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
317
    super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
5✔
318
    InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
6✔
319
    metadata.checkConfigMembers(beanDefinition);
3✔
320
  }
1✔
321

322
  @Override
323
  @Nullable
324
  public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
325
    BeanRegistrationAotContribution parentAotContribution = super.processAheadOfTime(registeredBean);
4✔
326
    Class<?> beanClass = registeredBean.getBeanClass();
3✔
327
    String beanName = registeredBean.getBeanName();
3✔
328
    RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
3✔
329
    InjectionMetadata metadata = findResourceMetadata(beanName, beanClass,
6✔
330
            beanDefinition.getPropertyValues());
1✔
331
    Collection<LookupElement> injectedElements = getInjectedElements(metadata,
5✔
332
            beanDefinition.getPropertyValues());
1✔
333
    if (ObjectUtils.isNotEmpty(injectedElements)) {
3✔
334
      AotContribution aotContribution = new AotContribution(beanClass, injectedElements,
6✔
335
              getAutowireCandidateResolver(registeredBean));
3✔
336
      return BeanRegistrationAotContribution.concat(parentAotContribution, aotContribution);
4✔
337
    }
338
    return parentAotContribution;
2✔
339
  }
340

341
  @Nullable
342
  private AutowireCandidateResolver getAutowireCandidateResolver(RegisteredBean registeredBean) {
343
    if (registeredBean.getBeanFactory() instanceof StandardBeanFactory factory) {
9!
344
      return factory.getAutowireCandidateResolver();
3✔
345
    }
346
    return null;
×
347
  }
348

349
  @SuppressWarnings({ "rawtypes", "unchecked" })
350
  private Collection<LookupElement> getInjectedElements(InjectionMetadata metadata, PropertyValues propertyValues) {
351
    return (Collection) metadata.getInjectedElements(propertyValues);
4✔
352
  }
353

354
  @Override
355
  public void resetBeanDefinition(String beanName) {
356
    this.injectionMetadataCache.remove(beanName);
×
357
  }
×
358

359
  @Override
360
  @Nullable
361
  public PropertyValues processDependencies(@Nullable PropertyValues propertyValues, Object bean, String beanName) {
362
    InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), propertyValues);
7✔
363
    try {
364
      metadata.inject(bean, beanName, propertyValues);
5✔
365
      return propertyValues;
2✔
366
    }
367
    catch (Throwable ex) {
1✔
368
      throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
7✔
369
    }
370
  }
371

372
  /**
373
   * <em>Native</em> processing method for direct calls with an arbitrary target
374
   * instance, resolving all of its fields and methods which are annotated with
375
   * one of the supported 'resource' annotation types.
376
   *
377
   * @param bean the target instance to process
378
   * @throws BeanCreationException if resource injection failed
379
   * @since 5.0
380
   */
381
  public void processInjection(Object bean) throws BeanCreationException {
382
    Class<?> clazz = bean.getClass();
3✔
383
    InjectionMetadata metadata = findResourceMetadata(clazz.getName(), clazz, null);
7✔
384
    try {
385
      metadata.inject(bean, null, null);
5✔
386
    }
387
    catch (BeanCreationException ex) {
×
388
      throw ex;
×
389
    }
390
    catch (Throwable ex) {
×
391
      throw new BeanCreationException(
×
392
              "Injection of resource dependencies failed for class [" + clazz + "]", ex);
393
    }
1✔
394
  }
1✔
395

396
  @SuppressWarnings("NullAway")
397
  private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
398
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
399
    String cacheKey = StringUtils.isNotEmpty(beanName) ? beanName : clazz.getName();
6!
400
    // Quick check on the concurrent map first, with minimal locking.
401
    InjectionMetadata metadata = injectionMetadataCache.get(cacheKey);
6✔
402
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
4✔
403
      synchronized(injectionMetadataCache) {
5✔
404
        metadata = injectionMetadataCache.get(cacheKey);
6✔
405
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
4!
406
          if (metadata != null) {
2✔
407
            metadata.clear(pvs);
3✔
408
          }
409
          metadata = buildResourceMetadata(clazz);
4✔
410
          this.injectionMetadataCache.put(cacheKey, metadata);
6✔
411
        }
412
      }
3✔
413
    }
414
    return metadata;
2✔
415
  }
416

417
  private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
418
    if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
4✔
419
      return InjectionMetadata.EMPTY;
2✔
420
    }
421

422
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
4✔
423
    Class<?> targetClass = ClassUtils.getUserClass(clazz);
3✔
424

425
    do {
426
      final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
4✔
427

428
      ReflectionUtils.doWithLocalFields(targetClass, field -> {
5✔
429
        if (ejbAnnotationType != null && field.isAnnotationPresent(ejbAnnotationType)) {
6✔
430
          if (Modifier.isStatic(field.getModifiers())) {
4!
431
            throw new IllegalStateException("@EJB annotation is not supported on static fields");
×
432
          }
433
          currElements.add(new EjbRefElement(field, field, null));
11✔
434
        }
435
        else if (jakartaResourceType != null && field.isAnnotationPresent(jakartaResourceType)) {
6!
436
          if (Modifier.isStatic(field.getModifiers())) {
4!
437
            throw new IllegalStateException("@Resource annotation is not supported on static fields");
×
438
          }
439
          if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
7!
440
            currElements.add(new ResourceElement(field, field, null));
11✔
441
          }
442
        }
443
        else if (javaxResourceType != null && field.isAnnotationPresent(javaxResourceType)) {
6✔
444
          if (Modifier.isStatic(field.getModifiers())) {
4!
445
            throw new IllegalStateException("@Resource annotation is not supported on static fields");
×
446
          }
447
          if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
7!
448
            currElements.add(new LegacyResourceElement(field, field, null));
10✔
449
          }
450
        }
451
      });
1✔
452

453
      ReflectionUtils.doWithLocalMethods(targetClass, method -> {
6✔
454
        if (method.isBridge()) {
3✔
455
          return;
1✔
456
        }
457
        if (ejbAnnotationType != null && method.isAnnotationPresent(ejbAnnotationType)) {
6✔
458
          if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
6!
459
            if (Modifier.isStatic(method.getModifiers())) {
4!
460
              throw new IllegalStateException("@EJB annotation is not supported on static methods");
×
461
            }
462
            if (method.getParameterCount() != 1) {
4!
463
              throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
×
464
            }
465
            PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
4✔
466
            currElements.add(new EjbRefElement(method, method, pd));
10✔
467
          }
1✔
468
        }
469
        else if (jakartaResourceType != null && method.isAnnotationPresent(jakartaResourceType)) {
6!
470
          if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
6✔
471
            if (Modifier.isStatic(method.getModifiers())) {
4!
472
              throw new IllegalStateException("@Resource annotation is not supported on static methods");
×
473
            }
474
            Class<?>[] paramTypes = method.getParameterTypes();
3✔
475
            if (paramTypes.length != 1) {
4!
476
              throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
×
477
            }
478
            if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
8!
479
              PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
4✔
480
              currElements.add(new ResourceElement(method, method, pd));
10✔
481
            }
482
          }
1✔
483
        }
484
        else if (javaxResourceType != null && method.isAnnotationPresent(javaxResourceType)) {
6✔
485
          if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
6!
486
            if (Modifier.isStatic(method.getModifiers())) {
4!
487
              throw new IllegalStateException("@Resource annotation is not supported on static methods");
×
488
            }
489
            Class<?>[] paramTypes = method.getParameterTypes();
3✔
490
            if (paramTypes.length != 1) {
4!
491
              throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
×
492
            }
493
            if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
8!
494
              PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
4✔
495
              currElements.add(new LegacyResourceElement(method, method, pd));
10✔
496
            }
497
          }
498
        }
499
      });
1✔
500

501
      elements.addAll(0, currElements);
5✔
502
      targetClass = targetClass.getSuperclass();
3✔
503
    }
504
    while (targetClass != null && targetClass != Object.class);
5✔
505

506
    return InjectionMetadata.forElements(elements, clazz);
4✔
507
  }
508

509
  /**
510
   * Obtain a lazily resolving resource proxy for the given name and type,
511
   * delegating to {@link #getResource} on demand once a method call comes in.
512
   *
513
   * @param element the descriptor for the annotated field/method
514
   * @param requestingBeanName the name of the requesting bean
515
   * @return the resource object (never {@code null})
516
   * @see #getResource
517
   * @see Lazy
518
   */
519
  protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
520
    TargetSource ts = new TargetSource() {
19✔
521
      @Override
522
      public Class<?> getTargetClass() {
523
        return element.lookupType;
4✔
524
      }
525

526
      @Override
527
      public boolean isStatic() {
528
        return false;
2✔
529
      }
530

531
      @Override
532
      public Object getTarget() {
533
        return getResource(element, requestingBeanName);
8✔
534
      }
535

536
      @Override
537
      public void releaseTarget(Object target) {
538
      }
1✔
539
    };
540

541
    ProxyFactory pf = new ProxyFactory();
4✔
542
    pf.setTargetSource(ts);
3✔
543
    if (element.lookupType.isInterface()) {
4✔
544
      pf.addInterface(element.lookupType);
4✔
545
    }
546
    ClassLoader classLoader = beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory
9!
547
            ? configurableBeanFactory.getBeanClassLoader() : null;
4✔
548
    return pf.getProxy(classLoader);
4✔
549
  }
550

551
  /**
552
   * Obtain the resource object for the given name and type.
553
   *
554
   * @param element the descriptor for the annotated field/method
555
   * @param requestingBeanName the name of the requesting bean
556
   * @return the resource object (never {@code null})
557
   * @throws NoSuchBeanDefinitionException if no corresponding target resource found
558
   */
559
  protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
560
          throws NoSuchBeanDefinitionException {
561

562
    // JNDI lookup to perform?
563
    String jndiName = null;
2✔
564
    if (StringUtils.isNotEmpty(element.mappedName)) {
4!
565
      jndiName = element.mappedName;
×
566
    }
567
    else if (this.alwaysUseJndiLookup) {
3!
568
      jndiName = element.name;
×
569
    }
570
    if (jndiName != null) {
2!
571
      if (this.jndiFactory == null) {
×
572
        throw new NoSuchBeanDefinitionException(element.lookupType,
×
573
                "No JNDI factory configured - specify the 'jndiFactory' property");
574
      }
575
      return this.jndiFactory.getBean(jndiName, element.lookupType);
×
576
    }
577

578
    // Regular resource autowiring
579
    if (this.resourceFactory == null) {
3!
580
      throw new NoSuchBeanDefinitionException(element.lookupType,
×
581
              "No resource factory configured - specify the 'resourceFactory' property");
582
    }
583
    return autowireResource(this.resourceFactory, element, requestingBeanName);
7✔
584
  }
585

586
  /**
587
   * Obtain a resource object for the given name and type through autowiring
588
   * based on the given factory.
589
   *
590
   * @param factory the factory to autowire against
591
   * @param element the descriptor for the annotated field/method
592
   * @param requestingBeanName the name of the requesting bean
593
   * @return the resource object (never {@code null})
594
   * @throws NoSuchBeanDefinitionException if no corresponding target resource found
595
   */
596
  protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
597
          throws NoSuchBeanDefinitionException {
598

599
    Object resource;
600
    Set<String> autowiredBeanNames;
601
    String name = element.name;
3✔
602

603
    if (factory instanceof AutowireCapableBeanFactory autowireCapableBeanFactory) {
6✔
604
      DependencyDescriptor descriptor = element.getDependencyDescriptor();
3✔
605
      if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
10!
606
        autowiredBeanNames = new LinkedHashSet<>();
4✔
607
        resource = autowireCapableBeanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
7✔
608
        if (resource == null) {
2!
609
          throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
×
610
        }
611
      }
612
      else {
613
        resource = autowireCapableBeanFactory.resolveBeanByName(name, descriptor);
5✔
614
        autowiredBeanNames = Collections.singleton(name);
3✔
615
      }
616
    }
1✔
617
    else {
618
      resource = factory.getBean(name, element.lookupType);
6✔
619
      autowiredBeanNames = Collections.singleton(name);
3✔
620
    }
621

622
    if (factory instanceof ConfigurableBeanFactory configurableBeanFactory) {
6✔
623
      for (String autowiredBeanName : autowiredBeanNames) {
10✔
624
        if (requestingBeanName != null && configurableBeanFactory.containsBean(autowiredBeanName)) {
6✔
625
          configurableBeanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
4✔
626
        }
627
      }
1✔
628
    }
629

630
    return resource;
2✔
631
  }
632

633
  @Nullable
634
  private static Class<? extends Annotation> loadAnnotationType(String name) {
635
    return ClassUtils.load(name, CommonAnnotationBeanPostProcessor.class.getClassLoader());
5✔
636
  }
637

638
  /**
639
   * Class representing generic injection information about an annotated field
640
   * or setter method, supporting @Resource and related annotations.
641
   */
642
  protected abstract static class LookupElement extends InjectionMetadata.InjectedElement {
643

644
    protected String name = "";
3✔
645

646
    protected boolean isDefaultName = false;
3✔
647

648
    protected Class<?> lookupType = Object.class;
3✔
649

650
    @Nullable
651
    protected String mappedName;
652

653
    public LookupElement(Member member, @Nullable PropertyDescriptor pd) {
654
      super(member, pd);
4✔
655
    }
1✔
656

657
    /**
658
     * Return the resource name for the lookup.
659
     */
660
    public final String getName() {
661
      return this.name;
3✔
662
    }
663

664
    /**
665
     * Return the desired type for the lookup.
666
     */
667
    public final Class<?> getLookupType() {
668
      return this.lookupType;
3✔
669
    }
670

671
    /**
672
     * Build a DependencyDescriptor for the underlying field/method.
673
     */
674
    public final DependencyDescriptor getDependencyDescriptor() {
675
      if (member instanceof Field field) {
9✔
676
        return new LookupDependencyDescriptor(field, lookupType, isLazyLookup());
9✔
677
      }
678
      else {
679
        return new LookupDependencyDescriptor((Method) member, lookupType, isLazyLookup());
11✔
680
      }
681
    }
682

683
    /**
684
     * Determine whether this dependency is marked for lazy lookup.
685
     * The default is {@code false}.
686
     */
687
    boolean isLazyLookup() {
688
      return false;
2✔
689
    }
690
  }
691

692
  /**
693
   * Class representing injection information about an annotated field
694
   * or setter method, supporting the @Resource annotation.
695
   */
696
  private class ResourceElement extends LookupElement {
697

698
    private final boolean lazyLookup;
699

700
    public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
3✔
701
      super(member, pd);
4✔
702
      Resource resource = ae.getAnnotation(Resource.class);
5✔
703
      String resourceName = resource.name();
3✔
704
      Class<?> resourceType = resource.type();
3✔
705
      this.isDefaultName = StringUtils.isEmpty(resourceName);
4✔
706
      if (this.isDefaultName) {
3✔
707
        resourceName = this.member.getName();
4✔
708
        if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
12!
709
          resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
6✔
710
        }
711
      }
712
      else if (embeddedValueResolver != null) {
3!
713
        resourceName = embeddedValueResolver.resolveStringValue(resourceName);
5✔
714
      }
715
      if (Object.class != resourceType) {
3✔
716
        checkResourceType(resourceType);
4✔
717
      }
718
      else {
719
        // No resource type specified... check field/method.
720
        resourceType = getResourceType();
3✔
721
      }
722
      this.name = (resourceName != null ? resourceName : "");
6!
723
      this.lookupType = resourceType;
3✔
724
      String lookupValue = resource.lookup();
3✔
725
      this.mappedName = StringUtils.isNotEmpty(lookupValue) ? lookupValue : resource.mappedName();
7!
726
      Lazy lazy = ae.getAnnotation(Lazy.class);
5✔
727
      this.lazyLookup = (lazy != null && lazy.value());
10!
728
    }
1✔
729

730
    @Override
731
    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
732
      return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
10✔
733
              getResource(this, requestingBeanName));
5✔
734
    }
735

736
    @Override
737
    boolean isLazyLookup() {
738
      return this.lazyLookup;
3✔
739
    }
740

741
  }
742

743
  /**
744
   * Class representing injection information about an annotated field
745
   * or setter method, supporting the @Resource annotation.
746
   */
747
  private class LegacyResourceElement extends LookupElement {
748

749
    private final boolean lazyLookup;
750

751
    public LegacyResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
3✔
752
      super(member, pd);
4✔
753
      javax.annotation.Resource resource = ae.getAnnotation(javax.annotation.Resource.class);
5✔
754
      String resourceName = resource.name();
3✔
755
      Class<?> resourceType = resource.type();
3✔
756
      this.isDefaultName = StringUtils.isEmpty(resourceName);
4✔
757
      if (this.isDefaultName) {
3!
758
        resourceName = this.member.getName();
4✔
759
        if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
12!
760
          resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
6✔
761
        }
762
      }
763
      else if (embeddedValueResolver != null) {
×
764
        resourceName = embeddedValueResolver.resolveStringValue(resourceName);
×
765
      }
766
      if (Object.class != resourceType) {
3!
767
        checkResourceType(resourceType);
×
768
      }
769
      else {
770
        // No resource type specified... check field/method.
771
        resourceType = getResourceType();
3✔
772
      }
773
      this.name = (resourceName != null ? resourceName : "");
6!
774
      this.lookupType = resourceType;
3✔
775
      String lookupValue = resource.lookup();
3✔
776
      this.mappedName = StringUtils.isNotEmpty(lookupValue) ? lookupValue : resource.mappedName();
7!
777
      Lazy lazy = ae.getAnnotation(Lazy.class);
5✔
778
      this.lazyLookup = (lazy != null && lazy.value());
5!
779
    }
1✔
780

781
    @Override
782
    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
783
      return lazyLookup
4!
784
              ? buildLazyResourceProxy(this, requestingBeanName)
×
785
              : getResource(this, requestingBeanName);
5✔
786
    }
787

788
    @Override
789
    boolean isLazyLookup() {
790
      return this.lazyLookup;
3✔
791
    }
792

793
  }
794

795
  /**
796
   * Class representing injection information about an annotated field
797
   * or setter method, supporting the @EJB annotation.
798
   */
799
  private class EjbRefElement extends LookupElement {
800

801
    private final String beanName;
802

803
    public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
3✔
804
      super(member, pd);
4✔
805
      EJB resource = ae.getAnnotation(EJB.class);
5✔
806
      String resourceBeanName = resource.beanName();
3✔
807
      String resourceName = resource.name();
3✔
808
      this.isDefaultName = StringUtils.isEmpty(resourceName);
4✔
809
      if (this.isDefaultName) {
3✔
810
        resourceName = this.member.getName();
4✔
811
        if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
12!
812
          resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
5✔
813
        }
814
      }
815
      Class<?> resourceType = resource.beanInterface();
3✔
816
      if (Object.class != resourceType) {
3✔
817
        checkResourceType(resourceType);
4✔
818
      }
819
      else {
820
        // No resource type specified... check field/method.
821
        resourceType = getResourceType();
3✔
822
      }
823
      this.beanName = resourceBeanName;
3✔
824
      this.name = resourceName;
3✔
825
      this.lookupType = resourceType;
3✔
826
      this.mappedName = resource.mappedName();
4✔
827
    }
1✔
828

829
    @Override
830
    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
831
      if (StringUtils.isNotEmpty(this.beanName)) {
4✔
832
        if (beanFactory != null && beanFactory.containsBean(this.beanName)) {
11!
833
          // Local match found for explicitly specified local bean name.
834
          Object bean = beanFactory.getBean(this.beanName, this.lookupType);
9✔
835
          if (requestingBeanName != null && beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
12!
836
            configurableBeanFactory.registerDependentBean(this.beanName, requestingBeanName);
5✔
837
          }
838
          return bean;
2✔
839
        }
840
        else if (this.isDefaultName && StringUtils.isEmpty(this.mappedName)) {
×
841
          throw new NoSuchBeanDefinitionException(this.beanName,
×
842
                  "Cannot resolve 'beanName' in local BeanFactory. Consider specifying a general 'name' value instead.");
843
        }
844
      }
845
      // JNDI name lookup - may still go to a local BeanFactory.
846
      return getResource(this, requestingBeanName);
6✔
847
    }
848
  }
849

850
  /**
851
   * Extension of the DependencyDescriptor class,
852
   * overriding the dependency type with the specified resource type.
853
   */
854
  private static class LookupDependencyDescriptor extends DependencyDescriptor {
855

856
    @Serial
857
    private static final long serialVersionUID = 1L;
858

859
    private final Class<?> lookupType;
860

861
    private final boolean lazyLookup;
862

863
    public LookupDependencyDescriptor(Field field, Class<?> lookupType, boolean lazyLookup) {
864
      super(field, true);
4✔
865
      this.lookupType = lookupType;
3✔
866
      this.lazyLookup = lazyLookup;
3✔
867
    }
1✔
868

869
    public LookupDependencyDescriptor(Method method, Class<?> lookupType, boolean lazyLookup) {
870
      super(new MethodParameter(method, 0), true);
8✔
871
      this.lookupType = lookupType;
3✔
872
      this.lazyLookup = lazyLookup;
3✔
873
    }
1✔
874

875
    @Override
876
    public Class<?> getDependencyType() {
877
      return this.lookupType;
3✔
878
    }
879

880
    @Override
881
    public boolean supportsLazyResolution() {
882
      return !lazyLookup;
7✔
883
    }
884

885
  }
886

887
  /**
888
   * {@link BeanRegistrationAotContribution} to inject resources on fields and methods.
889
   */
890
  private static class AotContribution implements BeanRegistrationAotContribution {
891

892
    private static final String REGISTERED_BEAN_PARAMETER = "registeredBean";
893

894
    private static final String INSTANCE_PARAMETER = "instance";
895

896
    private final Class<?> target;
897

898
    private final Collection<LookupElement> lookupElements;
899

900
    @Nullable
901
    private final AutowireCandidateResolver candidateResolver;
902

903
    AotContribution(Class<?> target, Collection<LookupElement> lookupElements,
904
            @Nullable AutowireCandidateResolver candidateResolver) {
2✔
905

906
      this.target = target;
3✔
907
      this.lookupElements = lookupElements;
3✔
908
      this.candidateResolver = candidateResolver;
3✔
909
    }
1✔
910

911
    @Override
912
    public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
913
      GeneratedClass generatedClass = generationContext.getGeneratedClasses()
7✔
914
              .addForFeatureComponent("ResourceAutowiring", this.target, type -> {
2✔
915
                type.addJavadoc("Resource autowiring for {@link $T}.", this.target);
11✔
916
                type.addModifiers(javax.lang.model.element.Modifier.PUBLIC);
9✔
917
              });
1✔
918
      GeneratedMethod generateMethod = generatedClass.getMethods().add("apply", method -> {
9✔
919
        method.addJavadoc("Apply resource autowiring.");
6✔
920
        method.addModifiers(javax.lang.model.element.Modifier.PUBLIC,
13✔
921
                javax.lang.model.element.Modifier.STATIC);
922
        method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
7✔
923
        method.addParameter(this.target, INSTANCE_PARAMETER);
8✔
924
        method.returns(this.target);
5✔
925
        method.addCode(generateMethodCode(generatedClass.getName(),
8✔
926
                generationContext.getRuntimeHints()));
1✔
927
      });
1✔
928
      beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
4✔
929

930
      registerHints(generationContext.getRuntimeHints());
4✔
931
    }
1✔
932

933
    private CodeBlock generateMethodCode(ClassName targetClassName, RuntimeHints hints) {
934
      CodeBlock.Builder code = CodeBlock.builder();
2✔
935
      for (LookupElement lookupElement : this.lookupElements) {
11✔
936
        code.addStatement(generateMethodStatementForElement(
8✔
937
                targetClassName, lookupElement, hints));
938
      }
1✔
939
      code.addStatement("return $L", INSTANCE_PARAMETER);
10✔
940
      return code.build();
3✔
941
    }
942

943
    private CodeBlock generateMethodStatementForElement(ClassName targetClassName,
944
            LookupElement lookupElement, RuntimeHints hints) {
945

946
      Member member = lookupElement.getMember();
3✔
947
      if (member instanceof Field field) {
6✔
948
        return generateMethodStatementForField(
7✔
949
                targetClassName, field, lookupElement, hints);
950
      }
951
      if (member instanceof Method method) {
6!
952
        return generateMethodStatementForMethod(
7✔
953
                targetClassName, method, lookupElement, hints);
954
      }
955
      throw new IllegalStateException(
×
956
              "Unsupported member type " + member.getClass().getName());
×
957
    }
958

959
    private CodeBlock generateMethodStatementForField(ClassName targetClassName,
960
            Field field, LookupElement lookupElement, RuntimeHints hints) {
961

962
      hints.reflection().registerField(field);
5✔
963
      CodeBlock resolver = generateFieldResolverCode(field, lookupElement);
5✔
964
      AccessControl accessControl = AccessControl.forMember(field);
3✔
965
      if (!accessControl.isAccessibleFrom(targetClassName)) {
4!
966
        return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
17✔
967
                REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
968
      }
969
      return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
×
970
              field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
×
971
    }
972

973
    private CodeBlock generateFieldResolverCode(Field field, LookupElement lookupElement) {
974
      if (lookupElement.isDefaultName) {
3!
975
        return CodeBlock.of("$T.$L($S)", ResourceElementResolver.class,
16✔
976
                "forField", field.getName());
2✔
977
      }
978
      else {
979
        return CodeBlock.of("$T.$L($S, $S)", ResourceElementResolver.class,
×
980
                "forField", field.getName(), lookupElement.getName());
×
981
      }
982
    }
983

984
    private CodeBlock generateMethodStatementForMethod(ClassName targetClassName,
985
            Method method, LookupElement lookupElement, RuntimeHints hints) {
986

987
      CodeBlock resolver = generateMethodResolverCode(method, lookupElement);
5✔
988
      AccessControl accessControl = AccessControl.forMember(method);
3✔
989
      if (!accessControl.isAccessibleFrom(targetClassName)) {
4✔
990
        hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
6✔
991
        return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
17✔
992
                REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
993
      }
994
      hints.reflection().registerType(method.getDeclaringClass());
8✔
995
      return CodeBlock.of("$L.$L($L.resolve($L))", INSTANCE_PARAMETER,
12✔
996
              method.getName(), resolver, REGISTERED_BEAN_PARAMETER);
10✔
997

998
    }
999

1000
    private CodeBlock generateMethodResolverCode(Method method, LookupElement lookupElement) {
1001
      if (lookupElement.isDefaultName) {
3✔
1002
        return CodeBlock.of("$T.$L($S, $T.class)", ResourceElementResolver.class,
16✔
1003
                "forMethod", method.getName(), lookupElement.getLookupType());
7✔
1004
      }
1005
      else {
1006
        return CodeBlock.of("$T.$L($S, $T.class, $S)", ResourceElementResolver.class,
16✔
1007
                "forMethod", method.getName(), lookupElement.getLookupType(), lookupElement.getName());
12✔
1008
      }
1009
    }
1010

1011
    private void registerHints(RuntimeHints runtimeHints) {
1012
      this.lookupElements.forEach(lookupElement ->
6✔
1013
              registerProxyIfNecessary(runtimeHints, lookupElement.getDependencyDescriptor()));
6✔
1014
    }
1✔
1015

1016
    private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
1017
      if (this.candidateResolver != null) {
3!
1018
        Class<?> proxyClass =
4✔
1019
                this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null);
2✔
1020
        if (proxyClass != null) {
2✔
1021
          ClassHintUtils.registerProxyIfNecessary(proxyClass, runtimeHints);
3✔
1022
        }
1023
      }
1024
    }
1✔
1025
  }
1026

1027
}
1028

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