/* * Copyright 2002-2007 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 * * http://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.context.annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.util.ObjectUtils; /** * <p> * JUnit-3.8-based unit test which verifies expected <em>init</em> and * <em>destroy</em> bean lifecycle behavior as requested in <a * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3775" * target="_blank">SPR-3775</a>. * </p> * <p> * Specifically, combinations of the following are tested: * </p> * <ul> * <li>{@link InitializingBean} & {@link DisposableBean} interfaces</li> * <li>Custom {@link RootBeanDefinition#getInitMethodName() init} & * {@link RootBeanDefinition#getDestroyMethodName() destroy} methods</li> * <li>JSR 250's {@link javax.annotation.PostConstruct @PostConstruct} & * {@link javax.annotation.PreDestroy @PreDestroy} annotations</li> * </ul> * * @author Sam Brannen * @since 2.5 */ public class Spr3775InitDestroyLifecycleTests extends TestCase { private static final Log logger = LogFactory.getLog(Spr3775InitDestroyLifecycleTests.class); /** LIFECYCLE_TEST_BEAN. */ private static final String LIFECYCLE_TEST_BEAN = "lifecycleTestBean"; private void debugMethods(Class<?> clazz, String category, List<String> methodNames) { if (logger.isDebugEnabled()) { logger.debug(clazz.getSimpleName() + ": " + category + ": " + methodNames); } } private void assertMethodOrdering(Class<?> clazz, String category, List<String> expectedMethods, List<String> actualMethods) { debugMethods(clazz, category, actualMethods); assertTrue("Verifying " + category + ": expected<" + expectedMethods + "> but got<" + actualMethods + ">.", ObjectUtils.nullSafeEquals(expectedMethods, actualMethods)); } private DefaultListableBeanFactory createBeanFactoryAndRegisterBean(final Class<?> beanClass, final String initMethodName, final String destroyMethodName) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setInitMethodName(initMethodName); beanDefinition.setDestroyMethodName(destroyMethodName); beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); beanFactory.registerBeanDefinition(LIFECYCLE_TEST_BEAN, beanDefinition); return beanFactory; } public void testInitDestroyMethods() { final Class<?> beanClass = InitDestroyBean.class; final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy"); final InitDestroyBean bean = (InitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods); beanFactory.destroySingletons(); assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy"), bean.destroyMethods); } public void testInitializingDisposableInterfaces() { final Class<?> beanClass = CustomInitializingDisposableBean.class; final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); final CustomInitializingDisposableBean bean = (CustomInitializingDisposableBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet", "customInit"), bean.initMethods); beanFactory.destroySingletons(); assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy", "customDestroy"), bean.destroyMethods); } public void testInitializingDisposableInterfacesWithShadowedMethods() { final Class<?> beanClass = InitializingDisposableWithShadowedMethodsBean.class; final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy"); final InitializingDisposableWithShadowedMethodsBean bean = (InitializingDisposableWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); assertMethodOrdering(beanClass, "init-methods", Arrays.asList("InitializingBean.afterPropertiesSet"), bean.initMethods); beanFactory.destroySingletons(); assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("DisposableBean.destroy"), bean.destroyMethods); } public void testJsr250Annotations() { final Class<?> beanClass = CustomAnnotatedInitDestroyBean.class; final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); final CustomAnnotatedInitDestroyBean bean = (CustomAnnotatedInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); assertMethodOrdering(beanClass, "init-methods", Arrays.asList("postConstruct", "afterPropertiesSet", "customInit"), bean.initMethods); beanFactory.destroySingletons(); assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("preDestroy", "destroy", "customDestroy"), bean.destroyMethods); } public void testJsr250AnnotationsWithShadowedMethods() { final Class<?> beanClass = CustomAnnotatedInitDestroyWithShadowedMethodsBean.class; final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); final CustomAnnotatedInitDestroyWithShadowedMethodsBean bean = (CustomAnnotatedInitDestroyWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); assertMethodOrdering(beanClass, "init-methods", Arrays.asList("@PostConstruct.afterPropertiesSet", "customInit"), bean.initMethods); beanFactory.destroySingletons(); assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("@PreDestroy.destroy", "customDestroy"), bean.destroyMethods); } public void testAllLifecycleMechanismsAtOnce() { final Class<?> beanClass = AllInOneBean.class; final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "afterPropertiesSet", "destroy"); final AllInOneBean bean = (AllInOneBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods); beanFactory.destroySingletons(); assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy"), bean.destroyMethods); } public static class InitDestroyBean { final List<String> initMethods = new ArrayList<String>(); final List<String> destroyMethods = new ArrayList<String>(); public void afterPropertiesSet() throws Exception { this.initMethods.add("afterPropertiesSet"); } public void destroy() throws Exception { this.destroyMethods.add("destroy"); } } public static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean implements InitializingBean, DisposableBean { @Override public void afterPropertiesSet() throws Exception { this.initMethods.add("InitializingBean.afterPropertiesSet"); } @Override public void destroy() throws Exception { this.destroyMethods.add("DisposableBean.destroy"); } } public static class CustomInitDestroyBean { final List<String> initMethods = new ArrayList<String>(); final List<String> destroyMethods = new ArrayList<String>(); public void customInit() throws Exception { this.initMethods.add("customInit"); } public void customDestroy() throws Exception { this.destroyMethods.add("customDestroy"); } } public static class CustomInitializingDisposableBean extends CustomInitDestroyBean implements InitializingBean, DisposableBean { public void afterPropertiesSet() throws Exception { this.initMethods.add("afterPropertiesSet"); } public void destroy() throws Exception { this.destroyMethods.add("destroy"); } } public static class CustomAnnotatedInitDestroyBean extends CustomInitializingDisposableBean { @PostConstruct public void postConstruct() throws Exception { this.initMethods.add("postConstruct"); } @PreDestroy public void preDestroy() throws Exception { this.destroyMethods.add("preDestroy"); } } public static class CustomAnnotatedInitDestroyWithShadowedMethodsBean extends CustomInitializingDisposableBean { @PostConstruct @Override public void afterPropertiesSet() throws Exception { this.initMethods.add("@PostConstruct.afterPropertiesSet"); } @PreDestroy @Override public void destroy() throws Exception { this.destroyMethods.add("@PreDestroy.destroy"); } } public static class AllInOneBean implements InitializingBean, DisposableBean { final List<String> initMethods = new ArrayList<String>(); final List<String> destroyMethods = new ArrayList<String>(); @PostConstruct public void afterPropertiesSet() throws Exception { this.initMethods.add("afterPropertiesSet"); } @PreDestroy public void destroy() throws Exception { this.destroyMethods.add("destroy"); } } }