All Downloads are FREE. Search and download functionalities are using the official Maven repository.

keycloakjar.org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor Maven / Gradle / Ivy

There is a newer version: 7.21.1
Show newest version
/*
 * 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.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.annotation;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

/**
 * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
 * that invokes annotated init and destroy methods. Allows for an annotation
 * alternative to Spring's {@link org.springframework.beans.factory.InitializingBean}
 * and {@link org.springframework.beans.factory.DisposableBean} callback interfaces.
 *
 * 

The actual annotation types that this post-processor checks for can be * configured through the {@link #setInitAnnotationType "initAnnotationType"} * and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties. * Any custom annotation can be used, since there are no required annotation * attributes. * *

Init and destroy annotations may be applied to methods of any visibility: * public, package-protected, protected, or private. Multiple such methods * may be annotated, but it is recommended to only annotate one single * init method and destroy method, respectively. * *

Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} * supports the {@link jakarta.annotation.PostConstruct} and {@link jakarta.annotation.PreDestroy} * annotations out of the box, as init annotation and destroy annotation, respectively. * Furthermore, it also supports the {@link jakarta.annotation.Resource} annotation * for annotation-driven injection of named beans. * * @author Juergen Hoeller * @author Stephane Nicoll * @author Phillip Webb * @author Sam Brannen * @since 2.5 * @see #setInitAnnotationType * @see #setDestroyAnnotationType */ @SuppressWarnings("serial") public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, Serializable { private final transient LifecycleMetadata emptyLifecycleMetadata = new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) { @Override public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) { } @Override public void invokeInitMethods(Object target, String beanName) { } @Override public void invokeDestroyMethods(Object target, String beanName) { } @Override public boolean hasDestroyMethods() { return false; } }; protected transient Log logger = LogFactory.getLog(getClass()); private final Set> initAnnotationTypes = new LinkedHashSet<>(2); private final Set> destroyAnnotationTypes = new LinkedHashSet<>(2); private int order = Ordered.LOWEST_PRECEDENCE; @Nullable private final transient Map, LifecycleMetadata> lifecycleMetadataCache = new ConcurrentHashMap<>(256); /** * Specify the init annotation to check for, indicating initialization * methods to call after configuration of a bean. *

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 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 initAnnotationType) { if (initAnnotationType != null) { this.initAnnotationTypes.add(initAnnotationType); } } /** * Specify the destroy annotation to check for, indicating destruction * methods to call when the context is shutting down. *

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 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 destroyAnnotationType) { if (destroyAnnotationType != null) { this.destroyAnnotationTypes.add(destroyAnnotationType); } } public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.order; } @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanClass, String beanName) { findLifecycleMetadata(beanDefinition, beanClass); } @Override @Nullable public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); beanDefinition.resolveDestroyMethodIfNecessary(); LifecycleMetadata metadata = findLifecycleMetadata(beanDefinition, registeredBean.getBeanClass()); if (!CollectionUtils.isEmpty(metadata.initMethods)) { String[] initMethodNames = safeMerge(beanDefinition.getInitMethodNames(), metadata.initMethods); beanDefinition.setInitMethodNames(initMethodNames); } if (!CollectionUtils.isEmpty(metadata.destroyMethods)) { String[] destroyMethodNames = safeMerge(beanDefinition.getDestroyMethodNames(), metadata.destroyMethods); beanDefinition.setDestroyMethodNames(destroyMethodNames); } return null; } private LifecycleMetadata findLifecycleMetadata(RootBeanDefinition beanDefinition, Class beanClass) { LifecycleMetadata metadata = findLifecycleMetadata(beanClass); metadata.checkInitDestroyMethods(beanDefinition); return metadata; } private static String[] safeMerge(@Nullable String[] existingNames, Collection detectedMethods) { Stream detectedNames = detectedMethods.stream().map(LifecycleMethod::getIdentifier); Stream mergedNames = (existingNames != null ? Stream.concat(detectedNames, Stream.of(existingNames)) : detectedNames); return mergedNames.distinct().toArray(String[]::new); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeDestroyMethods(bean, beanName); } catch (InvocationTargetException ex) { String msg = "Destroy method on bean with name '" + beanName + "' threw an exception"; if (logger.isDebugEnabled()) { logger.warn(msg, ex.getTargetException()); } else if (logger.isWarnEnabled()) { logger.warn(msg + ": " + ex.getTargetException()); } } catch (Throwable ex) { if (logger.isWarnEnabled()) { logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex); } } } @Override public boolean requiresDestruction(Object bean) { return findLifecycleMetadata(bean.getClass()).hasDestroyMethods(); } private LifecycleMetadata findLifecycleMetadata(Class beanClass) { if (this.lifecycleMetadataCache == null) { // Happens after deserialization, during destruction... return buildLifecycleMetadata(beanClass); } // Quick check on the concurrent map first, with minimal locking. LifecycleMetadata metadata = this.lifecycleMetadataCache.get(beanClass); if (metadata == null) { synchronized (this.lifecycleMetadataCache) { metadata = this.lifecycleMetadataCache.get(beanClass); if (metadata == null) { metadata = buildLifecycleMetadata(beanClass); this.lifecycleMetadataCache.put(beanClass, metadata); } return metadata; } } return metadata; } private LifecycleMetadata buildLifecycleMetadata(final Class beanClass) { if (!AnnotationUtils.isCandidateClass(beanClass, this.initAnnotationTypes) && !AnnotationUtils.isCandidateClass(beanClass, this.destroyAnnotationTypes)) { return this.emptyLifecycleMetadata; } List initMethods = new ArrayList<>(); List destroyMethods = new ArrayList<>(); Class currentClass = beanClass; do { final List currInitMethods = new ArrayList<>(); final List currDestroyMethods = new ArrayList<>(); ReflectionUtils.doWithLocalMethods(currentClass, method -> { for (Class 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); } } } for (Class 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); } } } }); initMethods.addAll(0, currInitMethods); destroyMethods.addAll(currDestroyMethods); currentClass = currentClass.getSuperclass(); } while (currentClass != null && currentClass != Object.class); return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata : new LifecycleMetadata(beanClass, initMethods, destroyMethods)); } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Initialize transient fields. this.logger = LogFactory.getLog(getClass()); } /** * Class representing information about annotated init and destroy methods. */ private class LifecycleMetadata { private final Class beanClass; private final Collection initMethods; private final Collection destroyMethods; @Nullable private volatile Set checkedInitMethods; @Nullable private volatile Set checkedDestroyMethods; public LifecycleMetadata(Class beanClass, Collection initMethods, Collection destroyMethods) { this.beanClass = beanClass; this.initMethods = initMethods; this.destroyMethods = destroyMethods; } public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) { Set checkedInitMethods = new LinkedHashSet<>(this.initMethods.size()); for (LifecycleMethod lifecycleMethod : this.initMethods) { String methodIdentifier = lifecycleMethod.getIdentifier(); if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) { beanDefinition.registerExternallyManagedInitMethod(methodIdentifier); checkedInitMethods.add(lifecycleMethod); if (logger.isTraceEnabled()) { logger.trace("Registered init method on class [" + this.beanClass.getName() + "]: " + methodIdentifier); } } } Set checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size()); for (LifecycleMethod lifecycleMethod : this.destroyMethods) { String methodIdentifier = lifecycleMethod.getIdentifier(); if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) { beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier); checkedDestroyMethods.add(lifecycleMethod); if (logger.isTraceEnabled()) { logger.trace("Registered destroy method on class [" + this.beanClass.getName() + "]: " + methodIdentifier); } } } this.checkedInitMethods = checkedInitMethods; this.checkedDestroyMethods = checkedDestroyMethods; } public void invokeInitMethods(Object target, String beanName) throws Throwable { Collection checkedInitMethods = this.checkedInitMethods; Collection initMethodsToIterate = (checkedInitMethods != null ? checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { for (LifecycleMethod lifecycleMethod : initMethodsToIterate) { if (logger.isTraceEnabled()) { logger.trace("Invoking init method on bean '" + beanName + "': " + lifecycleMethod.getMethod()); } lifecycleMethod.invoke(target); } } } public void invokeDestroyMethods(Object target, String beanName) throws Throwable { Collection checkedDestroyMethods = this.checkedDestroyMethods; Collection destroyMethodsToUse = (checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods); if (!destroyMethodsToUse.isEmpty()) { for (LifecycleMethod lifecycleMethod : destroyMethodsToUse) { if (logger.isTraceEnabled()) { logger.trace("Invoking destroy method on bean '" + beanName + "': " + lifecycleMethod.getMethod()); } lifecycleMethod.invoke(target); } } } public boolean hasDestroyMethods() { Collection checkedDestroyMethods = this.checkedDestroyMethods; Collection destroyMethodsToUse = (checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods); return !destroyMethodsToUse.isEmpty(); } } /** * Class representing an annotated init or destroy method. */ private static class LifecycleMethod { private final Method method; private final String identifier; public LifecycleMethod(Method method, Class beanClass) { if (method.getParameterCount() != 0) { throw new IllegalStateException("Lifecycle annotation requires a no-arg method: " + method); } this.method = method; this.identifier = (isPrivateOrNotVisible(method, beanClass) ? ClassUtils.getQualifiedMethodName(method) : method.getName()); } public Method getMethod() { return this.method; } public String getIdentifier() { return this.identifier; } public void invoke(Object target) throws Throwable { ReflectionUtils.makeAccessible(this.method); this.method.invoke(target); } @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof LifecycleMethod that && this.identifier.equals(that.identifier))); } @Override public int hashCode() { return this.identifier.hashCode(); } /** * Determine if the supplied lifecycle {@link Method} is private or not * visible to the supplied bean {@link Class}. * @since 6.0.11 */ private static boolean isPrivateOrNotVisible(Method method, Class beanClass) { int modifiers = method.getModifiers(); if (Modifier.isPrivate(modifiers)) { return true; } // Method is declared in a class that resides in a different package // than the bean class and the method is neither public nor protected? return (!method.getDeclaringClass().getPackageName().equals(beanClass.getPackageName()) && !(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy