package com.github.bingoohuang.springrestclient.spring; import lombok.val; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyResourceConfigurer; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.util.StringUtils; import static org.springframework.util.Assert.notNull; public class SpringRestClientScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { private String basePackage; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; /* * This property lets you set the base package for your mapper interface files. * <p/> * You can set more than one package by using a semicolon or comma as a separator. * <p/> * Mappers will be searched for recursively starting in the specified package(s). * * @param basePackage base package name */ public void setBasePackage(String basePackage) { this.basePackage = basePackage; } public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) { this.processPropertyPlaceHolders = processPropertyPlaceHolders; } /** * {@inheritDoc} */ public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } /** * {@inheritDoc} */ public void setBeanName(String name) { this.beanName = name; } /** * Gets beanNameGenerator to be used while running the scanner. * * @return the beanNameGenerator BeanNameGenerator that has been configured */ public BeanNameGenerator getNameGenerator() { return nameGenerator; } /** * Sets beanNameGenerator to be used while running the scanner. * * @param nameGenerator the beanNameGenerator to set */ public void setNameGenerator(BeanNameGenerator nameGenerator) { this.nameGenerator = nameGenerator; } /** * {@inheritDoc} */ public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); } /** * {@inheritDoc} */ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // left intentionally blank } /** * {@inheritDoc} */ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathSpringRestClientScanner scanner = new ClassPathSpringRestClientScanner(registry); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } /* * BeanDefinitionRegistries are called early in application startup, before * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been * loaded and any property substitution of this class' properties will fail. To avoid this, find * any PropertyResourceConfigurers defined in the context and run them on this class' bean * definition. Then update the values. */ private void processPropertyPlaceHolders() { val prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) { val mapperScannerBean = ((GenericApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform // property placeholder substitution. Instead, create a BeanFactory that just // contains this mapper scanner and post process the factory. val factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) { prc.postProcessBeanFactory(factory); } val values = mapperScannerBean.getPropertyValues(); this.basePackage = updatePropertyValue("basePackage", values); } } private String updatePropertyValue(String propertyName, PropertyValues values) { PropertyValue property = values.getPropertyValue(propertyName); if (property == null) { return null; } Object value = property.getValue(); if (value == null) { return null; } else if (value instanceof String) { return value.toString(); } else if (value instanceof TypedStringValue) { return ((TypedStringValue) value).getValue(); } else { return null; } } }