/* * Copyright 2016-2018 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.joinfaces.autoconfigure.viewscope; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PreDestroy; import lombok.Getter; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.CustomScopeConfigurer; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import org.springframework.lang.Nullable; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest public class ViewScopeDestructionCallbackIT { @Autowired private ApplicationContext applicationContext; @Autowired @Qualifier("testScopeConfigurer") CustomScopeConfigurer testScopeConfigurer; @Test public void testExpectedSpringBehaviour() throws NoSuchFieldException, IllegalAccessException { Field scopesField = CustomScopeConfigurer.class.getDeclaredField("scopes"); scopesField.setAccessible(true); Map<String, org.springframework.beans.factory.config.Scope> scopes = (Map<String, org.springframework.beans.factory.config.Scope>) scopesField.get(this.testScopeConfigurer); TestConfig.TestScope testScope = (TestConfig.TestScope) scopes.get("test"); BeanWithTwoDestructionCallbacks bean = this.applicationContext.getBean(BeanWithTwoDestructionCallbacks.class); assertThat(bean.destroyCalled).isFalse(); assertThat(bean.preDestroyCalled).isFalse(); assertThat(testScope.getBeans()).hasSize(1); assertThat(testScope.getCallbacks()).hasSize(1); //Only one destruction callback per bean List<Runnable> destructionCallbacks = testScope.getCallbacks().get("beanWithTwoDestructionCallbacks"); assertThat(destructionCallbacks).hasSize(1); //The one destruction callback calls all destroy-methods destructionCallbacks.get(0).run(); assertThat(bean.preDestroyCalled).isTrue(); assertThat(bean.destroyCalled).isTrue(); } @TestConfiguration public static class TestConfig { @Bean public static CustomScopeConfigurer testScopeConfigurer() { CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer(); customScopeConfigurer.addScope("test", new TestScope()); return customScopeConfigurer; } @Bean @Scope("test") public BeanWithTwoDestructionCallbacks beanWithTwoDestructionCallbacks() { return new BeanWithTwoDestructionCallbacks(); } @Getter public static class TestScope implements org.springframework.beans.factory.config.Scope { Map<String, Object> beans = new HashMap<>(); Map<String, List<Runnable>> callbacks = new HashMap<>(); @Override public Object get(String name, ObjectFactory<?> objectFactory) { return this.beans.computeIfAbsent(name, k -> objectFactory.getObject()); } @Override public Object remove(String name) { return this.beans.remove(name); } @Override public void registerDestructionCallback(String name, Runnable callback) { this.callbacks.computeIfAbsent(name, k -> new ArrayList<>()).add(callback); } @Override @Nullable public Object resolveContextualObject(String key) { return null; } @Override @Nullable public String getConversationId() { return null; } } } public static class BeanWithTwoDestructionCallbacks implements DisposableBean { boolean destroyCalled; boolean preDestroyCalled; @Override public void destroy() { this.destroyCalled = true; } @PreDestroy public void preDestroy() { this.preDestroyCalled = true; } } }