diff --git a/druid-spring-boot-actuator/druid-spring-boot-actuator-autoconfigure/src/main/java/com/github/trang/druid/actuate/autoconfigure/DruidDataSourceEndpointConfiguration.java b/druid-spring-boot-actuator/druid-spring-boot-actuator-autoconfigure/src/main/java/com/github/trang/druid/actuate/autoconfigure/DruidDataSourceEndpointConfiguration.java index c453575..b232097 100644 --- a/druid-spring-boot-actuator/druid-spring-boot-actuator-autoconfigure/src/main/java/com/github/trang/druid/actuate/autoconfigure/DruidDataSourceEndpointConfiguration.java +++ b/druid-spring-boot-actuator/druid-spring-boot-actuator-autoconfigure/src/main/java/com/github/trang/druid/actuate/autoconfigure/DruidDataSourceEndpointConfiguration.java @@ -1,7 +1,7 @@ package com.github.trang.druid.actuate.autoconfigure; -import com.github.trang.druid.actuate.DruidDataSourceEndpoint; import com.github.trang.druid.actuate.DruidDataSourceMvcEndpoint; +import com.github.trang.druid.actuate.DruidDataSourceEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; diff --git a/druid-spring-boot/druid-spring-boot-autoconfigure/src/main/java/com/github/trang/druid/autoconfigure/DruidDataSourceConfiguration.java b/druid-spring-boot/druid-spring-boot-autoconfigure/src/main/java/com/github/trang/druid/autoconfigure/DruidDataSourceConfiguration.java new file mode 100644 index 0000000..6eafae1 --- /dev/null +++ b/druid-spring-boot/druid-spring-boot-autoconfigure/src/main/java/com/github/trang/druid/autoconfigure/DruidDataSourceConfiguration.java @@ -0,0 +1,165 @@ +package com.github.trang.druid.autoconfigure; + +import com.alibaba.druid.pool.DruidDataSource; +import com.github.trang.druid.autoconfigure.DruidDataSourceConfiguration.DataSourceImportSelector; +import com.github.trang.druid.autoconfigure.datasource.DruidDataSource2; +import com.github.trang.druid.autoconfigure.util.CharMatcher; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.bind.PropertySourcesBinder; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotationMetadata; + +import java.util.Map; + +/** + * Druid 数据源配置 + * + * @author trang + */ +@Import(DataSourceImportSelector.class) +@Slf4j +public class DruidDataSourceConfiguration { + + /** + * 单数据源注册 + * + * @author trang + */ + static class SingleDataSourceRegister implements ImportBeanDefinitionRegistrar { + + static final String BEAN_NAME = "dataSource"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(BEAN_NAME)) { + BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource2.class) + .setInitMethodName("init") + .setDestroyMethodName("close") + .getBeanDefinition(); + registry.registerBeanDefinition(BEAN_NAME, beanDefinition); + log.debug("druid data-source init..."); + } + } + } + + /** + * 多数据源注册 + * + * @author trang + */ + static class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { + + private static final String BEAN_SUFFIX = "DataSource"; + private RelaxedPropertyResolver resolver; + + @Override + public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { + resolver.getSubProperties("").keySet().stream() + .map(key -> key.substring(0, key.indexOf("."))) + .distinct() + .forEach(dataSourceName -> { + // 构造 BeanDefinition,通过 DruidDataSource2 实现继承 'spring.datasource.druid' 的配置 + BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource2.class) + .setInitMethodName("init") + .setDestroyMethodName("close") + .getBeanDefinition(); + // 注册 BeanDefinition + registry.registerBeanDefinition(dataSourceName, beanDefinition); + // 注册驼峰别名 + String camelAlias = CharMatcher.separatedToCamel().apply(dataSourceName); + registry.registerAlias(dataSourceName, camelAlias); + // 注册以 DataSource 为后缀的别名 + String otherAlias; + if (camelAlias.toLowerCase().endsWith(BEAN_SUFFIX.toLowerCase())) { + if (!camelAlias.endsWith(BEAN_SUFFIX)) { + otherAlias = camelAlias.substring(0, camelAlias.toLowerCase().indexOf(BEAN_SUFFIX.toLowerCase())); + otherAlias = otherAlias + BEAN_SUFFIX; + registry.registerAlias(dataSourceName, otherAlias); + } + } else { + otherAlias = camelAlias + BEAN_SUFFIX; + registry.registerAlias(dataSourceName, otherAlias); + } + log.debug("druid {}-data-source init...", dataSourceName); + }); + } + + @Override + public void setEnvironment(Environment environment) { + this.resolver = new RelaxedPropertyResolver(environment, "spring.datasource.druid.data-sources."); + } + + } + + /** + * 数据源选择器 + * 当配置文件中存在 spring.datasource.druid.data-sources 属性时为多数据源 + * 不存在则为单数据源 + * + * @author trang + */ + static class DataSourceImportSelector implements ImportSelector, EnvironmentAware { + + private ConfigurableEnvironment environment; + + @Override + public String[] selectImports(AnnotationMetadata metadata) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, "spring.datasource.druid."); + Map properties = resolver.getSubProperties("data-sources"); + if (properties.isEmpty()) { + return new String[]{SingleDataSourceRegister.class.getName()}; + } + return new String[]{DynamicDataSourceRegister.class.getName(), + DataSourceBeanPostProcessor.class.getName()}; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = (ConfigurableEnvironment) environment; + } + + } + + /** + * DataSource Bean 处理器 + * + * @author trang + */ + static class DataSourceBeanPostProcessor implements BeanPostProcessor, EnvironmentAware { + + private ConfigurableEnvironment environment; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof DruidDataSource) { + // 将 'spring.datasource.druid.data-sources.${name}' 的配置绑定到 Bean + PropertySourcesBinder binder = new PropertySourcesBinder(environment); + binder.bindTo("spring.datasource.druid.data-sources." + beanName, bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = (ConfigurableEnvironment) environment; + } + + } + +} \ No newline at end of file diff --git a/druid-spring-boot/druid-spring-boot-autoconfigure/src/main/java/com/github/trang/druid/autoconfigure/util/CharMatcher.java b/druid-spring-boot/druid-spring-boot-autoconfigure/src/main/java/com/github/trang/druid/autoconfigure/util/CharMatcher.java new file mode 100644 index 0000000..25afce9 --- /dev/null +++ b/druid-spring-boot/druid-spring-boot-autoconfigure/src/main/java/com/github/trang/druid/autoconfigure/util/CharMatcher.java @@ -0,0 +1,137 @@ +package com.github.trang.druid.autoconfigure.util; + +import org.springframework.util.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author trang + */ +public final class CharMatcher { + + public static Manipulation none() { + return Manipulation.NONE; + } + + public static Manipulation separatedToCamel() { + return Manipulation.SEPARATED_TO_CAMELCASE; + } + + public static Manipulation camelToHyphen() { + return Manipulation.CAMELCASE_TO_HYPHEN; + } + + public static enum Manipulation { + + NONE { + @Override + public String apply(String value) { + return value; + } + }, + + HYPHEN_TO_UNDERSCORE { + @Override + public String apply(String value) { + return value.indexOf('-') != -1 ? value.replace('-', '_') : value; + } + }, + + UNDERSCORE_TO_PERIOD { + @Override + public String apply(String value) { + return value.indexOf('_') != -1 ? value.replace('_', '.') : value; + } + }, + + PERIOD_TO_UNDERSCORE { + @Override + public String apply(String value) { + return value.indexOf('.') != -1 ? value.replace('.', '_') : value; + } + }, + + CAMELCASE_TO_UNDERSCORE { + @Override + public String apply(String value) { + if (value.isEmpty()) { + return value; + } + Matcher matcher = CAMEL_CASE_PATTERN.matcher(value); + if (!matcher.find()) { + return value; + } + matcher = matcher.reset(); + StringBuffer result = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(result, matcher.group(1) + '_' + StringUtils.uncapitalize(matcher.group(2))); + } + matcher.appendTail(result); + return result.toString(); + } + }, + + CAMELCASE_TO_HYPHEN { + @Override + public String apply(String value) { + if (value.isEmpty()) { + return value; + } + Matcher matcher = CAMEL_CASE_PATTERN.matcher(value); + if (!matcher.find()) { + return value; + } + matcher = matcher.reset(); + StringBuffer result = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(result, matcher.group(1) + '-' + + StringUtils.uncapitalize(matcher.group(2))); + } + matcher.appendTail(result); + return result.toString(); + } + }, + + SEPARATED_TO_CAMELCASE { + @Override + public String apply(String value) { + return separatedToCamelCase(value, false); + } + }, + + CASE_INSENSITIVE_SEPARATED_TO_CAMELCASE { + @Override + public String apply(String value) { + return separatedToCamelCase(value, true); + } + }; + + private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])"); + private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern.compile("[_\\-.]"); + private static final char[] SUFFIXES = new char[]{'_', '-', '.'}; + + public abstract String apply(String value); + + private static String separatedToCamelCase(String value, boolean caseInsensitive) { + if (value.isEmpty()) { + return value; + } + StringBuilder builder = new StringBuilder(); + for (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) { + field = (caseInsensitive ? field.toLowerCase() : field); + builder.append(builder.length() == 0 ? field : StringUtils.capitalize(field)); + } + char lastChar = value.charAt(value.length() - 1); + for (char suffix : SUFFIXES) { + if (lastChar == suffix) { + builder.append(suffix); + break; + } + } + return builder.toString(); + } + + } + +} \ No newline at end of file