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