001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans.factory.annotation; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.Serializable; 022import java.lang.annotation.Annotation; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.lang.reflect.Modifier; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.concurrent.ConcurrentHashMap; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038 039import org.springframework.beans.BeansException; 040import org.springframework.beans.factory.BeanCreationException; 041import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; 042import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; 043import org.springframework.beans.factory.support.RootBeanDefinition; 044import org.springframework.core.Ordered; 045import org.springframework.core.PriorityOrdered; 046import org.springframework.core.annotation.AnnotationUtils; 047import org.springframework.lang.Nullable; 048import org.springframework.util.ClassUtils; 049import org.springframework.util.ReflectionUtils; 050 051/** 052 * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation 053 * that invokes annotated init and destroy methods. Allows for an annotation 054 * alternative to Spring's {@link org.springframework.beans.factory.InitializingBean} 055 * and {@link org.springframework.beans.factory.DisposableBean} callback interfaces. 056 * 057 * <p>The actual annotation types that this post-processor checks for can be 058 * configured through the {@link #setInitAnnotationType "initAnnotationType"} 059 * and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties. 060 * Any custom annotation can be used, since there are no required annotation 061 * attributes. 062 * 063 * <p>Init and destroy annotations may be applied to methods of any visibility: 064 * public, package-protected, protected, or private. Multiple such methods 065 * may be annotated, but it is recommended to only annotate one single 066 * init method and destroy method, respectively. 067 * 068 * <p>Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} 069 * supports the JSR-250 {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy} 070 * annotations out of the box, as init annotation and destroy annotation, respectively. 071 * Furthermore, it also supports the {@link javax.annotation.Resource} annotation 072 * for annotation-driven injection of named beans. 073 * 074 * @author Juergen Hoeller 075 * @since 2.5 076 * @see #setInitAnnotationType 077 * @see #setDestroyAnnotationType 078 */ 079@SuppressWarnings("serial") 080public class InitDestroyAnnotationBeanPostProcessor 081 implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable { 082 083 private final transient LifecycleMetadata emptyLifecycleMetadata = 084 new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) { 085 @Override 086 public void checkConfigMembers(RootBeanDefinition beanDefinition) { 087 } 088 @Override 089 public void invokeInitMethods(Object target, String beanName) { 090 } 091 @Override 092 public void invokeDestroyMethods(Object target, String beanName) { 093 } 094 @Override 095 public boolean hasDestroyMethods() { 096 return false; 097 } 098 }; 099 100 101 protected transient Log logger = LogFactory.getLog(getClass()); 102 103 @Nullable 104 private Class<? extends Annotation> initAnnotationType; 105 106 @Nullable 107 private Class<? extends Annotation> destroyAnnotationType; 108 109 private int order = Ordered.LOWEST_PRECEDENCE; 110 111 @Nullable 112 private final transient Map<Class<?>, LifecycleMetadata> lifecycleMetadataCache = new ConcurrentHashMap<>(256); 113 114 115 /** 116 * Specify the init annotation to check for, indicating initialization 117 * methods to call after configuration of a bean. 118 * <p>Any custom annotation can be used, since there are no required 119 * annotation attributes. There is no default, although a typical choice 120 * is the JSR-250 {@link javax.annotation.PostConstruct} annotation. 121 */ 122 public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) { 123 this.initAnnotationType = initAnnotationType; 124 } 125 126 /** 127 * Specify the destroy annotation to check for, indicating destruction 128 * methods to call when the context is shutting down. 129 * <p>Any custom annotation can be used, since there are no required 130 * annotation attributes. There is no default, although a typical choice 131 * is the JSR-250 {@link javax.annotation.PreDestroy} annotation. 132 */ 133 public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) { 134 this.destroyAnnotationType = destroyAnnotationType; 135 } 136 137 public void setOrder(int order) { 138 this.order = order; 139 } 140 141 @Override 142 public int getOrder() { 143 return this.order; 144 } 145 146 147 @Override 148 public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { 149 LifecycleMetadata metadata = findLifecycleMetadata(beanType); 150 metadata.checkConfigMembers(beanDefinition); 151 } 152 153 @Override 154 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 155 LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); 156 try { 157 metadata.invokeInitMethods(bean, beanName); 158 } 159 catch (InvocationTargetException ex) { 160 throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); 161 } 162 catch (Throwable ex) { 163 throw new BeanCreationException(beanName, "Failed to invoke init method", ex); 164 } 165 return bean; 166 } 167 168 @Override 169 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 170 return bean; 171 } 172 173 @Override 174 public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { 175 LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); 176 try { 177 metadata.invokeDestroyMethods(bean, beanName); 178 } 179 catch (InvocationTargetException ex) { 180 String msg = "Destroy method on bean with name '" + beanName + "' threw an exception"; 181 if (logger.isDebugEnabled()) { 182 logger.warn(msg, ex.getTargetException()); 183 } 184 else { 185 logger.warn(msg + ": " + ex.getTargetException()); 186 } 187 } 188 catch (Throwable ex) { 189 logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex); 190 } 191 } 192 193 @Override 194 public boolean requiresDestruction(Object bean) { 195 return findLifecycleMetadata(bean.getClass()).hasDestroyMethods(); 196 } 197 198 199 private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { 200 if (this.lifecycleMetadataCache == null) { 201 // Happens after deserialization, during destruction... 202 return buildLifecycleMetadata(clazz); 203 } 204 // Quick check on the concurrent map first, with minimal locking. 205 LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz); 206 if (metadata == null) { 207 synchronized (this.lifecycleMetadataCache) { 208 metadata = this.lifecycleMetadataCache.get(clazz); 209 if (metadata == null) { 210 metadata = buildLifecycleMetadata(clazz); 211 this.lifecycleMetadataCache.put(clazz, metadata); 212 } 213 return metadata; 214 } 215 } 216 return metadata; 217 } 218 219 private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) { 220 if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) { 221 return this.emptyLifecycleMetadata; 222 } 223 224 List<LifecycleElement> initMethods = new ArrayList<>(); 225 List<LifecycleElement> destroyMethods = new ArrayList<>(); 226 Class<?> targetClass = clazz; 227 228 do { 229 final List<LifecycleElement> currInitMethods = new ArrayList<>(); 230 final List<LifecycleElement> currDestroyMethods = new ArrayList<>(); 231 232 ReflectionUtils.doWithLocalMethods(targetClass, method -> { 233 if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { 234 LifecycleElement element = new LifecycleElement(method); 235 currInitMethods.add(element); 236 if (logger.isTraceEnabled()) { 237 logger.trace("Found init method on class [" + clazz.getName() + "]: " + method); 238 } 239 } 240 if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) { 241 currDestroyMethods.add(new LifecycleElement(method)); 242 if (logger.isTraceEnabled()) { 243 logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method); 244 } 245 } 246 }); 247 248 initMethods.addAll(0, currInitMethods); 249 destroyMethods.addAll(currDestroyMethods); 250 targetClass = targetClass.getSuperclass(); 251 } 252 while (targetClass != null && targetClass != Object.class); 253 254 return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata : 255 new LifecycleMetadata(clazz, initMethods, destroyMethods)); 256 } 257 258 259 //--------------------------------------------------------------------- 260 // Serialization support 261 //--------------------------------------------------------------------- 262 263 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 264 // Rely on default serialization; just initialize state after deserialization. 265 ois.defaultReadObject(); 266 267 // Initialize transient fields. 268 this.logger = LogFactory.getLog(getClass()); 269 } 270 271 272 /** 273 * Class representing information about annotated init and destroy methods. 274 */ 275 private class LifecycleMetadata { 276 277 private final Class<?> targetClass; 278 279 private final Collection<LifecycleElement> initMethods; 280 281 private final Collection<LifecycleElement> destroyMethods; 282 283 @Nullable 284 private volatile Set<LifecycleElement> checkedInitMethods; 285 286 @Nullable 287 private volatile Set<LifecycleElement> checkedDestroyMethods; 288 289 public LifecycleMetadata(Class<?> targetClass, Collection<LifecycleElement> initMethods, 290 Collection<LifecycleElement> destroyMethods) { 291 292 this.targetClass = targetClass; 293 this.initMethods = initMethods; 294 this.destroyMethods = destroyMethods; 295 } 296 297 public void checkConfigMembers(RootBeanDefinition beanDefinition) { 298 Set<LifecycleElement> checkedInitMethods = new LinkedHashSet<>(this.initMethods.size()); 299 for (LifecycleElement element : this.initMethods) { 300 String methodIdentifier = element.getIdentifier(); 301 if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) { 302 beanDefinition.registerExternallyManagedInitMethod(methodIdentifier); 303 checkedInitMethods.add(element); 304 if (logger.isTraceEnabled()) { 305 logger.trace("Registered init method on class [" + this.targetClass.getName() + "]: " + element); 306 } 307 } 308 } 309 Set<LifecycleElement> checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size()); 310 for (LifecycleElement element : this.destroyMethods) { 311 String methodIdentifier = element.getIdentifier(); 312 if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) { 313 beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier); 314 checkedDestroyMethods.add(element); 315 if (logger.isTraceEnabled()) { 316 logger.trace("Registered destroy method on class [" + this.targetClass.getName() + "]: " + element); 317 } 318 } 319 } 320 this.checkedInitMethods = checkedInitMethods; 321 this.checkedDestroyMethods = checkedDestroyMethods; 322 } 323 324 public void invokeInitMethods(Object target, String beanName) throws Throwable { 325 Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods; 326 Collection<LifecycleElement> initMethodsToIterate = 327 (checkedInitMethods != null ? checkedInitMethods : this.initMethods); 328 if (!initMethodsToIterate.isEmpty()) { 329 for (LifecycleElement element : initMethodsToIterate) { 330 if (logger.isTraceEnabled()) { 331 logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod()); 332 } 333 element.invoke(target); 334 } 335 } 336 } 337 338 public void invokeDestroyMethods(Object target, String beanName) throws Throwable { 339 Collection<LifecycleElement> checkedDestroyMethods = this.checkedDestroyMethods; 340 Collection<LifecycleElement> destroyMethodsToUse = 341 (checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods); 342 if (!destroyMethodsToUse.isEmpty()) { 343 for (LifecycleElement element : destroyMethodsToUse) { 344 if (logger.isTraceEnabled()) { 345 logger.trace("Invoking destroy method on bean '" + beanName + "': " + element.getMethod()); 346 } 347 element.invoke(target); 348 } 349 } 350 } 351 352 public boolean hasDestroyMethods() { 353 Collection<LifecycleElement> checkedDestroyMethods = this.checkedDestroyMethods; 354 Collection<LifecycleElement> destroyMethodsToUse = 355 (checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods); 356 return !destroyMethodsToUse.isEmpty(); 357 } 358 } 359 360 361 /** 362 * Class representing injection information about an annotated method. 363 */ 364 private static class LifecycleElement { 365 366 private final Method method; 367 368 private final String identifier; 369 370 public LifecycleElement(Method method) { 371 if (method.getParameterCount() != 0) { 372 throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method); 373 } 374 this.method = method; 375 this.identifier = (Modifier.isPrivate(method.getModifiers()) ? 376 ClassUtils.getQualifiedMethodName(method) : method.getName()); 377 } 378 379 public Method getMethod() { 380 return this.method; 381 } 382 383 public String getIdentifier() { 384 return this.identifier; 385 } 386 387 public void invoke(Object target) throws Throwable { 388 ReflectionUtils.makeAccessible(this.method); 389 this.method.invoke(target, (Object[]) null); 390 } 391 392 @Override 393 public boolean equals(@Nullable Object other) { 394 if (this == other) { 395 return true; 396 } 397 if (!(other instanceof LifecycleElement)) { 398 return false; 399 } 400 LifecycleElement otherElement = (LifecycleElement) other; 401 return (this.identifier.equals(otherElement.identifier)); 402 } 403 404 @Override 405 public int hashCode() { 406 return this.identifier.hashCode(); 407 } 408 } 409 410}