001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.beans.factory.annotation;
018:
019: import java.io.Serializable;
020: import java.lang.annotation.Annotation;
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.util.LinkedHashSet;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.concurrent.ConcurrentHashMap;
027:
028: import org.springframework.beans.BeansException;
029: import org.springframework.beans.factory.BeanCreationException;
030: import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
031: import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
032: import org.springframework.beans.factory.support.RootBeanDefinition;
033: import org.springframework.core.Ordered;
034: import org.springframework.core.PriorityOrdered;
035: import org.springframework.util.ReflectionUtils;
036:
037: /**
038: * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
039: * that invokes annotated init and destroy methods. Allows for an annotation
040: * alternative to Spring's {@link org.springframework.beans.factory.InitializingBean}
041: * and {@link org.springframework.beans.factory.DisposableBean} callback interfaces.
042: *
043: * <p>The actual annotation types that this post-processor checks for can be
044: * configured through the {@link #setInitAnnotationType "initAnnotationType"}
045: * and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties.
046: * Any custom annotation can be used, since there are no required annotation
047: * attributes.
048: *
049: * <p>Init and destroy annotations may be applied to methods of any visibility:
050: * public, package-protected, protected, or private. Multiple such methods
051: * may be annotated, but it is recommended to only annotate one single
052: * init method and destroy method, respectively.
053: *
054: * <p>Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}
055: * supports the JSR-250 {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy}
056: * annotations out of the box, as init annotation and destroy annotation, respectively.
057: * Furthermore, it also supports the {@link javax.annotation.Resource} annotation
058: * for annotation-driven injection of named beans.
059: *
060: * @author Juergen Hoeller
061: * @since 2.5
062: * @see #setInitAnnotationType
063: * @see #setDestroyAnnotationType
064: * @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
065: */
066: public class InitDestroyAnnotationBeanPostProcessor implements
067: DestructionAwareBeanPostProcessor,
068: MergedBeanDefinitionPostProcessor, PriorityOrdered,
069: Serializable {
070:
071: private Class<? extends Annotation> initAnnotationType;
072:
073: private Class<? extends Annotation> destroyAnnotationType;
074:
075: private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
076:
077: private transient final Map<Class<?>, LifecycleMetadata> lifecycleMetadataCache = new ConcurrentHashMap<Class<?>, LifecycleMetadata>();
078:
079: /**
080: * Specify the init annotation to check for, indicating initialization
081: * methods to call after configuration of a bean.
082: * <p>Any custom annotation can be used, since there are no required
083: * annotation attributes. There is no default, although a typical choice
084: * is the JSR-250 {@link javax.annotation.PostConstruct} annotation.
085: */
086: public void setInitAnnotationType(
087: Class<? extends Annotation> initAnnotationType) {
088: this .initAnnotationType = initAnnotationType;
089: }
090:
091: /**
092: * Specify the destroy annotation to check for, indicating destruction
093: * methods to call when the context is shutting down.
094: * <p>Any custom annotation can be used, since there are no required
095: * annotation attributes. There is no default, although a typical choice
096: * is the JSR-250 {@link javax.annotation.PreDestroy} annotation.
097: */
098: public void setDestroyAnnotationType(
099: Class<? extends Annotation> destroyAnnotationType) {
100: this .destroyAnnotationType = destroyAnnotationType;
101: }
102:
103: public void setOrder(int order) {
104: this .order = order;
105: }
106:
107: public int getOrder() {
108: return this .order;
109: }
110:
111: public void postProcessMergedBeanDefinition(
112: RootBeanDefinition beanDefinition, Class beanType,
113: String beanName) {
114: if (beanType != null) {
115: LifecycleMetadata metadata = findLifecycleMetadata(beanType);
116: for (LifecycleElement lifecycleElement : metadata
117: .getInitMethods()) {
118: beanDefinition
119: .registerExternallyManagedInitMethod(lifecycleElement
120: .getMethod().getName());
121: }
122: for (LifecycleElement lifecycleElement : metadata
123: .getDestroyMethods()) {
124: beanDefinition
125: .registerExternallyManagedDestroyMethod(lifecycleElement
126: .getMethod().getName());
127: }
128: }
129: }
130:
131: public Object postProcessBeforeInitialization(Object bean,
132: String beanName) throws BeansException {
133: LifecycleMetadata metadata = findLifecycleMetadata(bean
134: .getClass());
135: try {
136: metadata.invokeInitMethods(bean);
137: } catch (Throwable ex) {
138: throw new BeanCreationException(beanName,
139: "Invocation of init method failed", ex);
140: }
141: return bean;
142: }
143:
144: public Object postProcessAfterInitialization(Object bean,
145: String beanName) throws BeansException {
146: return bean;
147: }
148:
149: public void postProcessBeforeDestruction(Object bean,
150: String beanName) throws BeansException {
151: LifecycleMetadata metadata = findLifecycleMetadata(bean
152: .getClass());
153: try {
154: metadata.invokeDestroyMethods(bean);
155: } catch (Throwable ex) {
156: throw new BeanCreationException(beanName,
157: "Invocation of destroy method failed", ex);
158: }
159: }
160:
161: private LifecycleMetadata findLifecycleMetadata(Class clazz) {
162: if (this .lifecycleMetadataCache == null) {
163: // Happens after deserialization, during destruction...
164: return buildLifecycleMetadata(clazz);
165: }
166: // Quick check on the concurrent map first, with minimal locking.
167: LifecycleMetadata metadata = this .lifecycleMetadataCache
168: .get(clazz);
169: if (metadata == null) {
170: synchronized (this .lifecycleMetadataCache) {
171: metadata = this .lifecycleMetadataCache.get(clazz);
172: if (metadata == null) {
173: metadata = buildLifecycleMetadata(clazz);
174: this .lifecycleMetadataCache.put(clazz, metadata);
175: }
176: return metadata;
177: }
178: }
179: return metadata;
180: }
181:
182: private LifecycleMetadata buildLifecycleMetadata(Class clazz) {
183: final LifecycleMetadata newMetadata = new LifecycleMetadata();
184: ReflectionUtils.doWithMethods(clazz,
185: new ReflectionUtils.MethodCallback() {
186: public void doWith(Method method) {
187: if (initAnnotationType != null) {
188: if (method
189: .getAnnotation(initAnnotationType) != null) {
190: newMetadata.addInitMethod(method);
191: }
192: }
193: if (destroyAnnotationType != null) {
194: if (method
195: .getAnnotation(destroyAnnotationType) != null) {
196: newMetadata.addDestroyMethod(method);
197: }
198: }
199: }
200: });
201: return newMetadata;
202: }
203:
204: /**
205: * Class representing information about annotated init and destroy methods.
206: */
207: private static class LifecycleMetadata {
208:
209: private final Set<LifecycleElement> initMethods = new LinkedHashSet<LifecycleElement>();
210:
211: private final Set<LifecycleElement> destroyMethods = new LinkedHashSet<LifecycleElement>();
212:
213: public void addInitMethod(Method method) {
214: this .initMethods.add(new LifecycleElement(method));
215: }
216:
217: public Set<LifecycleElement> getInitMethods() {
218: return this .initMethods;
219: }
220:
221: public void invokeInitMethods(Object target) throws Throwable {
222: for (LifecycleElement lifecycleElement : this .initMethods) {
223: lifecycleElement.invoke(target);
224: }
225: }
226:
227: public void addDestroyMethod(Method method) {
228: this .destroyMethods.add(new LifecycleElement(method));
229: }
230:
231: public Set<LifecycleElement> getDestroyMethods() {
232: return this .destroyMethods;
233: }
234:
235: public void invokeDestroyMethods(Object target)
236: throws Throwable {
237: for (LifecycleElement lifecycleElement : this .destroyMethods) {
238: lifecycleElement.invoke(target);
239: }
240: }
241: }
242:
243: /**
244: * Class representing injection information about an annotated method.
245: */
246: private static class LifecycleElement {
247:
248: private final Method method;
249:
250: public LifecycleElement(Method method) {
251: if (method.getParameterTypes().length != 0) {
252: throw new IllegalStateException(
253: "Lifecycle method annotation requires a no-arg method: "
254: + method);
255: }
256: this .method = method;
257: }
258:
259: public Method getMethod() {
260: return this .method;
261: }
262:
263: public void invoke(Object target) throws Throwable {
264: ReflectionUtils.makeAccessible(this .method);
265: try {
266: this .method.invoke(target, (Object[]) null);
267: } catch (InvocationTargetException ex) {
268: throw ex.getTargetException();
269: }
270: }
271:
272: public boolean equals(Object other) {
273: return (this == other || (other instanceof LifecycleElement && this .method
274: .getName()
275: .equals(((LifecycleElement) other).method.getName())));
276: }
277:
278: public int hashCode() {
279: return this.method.getName().hashCode();
280: }
281: }
282:
283: }
|