• 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

631
    return resource;
2✔
632
  }
633

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

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

645
    protected String name = "";
3✔
646

647
    protected boolean isDefaultName = false;
3✔
648

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

651
    @Nullable
652
    protected String mappedName;
653

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

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

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

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

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

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

699
    private final boolean lazyLookup;
700

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

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

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

742
  }
743

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

750
    private final boolean lazyLookup;
751

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

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

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

794
  }
795

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

802
    private final String beanName;
803

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

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

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

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

860
    private final Class<?> lookupType;
861

862
    private final boolean lazyLookup;
863

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

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

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

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

886
  }
887

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

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

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

897
    private final Class<?> target;
898

899
    private final Collection<LookupElement> lookupElements;
900

901
    @Nullable
902
    private final AutowireCandidateResolver candidateResolver;
903

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

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

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

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

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

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

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

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

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

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

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

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

999
    }
1000

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

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

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

1028
}
1029

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