/* * Copyright 2011-2013 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 kr.debop4j.core.spring; import kr.debop4j.core.AutoCloseableAction; import kr.debop4j.core.Guard; import kr.debop4j.core.Local; import kr.debop4j.core.tools.ArrayTool; import kr.debop4j.core.tools.StringTool; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; import org.springframework.stereotype.Component; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import java.util.List; import java.util.Map; import java.util.Stack; import static kr.debop4j.core.Guard.shouldBe; import static kr.debop4j.core.Guard.shouldNotBeNull; /** * Springs Framework 의 Dependency Injection을 담당하는 클래스입니다. * * @author 배성혁 ( sunghyouk.bae@gmail.com ) * @since 12. 11. 23. */ @Slf4j @ThreadSafe @Component public final class Springs { /** * Instantiates a new Springs. * * @param context the context */ @Autowired protected Springs(ApplicationContext context) { log.info("ApplicationContext 가 Injection 되었습니다."); globalContext = context; } public static final String DEFAULT_APPLICATION_CONTEXT_XML = "applicationContext.xml"; private static final String LOCAL_SPRING_CONTEXT = Springs.class.getName() + ".globalContext"; private static final String NOT_INITIALIZED_MSG = "Springs의 ApplicationContext가 초기화되지 않았습니다. Springs를 ComponentScan 해주셔야합니다!!!"; private static volatile ApplicationContext globalContext; private static ThreadLocal<Stack<GenericApplicationContext>> localContextStack = new ThreadLocal<>(); /** * Spring ApplicationContext 가 초기화 되었으면 true를 반환한다. * * @return the boolean */ public static synchronized boolean isInitialized() { return (globalContext != null); } /** * Spring ApplicationContext 가 초기화 되지 않았으면 true를 반환한다. * * @return the boolean */ public static synchronized boolean isNotInitialized() { return (globalContext == null); } private static synchronized void assertInitialized() { shouldBe(isInitialized(), NOT_INITIALIZED_MSG); } /** * Gets context. * * @return the context */ public static synchronized GenericApplicationContext getContext() { ApplicationContext context = getLocalContext(); if (context == null) context = globalContext; shouldBe(context != null, NOT_INITIALIZED_MSG); return (GenericApplicationContext) context; } private static synchronized GenericApplicationContext getLocalContext() { if (getLocalContextStack().size() == 0) return null; return getLocalContextStack().peek(); } private static synchronized Stack<GenericApplicationContext> getLocalContextStack() { if (localContextStack.get() == null) { localContextStack.set(new Stack<GenericApplicationContext>()); } return localContextStack.get(); } /** 초기화를 합니다. */ public static synchronized void init() { init(DEFAULT_APPLICATION_CONTEXT_XML); } /** * 초기화를 합니다. * * @param resourceLocations the resource locations */ public static synchronized void init(String... resourceLocations) { log.info("Springs Context 를 초기화합니다. resourceLocations=[{}]", StringTool.listToString(resourceLocations)); init(new GenericXmlApplicationContext(resourceLocations)); } /** * 초기화를 합니다. * * @param applicationContext the application context */ public static synchronized void init(ApplicationContext applicationContext) { shouldNotBeNull(applicationContext, "applicationContext"); log.info("Springs ApplicationContext 를 초기화 작업을 시작합니다..."); if (globalContext != null) { log.info("Springs ApplicationContext가 이미 초기화 되었으므로, 무시합니다. reset 후 init 을 호출하세요."); } globalContext = applicationContext; log.info("Springs ApplicationContext를 초기화 작업을 완료했습니다."); } /** * Init by annotated classes. * * @param annotatedClasses the annotated classes */ public static synchronized void initByAnnotatedClasses(Class<?>... annotatedClasses) { init(new AnnotationConfigApplicationContext(annotatedClasses)); } /** * Init by packages. * * @param basePackages the base packages */ public static synchronized void initByPackages(String... basePackages) { init(new AnnotationConfigApplicationContext(basePackages)); } /** * Use local context. * * @param localContext the local context * @return the auto closeable action */ public static synchronized AutoCloseableAction useLocalContext(final GenericApplicationContext localContext) { shouldNotBeNull(localContext, "localContext"); if (log.isDebugEnabled()) log.debug("로컬 컨텍스트를 사용하려고 합니다... localContext=[{}]", localContext); getLocalContextStack().push(localContext); return new AutoCloseableAction(new Runnable() { @Override public void run() { reset(localContext); } }); } /** * 지정된 ApplicationContext 를 초기화합니다. * * @param contextToReset 초기화 시킬 ApplicationContext */ public static synchronized void reset(@Nullable final ApplicationContext contextToReset) { if (contextToReset == null) { globalContext = null; log.info("Global Springs Context 를 Reset 했습니다!!!"); return; } if (log.isDebugEnabled()) log.debug("ApplicationContext=[{}] 을 Reset 합니다...", contextToReset); if (getLocalContext() == contextToReset) { getLocalContextStack().pop(); if (getLocalContextStack().size() == 0) Local.put(LOCAL_SPRING_CONTEXT, null); log.info("Local Application Context 를 Reset 했습니다."); return; } if (globalContext == contextToReset) { globalContext = null; log.info("Global Application Context 를 Reset 했습니다!!!"); } } /** Springs ApplicationContext를 초기화합니다. */ public static synchronized void reset() { if (getLocalContext() != null) reset(getLocalContext()); else reset(globalContext); } public static synchronized Object getBean(final String name) { assertInitialized(); if (log.isDebugEnabled()) log.debug("ApplicationContext로부터 Bean을 가져옵니다. name=[{}]", name); return getContext().getBean(name); } public static synchronized Object getBean(final String name, Object... args) { assertInitialized(); if (log.isDebugEnabled()) log.debug("ApplicationContext로부터 Bean을 가져옵니다. name=[{}], args=[{}]", name, StringTool.listToString(args)); return getContext().getBean(name, args); } public static synchronized <T> T getBean(final Class<T> beanClass) { assertInitialized(); if (log.isDebugEnabled()) log.debug("ApplicationContext로부터 Bean을 가져옵니다. beanClass=[{}]", beanClass.getName()); return getContext().getBean(beanClass); } public static synchronized <T> T getBean(final String name, Class<T> beanClass) { assertInitialized(); if (log.isDebugEnabled()) log.debug("ApplicationContext로부터 Bean을 가져옵니다. beanName=[{}], beanClass=[{}]", name, beanClass); return getContext().getBean(name, beanClass); } public static synchronized <T> String[] getBeanNamesForType(Class<T> beanClass) { return getBeanNamesForType(beanClass, true, true); } public static synchronized <T> String[] getBeanNamesForType(Class<T> beanClass, boolean includeNonSingletons, boolean allowEagerInit) { shouldNotBeNull(beanClass, "beanClass"); if (log.isDebugEnabled()) log.debug("해당 수형의 모든 Bean의 이름을 조회합니다. beanClass=[{}], includeNonSingletons=[{}], allowEagerInit=[{}]", beanClass.getName(), includeNonSingletons, allowEagerInit); return getContext().getBeanNamesForType(beanClass, includeNonSingletons, allowEagerInit); } /** 지정한 타입의 Bean 들의 인스턴스를 가져옵니다. (Prototype Bean 도 포함됩니다.) */ public static <T> List<T> getBeansByType(Class<T> beanClass) { return getBeansByType(beanClass, true, true); } public static <T> List<T> getBeansByType(Class<T> beanClass, boolean includeNonSingletons, boolean allowEagerInit) { Map<String, T> beanMap = getBeansOfType(beanClass, includeNonSingletons, allowEagerInit); return ArrayTool.toList(beanMap.values()); } public static <T> T getFirstBeanByType(Class<T> beanClass) { return getFirstBeanByType(beanClass, true, true); } public static <T> T getFirstBeanByType(Class<T> beanClass, boolean includeNonSingletons, boolean allowEagerInit) { List<T> beans = getBeansByType(beanClass, includeNonSingletons, allowEagerInit); if (beans != null && beans.size() > 0) return beans.get(0); else return null; } /** 지정된 수형 또는 상속한 수형으로 등록된 bean 들을 조회합니다. */ public static synchronized <T> Map<String, T> getBeansOfType(Class<T> beanClass) { return getBeansOfType(beanClass, true, true); } /** * 지정된 수형 또는 상속한 수형으로 등록된 bean 들을 조회합니다. * * @param beanClass Bean 수형 * @param includeNonSingletons Singleton 타입의 Bean 이 아닌 경우도 포함 * @param allowEagerInit 미리 초기화를 수행할 것인가? */ public static synchronized <T> Map<String, T> getBeansOfType(Class<T> beanClass, boolean includeNonSingletons, boolean allowEagerInit) { assert beanClass != null; if (log.isDebugEnabled()) log.debug("해당 수형의 모든 Bean을 조회합니다. beanClass=[{}], includeNonSingletons=[{}], allowEagerInit=[{}]", beanClass.getName(), includeNonSingletons, allowEagerInit); return getContext().getBeansOfType(beanClass, includeNonSingletons, allowEagerInit); } /** * 지정된 수형의 Bean 을 조회합니다. 등록되지 않았으면 등록하고 반환합니다. * * @param beanClass Bean 수형 * @return Bean 인스턴스 */ public static synchronized <T> T getOrRegisterBean(Class<T> beanClass) { return getOrRegisterBean(beanClass, ConfigurableBeanFactory.SCOPE_SINGLETON); } /** * 지정된 수형의 Bean 을 조회합니다. 등록되지 않았으면 등록하고 반환합니다. * * @param beanClass Bean 수형 * @param scope scope ( singleton, prototype ) * @return Bean 인스턴스 */ public static synchronized <T> T getOrRegisterBean(Class<T> beanClass, String scope) { return getOrRegisterBean(beanClass, beanClass, scope); } /** * 지정된 수형의 Bean 을 조회합니다. 등록되지 않았으면 등록하고 반환합니다. * * @param beanClass Bean 수형 * @param registBeanClass 등록되지 않은 beanClass 일때, 실제 등록할 Bean의 수형 (Concrete Class) * @return Bean 인스턴스 */ public static synchronized <T> T getOrRegisterBean(Class<T> beanClass, Class<? extends T> registBeanClass) { return getOrRegisterBean(beanClass, registBeanClass, ConfigurableBeanFactory.SCOPE_SINGLETON); } /** * 등록된 beanClass 를 조회 (보통 Interface) 하고, 없다면, registerBeanClass (Concrete Class) 를 등록합니다. * * @param beanClass 조회할 Bean의 수형 (보통 인터페이스) * @param registBeanClass 등록되지 않은 beanClass 일때, 실제 등록할 Bean의 수형 (Concrete Class) * @param scope "singleton", "prototype" * @param <T> Bean의 수형 * @return 등록된 Bean의 인스턴스 */ public static synchronized <T> T getOrRegisterBean(Class<T> beanClass, Class<? extends T> registBeanClass, String scope) { T bean = getFirstBeanByType(beanClass, true, true); if (bean != null) return bean; registerBean(registBeanClass.getName(), registBeanClass, scope); return getContext().getBean(registBeanClass); } public static synchronized <T> T getOrRegisterSingletonBean(Class<T> beanClass) { return getOrRegisterBean(beanClass, ConfigurableBeanFactory.SCOPE_SINGLETON); } public static synchronized <T> T getOrRegisterPrototypeBean(Class<T> beanClass) { return getOrRegisterBean(beanClass, ConfigurableBeanFactory.SCOPE_PROTOTYPE); } /** * 지정된 Bean 이름이 사용되었는가? * * @param beanName Bean 이름 * @return 사용 여부 */ public static synchronized boolean isBeanNameInUse(final String beanName) { return getContext().isBeanNameInUse(beanName); } /** * 지정된 Bean 이름이 현재 Context에 등록되었는가? * * @param beanName Bean 이름 * @return 사용 여부 */ public static synchronized boolean isRegisteredBean(final String beanName) { return getContext().isBeanNameInUse(beanName); } /** * Is registered bean. * * @param beanClass the bean class * @return the boolean */ public static synchronized <T> boolean isRegisteredBean(Class<T> beanClass) { assert beanClass != null; try { return (getContext().getBean(beanClass) != null); } catch (Exception e) { return false; } } /** * Register bean. * * @param beanName the bean name * @param beanClass the bean class * @return the boolean */ public static synchronized <T> boolean registerBean(String beanName, Class<T> beanClass) { return registerBean(beanName, beanClass, ConfigurableBeanFactory.SCOPE_SINGLETON); } /** * Register bean. * * @param beanName the bean name * @param beanClass the bean class * @param scope the scope * @param propertyValues the property values * @return the boolean */ public static synchronized <T> boolean registerBean(String beanName, Class<T> beanClass, String scope, PropertyValue... propertyValues) { assert beanClass != null; BeanDefinition definition = new RootBeanDefinition(beanClass); definition.setScope(scope); for (PropertyValue pv : propertyValues) { definition.getPropertyValues().addPropertyValue(pv); } return registerBean(beanName, definition); } /** * Register bean. * * @param beanName the bean name * @param beanDefinition the bean definition * @return the boolean */ public static synchronized boolean registerBean(String beanName, BeanDefinition beanDefinition) { Guard.shouldNotBeEmpty(beanName, "beanName"); shouldNotBeNull(beanDefinition, "beanDefinition"); if (isBeanNameInUse(beanName)) throw new BeanDefinitionValidationException("이미 등록된 Bean입니다. beanName=" + beanName); if (log.isInfoEnabled()) log.info("새로운 Bean을 등록합니다. beanName=[{}], beanDefinition=[{}]", beanName, beanDefinition); try { getContext().registerBeanDefinition(beanName, beanDefinition); return true; } catch (Exception e) { log.error("새로운 Bean 등록에 실패했습니다. beanName=" + beanName, e); } return false; } /** * Register bean. * * @param beanName the bean name * @param instance the instance * @return the boolean */ public static synchronized boolean registerBean(String beanName, Object instance) { Guard.shouldNotBeEmpty(beanName, "beanName"); try { getContext().getBeanFactory().registerSingleton(beanName, instance); return true; } catch (Exception e) { log.error("인스턴스를 빈으로 등록하는데 실패했습니다. beanName=" + beanName, e); return false; } } /** * Register singleton bean. * * @param beanName the bean name * @param instance the instance * @return the boolean */ public static synchronized boolean registerSingletonBean(String beanName, Object instance) { return registerBean(beanName, instance); } /** * Register singleton bean. * * @param beanClass the bean class * @param pvs the pvs * @return the boolean */ public static synchronized <T> boolean registerSingletonBean(Class<T> beanClass, PropertyValue... pvs) { assert beanClass != null; return registerSingletonBean(beanClass.getName(), beanClass, pvs); } /** * Register singleton bean. * * @param beanName the bean name * @param beanClass the bean class * @param pvs the pvs * @return the boolean */ public static synchronized <T> boolean registerSingletonBean(final String beanName, final Class<T> beanClass, PropertyValue... pvs) { return registerBean(beanName, beanClass, ConfigurableBeanFactory.SCOPE_SINGLETON, pvs); } /** * Register prototype bean. * * @param beanClass the bean class * @param pvs the pvs * @return the boolean */ public static synchronized <T> boolean registerPrototypeBean(final Class<T> beanClass, PropertyValue... pvs) { assert beanClass != null; return registerPrototypeBean(beanClass.getName(), beanClass, pvs); } /** * Register prototype bean. * * @param beanName the bean name * @param beanClass the bean class * @param pvs the pvs * @return the boolean */ public static synchronized <T> boolean registerPrototypeBean(final String beanName, Class<T> beanClass, PropertyValue... pvs) { return registerBean(beanName, beanClass, ConfigurableBeanFactory.SCOPE_PROTOTYPE, pvs); } /** * Remove bean. * * @param beanName the bean name */ public static synchronized void removeBean(final String beanName) { Guard.shouldNotBeEmpty(beanName, "beanName"); if (isBeanNameInUse(beanName)) { if (log.isDebugEnabled()) log.debug("ApplicationContext에서 name=[{}]인 Bean을 제거합니다.", beanName); getContext().removeBeanDefinition(beanName); } } /** * Remove bean. * * @param beanClass the bean class */ public static synchronized <T> void removeBean(final Class<T> beanClass) { assert beanClass != null; if (log.isDebugEnabled()) log.debug("Bean 형식 [{}]의 모든 Bean을 ApplicationContext에서 제거합니다.", beanClass.getName()); String[] beanNames = getContext().getBeanNamesForType(beanClass, true, true); for (String beanName : beanNames) removeBean(beanName); } /** * Try get bean. * * @param beanName the bean name * @return the object */ public static synchronized Object tryGetBean(final String beanName) { Guard.shouldNotBeEmpty(beanName, "beanName"); try { return getBean(beanName); } catch (Exception e) { log.warn("bean을 찾는데 실패했습니다. null을 반환합니다. beanName=" + beanName, e); return null; } } /** * Try get bean. * * @param beanName the bean name * @param args the args * @return the object */ public static synchronized Object tryGetBean(final String beanName, Object... args) { Guard.shouldNotBeEmpty(beanName, "beanName"); try { return getBean(beanName, args); } catch (Exception e) { log.warn("bean을 찾는데 실패했습니다. null을 반환합니다. beanName=" + beanName, e); return null; } } /** * Try get bean. * * @param beanClass the bean class * @return the t */ public static synchronized <T> T tryGetBean(final Class<T> beanClass) { shouldNotBeNull(beanClass, "beanClass"); try { return getBean(beanClass); } catch (Exception e) { log.warn("bean을 찾는데 실패했습니다. null을 반환합니다. beanClass=" + beanClass.getName(), e); return null; } } /** * Try get bean. * * @param beanName the bean name * @param beanClass the bean class * @return the t */ public static synchronized <T> T tryGetBean(final String beanName, Class<T> beanClass) { shouldNotBeNull(beanClass, "beanClass"); try { return getBean(beanName, beanClass); } catch (Exception e) { log.warn("bean을 찾는데 실패했습니다. null을 반환합니다. beanName=" + beanName, e); return null; } } }