Skip to content

Commit

Permalink
Reinstate support for legacy JSR-250 Resource annotation
Browse files Browse the repository at this point in the history
This merges the existing support for the legacy JSR-250 PostConstruct/PreDestroy annotations into CommonAnnotationBeanPostProcessor itself, opening up the InitDestroyAnnotationBeanPostProcessor base class for multiple init/destroy methods in a single post-processor. This removes the need for a separate JSR-250 InitDestroyAnnotationBeanPostProcessor in AnnotationConfigUtils.

Closes gh-30695
  • Loading branch information
jhoeller committed Jul 9, 2023
1 parent b32b4f3 commit d03b6aa
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 69 deletions.
Expand Up @@ -107,11 +107,9 @@ public boolean hasDestroyMethods() {

protected transient Log logger = LogFactory.getLog(getClass());

@Nullable
private Class<? extends Annotation> initAnnotationType;
private final Set<Class<? extends Annotation>> initAnnotationTypes = new LinkedHashSet<>(2);

@Nullable
private Class<? extends Annotation> destroyAnnotationType;
private final Set<Class<? extends Annotation>> destroyAnnotationTypes = new LinkedHashSet<>(2);

private int order = Ordered.LOWEST_PRECEDENCE;

Expand All @@ -125,9 +123,23 @@ public boolean hasDestroyMethods() {
* <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PostConstruct} annotation.
* @see #addInitAnnotationType
*/
public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
this.initAnnotationType = initAnnotationType;
this.initAnnotationTypes.clear();
this.initAnnotationTypes.add(initAnnotationType);
}

/**
* Add an init annotation to check for, indicating initialization
* methods to call after configuration of a bean.
* @since 6.0.11
* @see #setInitAnnotationType
*/
public void addInitAnnotationType(@Nullable Class<? extends Annotation> initAnnotationType) {
if (initAnnotationType != null) {
this.initAnnotationTypes.add(initAnnotationType);
}
}

/**
Expand All @@ -136,9 +148,23 @@ public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType
* <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PreDestroy} annotation.
* @see #addDestroyAnnotationType
*/
public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
this.destroyAnnotationType = destroyAnnotationType;
this.destroyAnnotationTypes.clear();
this.destroyAnnotationTypes.add(destroyAnnotationType);
}

/**
* Add a destroy annotation to check for, indicating destruction
* methods to call when the context is shutting down.
* @since 6.0.11
* @see #setDestroyAnnotationType
*/
public void addDestroyAnnotationType(@Nullable Class<? extends Annotation> destroyAnnotationType) {
if (destroyAnnotationType != null) {
this.destroyAnnotationTypes.add(destroyAnnotationType);
}
}

public void setOrder(int order) {
Expand Down Expand Up @@ -255,7 +281,8 @@ private LifecycleMetadata findLifecycleMetadata(Class<?> beanClass) {
}

private LifecycleMetadata buildLifecycleMetadata(final Class<?> beanClass) {
if (!AnnotationUtils.isCandidateClass(beanClass, List.of(this.initAnnotationType, this.destroyAnnotationType))) {
if (!AnnotationUtils.isCandidateClass(beanClass, this.initAnnotationTypes) &&
!AnnotationUtils.isCandidateClass(beanClass, this.destroyAnnotationTypes)) {
return this.emptyLifecycleMetadata;
}

Expand All @@ -268,16 +295,20 @@ private LifecycleMetadata buildLifecycleMetadata(final Class<?> beanClass) {
final List<LifecycleMethod> currDestroyMethods = new ArrayList<>();

ReflectionUtils.doWithLocalMethods(currentClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
currInitMethods.add(new LifecycleMethod(method, beanClass));
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method);
for (Class<? extends Annotation> initAnnotationType : this.initAnnotationTypes) {
if (initAnnotationType != null && method.isAnnotationPresent(initAnnotationType)) {
currInitMethods.add(new LifecycleMethod(method, beanClass));
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method);
}
}
}
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleMethod(method, beanClass));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method);
for (Class<? extends Annotation> destroyAnnotationType : this.destroyAnnotationTypes) {
if (destroyAnnotationType != null && method.isAnnotationPresent(destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleMethod(method, beanClass));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method);
}
}
}
});
Expand Down
4 changes: 2 additions & 2 deletions spring-context/spring-context.gradle
Expand Up @@ -18,6 +18,7 @@ dependencies {
optional("jakarta.inject:jakarta.inject-api")
optional("jakarta.interceptor:jakarta.interceptor-api")
optional("jakarta.validation:jakarta.validation-api")
optional("javax.annotation:javax.annotation-api")
optional("javax.money:money-api")
optional("org.aspectj:aspectjweaver")
optional("org.apache.groovy:groovy")
Expand All @@ -31,12 +32,11 @@ dependencies {
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
testImplementation("io.projectreactor:reactor-core")
testImplementation("jakarta.inject:jakarta.inject-tck")
testImplementation("org.apache.groovy:groovy-jsr223")
testImplementation("org.apache.groovy:groovy-xml")
testImplementation("org.apache.commons:commons-pool2")
testImplementation("org.awaitility:awaitility")
testImplementation("jakarta.inject:jakarta.inject-tck")
testImplementation("javax.annotation:javax.annotation-api")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
testRuntimeOnly("jakarta.xml.bind:jakarta.xml.bind-api")
testRuntimeOnly("org.glassfish:jakarta.el")
Expand Down
Expand Up @@ -23,7 +23,6 @@

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
Expand Down Expand Up @@ -174,27 +173,13 @@ public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
}

// Check for Jakarta Annotations support, and if present add the CommonAnnotationBeanPostProcessor.
if (jakartaAnnotationsPresent && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
if ((jakartaAnnotationsPresent || jsr250Present) &&
!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}

// Check for JSR-250 support, and if present add an InitDestroyAnnotationBeanPostProcessor
// for the javax variant of PostConstruct/PreDestroy.
if (jsr250Present && !registry.containsBeanDefinition(JSR250_ANNOTATION_PROCESSOR_BEAN_NAME)) {
try {
RootBeanDefinition def = new RootBeanDefinition(InitDestroyAnnotationBeanPostProcessor.class);
def.getPropertyValues().add("initAnnotationType", classLoader.loadClass("javax.annotation.PostConstruct"));
def.getPropertyValues().add("destroyAnnotationType", classLoader.loadClass("javax.annotation.PreDestroy"));
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, JSR250_ANNOTATION_PROCESSOR_BEAN_NAME));
}
catch (ClassNotFoundException ex) {
// Failed to load javax variants of the annotation types -> ignore.
}
}

// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,11 +33,6 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import jakarta.ejb.EJB;

import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeanUtils;
Expand Down Expand Up @@ -85,10 +80,15 @@
* and default names as well. The target beans can be simple POJOs, with no special
* requirements other than the type having to match.
*
* <p>This post-processor also supports the EJB 3 {@link jakarta.ejb.EJB} annotation,
* <p>Additionally, the original {@code javax.annotation} variants of the annotations
* dating back to the JSR-250 specification (Java EE 5-8, also included in JDK 6-8)
* are still supported as well. Note that this is primarily for a smooth upgrade path,
* not for adoption in new applications.
*
* <p>This post-processor also supports the EJB {@link jakarta.ejb.EJB} annotation,
* analogous to {@link jakarta.annotation.Resource}, with the capability to
* specify both a local bean name and a global JNDI name for fallback retrieval.
* The target beans can be plain POJOs as well as EJB 3 Session Beans in this case.
* The target beans can be plain POJOs as well as EJB Session Beans in this case.
*
* <p>For default usage, resolving resource names as Spring bean names,
* simply define the following in your application context:
Expand All @@ -113,8 +113,8 @@
* by the "context:annotation-config" and "context:component-scan" XML tags.
* Remove or turn off the default annotation configuration there if you intend
* to specify a custom CommonAnnotationBeanPostProcessor bean definition!
* <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus
* the latter configuration will override the former for properties wired through
* <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection;
* thus the latter configuration will override the former for properties wired through
* both approaches.
*
* @author Juergen Hoeller
Expand All @@ -136,14 +136,28 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4);

@Nullable
private static final Class<? extends Annotation> ejbClass;
private static final Class<? extends Annotation> jakartaResourceType;

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

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

static {
resourceAnnotationTypes.add(Resource.class);
jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
if (jakartaResourceType != null) {
resourceAnnotationTypes.add(jakartaResourceType);
}

ejbClass = loadAnnotationType("jakarta.ejb.EJB");
if (ejbClass != null) {
resourceAnnotationTypes.add(ejbClass);
javaxResourceType = loadAnnotationType("javax.annotation.Resource");
if (javaxResourceType != null) {
resourceAnnotationTypes.add(javaxResourceType);
}

ejbAnnotationType = loadAnnotationType("jakarta.ejb.EJB");
if (ejbAnnotationType != null) {
resourceAnnotationTypes.add(ejbAnnotationType);
}
}

Expand Down Expand Up @@ -177,8 +191,14 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
*/
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);

// Jakarta EE 9 set of annotations in jakarta.annotation package
addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));

// Tolerate legacy JSR-250 annotations in javax.annotation package
addInitAnnotationType(loadAnnotationType("javax.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("javax.annotation.PreDestroy"));

// java.naming module present on JDK 9+?
if (jndiPresent) {
Expand Down Expand Up @@ -338,20 +358,28 @@ private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

ReflectionUtils.doWithLocalFields(targetClass, field -> {
if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
if (ejbAnnotationType != null && field.isAnnotationPresent(ejbAnnotationType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static fields");
}
currElements.add(new EjbRefElement(field, field, null));
}
else if (field.isAnnotationPresent(Resource.class)) {
else if (jakartaResourceType != null && field.isAnnotationPresent(jakartaResourceType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new ResourceElement(field, field, null));
}
}
else if (javaxResourceType != null && field.isAnnotationPresent(javaxResourceType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new LegacyResourceElement(field, field, null));
}
}
});

ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Expand All @@ -360,7 +388,7 @@ else if (field.isAnnotationPresent(Resource.class)) {
return;
}
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
Expand All @@ -370,7 +398,7 @@ else if (field.isAnnotationPresent(Resource.class)) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
}
else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
Expand All @@ -383,6 +411,19 @@ else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1) {
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
}
}
}
});

Expand Down Expand Up @@ -584,7 +625,55 @@ private class ResourceElement extends LookupElement {

public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
jakarta.annotation.Resource resource = ae.getAnnotation(jakarta.annotation.Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (Object.class != resourceType) {
checkResourceType(resourceType);
}
else {
// No resource type specified... check field/method.
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}

@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
}




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

private final boolean lazyLookup;

public LegacyResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
javax.annotation.Resource resource = ae.getAnnotation(javax.annotation.Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
Expand Down Expand Up @@ -630,7 +719,7 @@ private class EjbRefElement extends LookupElement {

public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
EJB resource = ae.getAnnotation(EJB.class);
jakarta.ejb.EJB resource = ae.getAnnotation(jakarta.ejb.EJB.class);
String resourceBeanName = resource.beanName();
String resourceName = resource.name();
this.isDefaultName = !StringUtils.hasLength(resourceName);
Expand Down

0 comments on commit d03b6aa

Please sign in to comment.