From 5394eb89fb2ca1681c74ebef77f08036dee7da8d Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Fri, 17 Aug 2018 11:13:19 +0800 Subject: [PATCH] sentinel-starter support datasource & refactor some classes --- spring-cloud-alibaba-dependencies/pom.xml | 22 ++ .../pom.xml | 23 ++ .../alibaba/sentinel/SentinelConstants.java | 33 +++ .../alibaba/sentinel/SentinelProperties.java | 2 +- .../SentinelWebAutoConfiguration.java | 28 +-- .../annotation/SentinelDataSource.java | 44 ++++ .../custom/SentinelAutoConfiguration.java | 40 +++- .../custom/SentinelProtectInterceptor.java | 6 +- .../sentinel/datasource/DataSourceLoader.java | 151 ++++++++++++ .../SentinelDataSourcePostProcessor.java | 217 ++++++++++++++++++ .../SentinelDataSourceRegistry.java | 67 ++++++ .../ApolloDataSourceFactoryBean.java | 61 +++++ .../FileRefreshableDataSourceFactoryBean.java | 74 ++++++ .../NacosDataSourceFactoryBean.java | 60 +++++ .../ZookeeperDataSourceFactoryBean.java | 80 +++++++ .../sentinel/util/PropertySourcesUtils.java | 75 ++++++ .../META-INF/sentinel-datasource.properties | 4 + spring-cloud-starter-sentinel/pom.xml | 12 + 18 files changed, 969 insertions(+), 30 deletions(-) create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelDataSource.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/util/PropertySourcesUtils.java create mode 100644 spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/sentinel-datasource.properties diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index ed51123ab..0b45f8065 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -31,6 +31,21 @@ sentinel-datasource-extension ${sentinel.version} + + com.alibaba.csp + sentinel-datasource-apollo + ${sentinel.version} + + + com.alibaba.csp + sentinel-datasource-zookeeper + ${sentinel.version} + + + com.alibaba.csp + sentinel-datasource-nacos + ${sentinel.version} + com.alibaba.csp sentinel-web-servlet @@ -67,6 +82,13 @@ ${project.version} + + + org.springframework.cloud + sentinel-dubbo-api + ${project.version} + + diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml b/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml index 7b10ee91d..486d320e6 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml +++ b/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml @@ -25,6 +25,29 @@ sentinel-transport-simple-http + + com.alibaba.csp + sentinel-datasource-extension + + + + com.alibaba.csp + sentinel-datasource-nacos + true + + + + com.alibaba.csp + sentinel-datasource-zookeeper + true + + + + com.alibaba.csp + sentinel-datasource-apollo + true + + com.alibaba.csp sentinel-annotation-aspectj diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java new file mode 100644 index 000000000..747044a50 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 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.springframework.cloud.alibaba.sentinel; + +/** + * @author fangjian + */ +public interface SentinelConstants { + + String PROPERTY_PREFIX = "spring.cloud.sentinel"; + + String PROPERTY_ITEM_SEPARATOR = "."; + + String PROPERTY_DATASOURCE_NAME = "datasource"; + + String PROPERTY_DATASOURCE_PREFIX = PROPERTY_PREFIX + PROPERTY_ITEM_SEPARATOR + + PROPERTY_DATASOURCE_NAME; + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java index 283ea0705..eb1f2be77 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java @@ -26,7 +26,7 @@ import org.springframework.core.Ordered; * @author xiaojing * @author hengyunabc */ -@ConfigurationProperties(prefix = "spring.cloud.sentinel") +@ConfigurationProperties(prefix = SentinelConstants.PROPERTY_PREFIX) public class SentinelProperties { /** diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java index 0f4fb4fee..f982e8ceb 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java @@ -19,59 +19,37 @@ package org.springframework.cloud.alibaba.sentinel; import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; import javax.servlet.Filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; -import com.alibaba.csp.sentinel.transport.config.TransportConfig; /** * @author xiaojing */ @Configuration +@ConditionalOnWebApplication @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelWebAutoConfiguration { + private static final Logger logger = LoggerFactory .getLogger(SentinelWebAutoConfiguration.class); - @Value("${project.name:${spring.application.name:}}") - private String projectName; - @Autowired private SentinelProperties properties; - public static final String APP_NAME = "project.name"; - - @PostConstruct - private void init() { - if (StringUtils.isEmpty(System.getProperty(APP_NAME))) { - System.setProperty(APP_NAME, projectName); - } - if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))) { - System.setProperty(TransportConfig.SERVER_PORT, properties.getPort()); - } - if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))) { - System.setProperty(TransportConfig.CONSOLE_SERVER, properties.getDashboard()); - } - - } - @Bean - @ConditionalOnWebApplication - public FilterRegistrationBean servletRequestListener() { + public FilterRegistrationBean servletRequestListener() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); SentinelProperties.Filter filterConfig = properties.getFilter(); diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelDataSource.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelDataSource.java new file mode 100644 index 000000000..ef09eb0ef --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelDataSource.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 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.springframework.cloud.alibaba.sentinel.annotation; + +import java.lang.annotation.*; + +import org.springframework.core.annotation.AliasFor; + +/** + * An annotation to inject {@link com.alibaba.csp.sentinel.datasource.DataSource} instance + * into a Spring Bean. The Properties of DataSource bean get from config files with + * specific prefix. + * + * @author fangjian + * @see com.alibaba.csp.sentinel.datasource.DataSource + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SentinelDataSource { + + @AliasFor("prefix") + String value() default ""; + + @AliasFor("value") + String prefix() default ""; + + String name() default ""; // spring bean name + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java index 7d2a2e10a..623422926 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java @@ -16,20 +16,52 @@ package org.springframework.cloud.alibaba.sentinel.custom; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import com.alibaba.csp.sentinel.util.AppNameUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alibaba.sentinel.SentinelProperties; +import org.springframework.cloud.alibaba.sentinel.datasource.SentinelDataSourcePostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; +import javax.annotation.PostConstruct; + /** * @author xiaojing */ @Configuration +@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) +@EnableConfigurationProperties(SentinelProperties.class) public class SentinelAutoConfiguration { + @Value("${project.name:${spring.application.name:}}") + private String projectName; + + @Autowired + private SentinelProperties properties; + + @PostConstruct + private void init() { + if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME))) { + System.setProperty(AppNameUtil.APP_NAME, projectName); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))) { + System.setProperty(TransportConfig.SERVER_PORT, properties.getPort()); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))) { + System.setProperty(TransportConfig.CONSOLE_SERVER, properties.getDashboard()); + } + } + @Bean @ConditionalOnMissingBean public SentinelResourceAspect sentinelResourceAspect() { @@ -38,9 +70,15 @@ public class SentinelAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnClass(value = RestTemplate.class) + @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") public SentinelBeanPostProcessor sentinelBeanPostProcessor() { return new SentinelBeanPostProcessor(); } + @Bean + @ConditionalOnMissingBean + public SentinelDataSourcePostProcessor sentinelDataSourcePostProcessor() { + return new SentinelDataSourcePostProcessor(); + } + } diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java index 2d2c9ba45..844a5416f 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java @@ -43,7 +43,7 @@ import com.alibaba.csp.sentinel.util.StringUtil; */ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { - private static final Logger LOGGER = LoggerFactory + private static final Logger logger = LoggerFactory .getLogger(SentinelProtectInterceptor.class); private SentinelProtect sentinelProtect; @@ -68,12 +68,12 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor response = execution.execute(request, body); } catch (BlockException e) { - LOGGER.error("RestTemplate block", e); + logger.error("RestTemplate block", e); try { handleBlockException(e); } catch (Exception ex) { - LOGGER.error("sentinel handle BlockException error.", e); + logger.error("sentinel handle BlockException error.", e); } } finally { diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java new file mode 100644 index 000000000..7ac89dfc9 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 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.springframework.cloud.alibaba.sentinel.datasource; + +import static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import com.alibaba.csp.sentinel.datasource.DataSource; + +/** + * {@link DataSource} Loader + * + * @author fangjian + */ +public class DataSourceLoader { + + private static final Logger logger = LoggerFactory.getLogger(DataSourceLoader.class); + + private final static String PROPERTIES_RESOURCE_LOCATION = "META-INF/sentinel-datasource.properties"; + + private final static String ALL_PROPERTIES_RESOURCES_LOCATION = CLASSPATH_ALL_URL_PREFIX + + PROPERTIES_RESOURCE_LOCATION; + + private final static ConcurrentMap> dataSourceClassesCache = new ConcurrentHashMap>( + 4); + + static void loadAllDataSourceClassesCache() { + Map> dataSourceClassesMap = loadAllDataSourceClassesCache( + ALL_PROPERTIES_RESOURCES_LOCATION); + + dataSourceClassesCache.putAll(dataSourceClassesMap); + } + + static Map> loadAllDataSourceClassesCache( + String resourcesLocation) { + + Map> dataSourcesMap = new HashMap>( + 4); + + ClassLoader classLoader = DataSourceLoader.class.getClassLoader(); + + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + try { + + Resource[] resources = resolver.getResources(resourcesLocation); + + for (Resource resource : resources) { + if (resource.exists()) { + Properties properties = PropertiesLoaderUtils + .loadProperties(resource); + for (Map.Entry entry : properties.entrySet()) { + + String type = (String) entry.getKey(); + String className = (String) entry.getValue(); + + if (!ClassUtils.isPresent(className, classLoader)) { + if (logger.isDebugEnabled()) { + logger.debug( + "Sentinel DataSource implementation [ type : " + + type + ": , class : " + className + + " , url : " + resource.getURL() + + "] was not present in current classpath , " + + "thus loading will be ignored , please add dependency if required !"); + } + continue; + } + + Assert.isTrue(!dataSourcesMap.containsKey(type), + "The duplicated type[" + type + + "] of SentinelDataSource were found in " + + "resource [" + resource.getURL() + "]"); + + Class dataSourceClass = ClassUtils.resolveClassName(className, + classLoader); + Assert.isAssignable(DataSource.class, dataSourceClass); + + dataSourcesMap.put(type, + (Class) dataSourceClass); + + if (logger.isDebugEnabled()) { + logger.debug("Sentinel DataSource implementation [ type : " + + type + ": , class : " + className + + "] was loaded."); + } + } + } + } + + } + catch (IOException e) { + if (logger.isErrorEnabled()) { + logger.error(e.getMessage(), e); + } + } + + return dataSourcesMap; + } + + public static Class loadClass(String type) + throws IllegalArgumentException { + + Class dataSourceClass = dataSourceClassesCache.get(type); + + if (dataSourceClass == null) { + if (dataSourceClassesCache.isEmpty()) { + loadAllDataSourceClassesCache(); + dataSourceClass = dataSourceClassesCache.get(type); + } + } + + if (dataSourceClass == null) { + throw new IllegalArgumentException( + "Sentinel DataSource implementation [ type : " + type + + " ] can't be found!"); + } + + return dataSourceClass; + + } + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java new file mode 100644 index 000000000..05914b7d2 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 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.springframework.cloud.alibaba.sentinel.datasource; + +import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.cloud.alibaba.sentinel.SentinelConstants; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelDataSource; +import org.springframework.cloud.alibaba.sentinel.util.PropertySourcesUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import com.alibaba.csp.sentinel.datasource.ConfigParser; + +/** + * {@link SentinelDataSource @SentinelDataSource} Post Processor + * + * @author fangjian + * @see com.alibaba.csp.sentinel.datasource.DataSource + * @see SentinelDataSource + */ +public class SentinelDataSourcePostProcessor + extends InstantiationAwareBeanPostProcessorAdapter + implements MergedBeanDefinitionPostProcessor { + + private static final Logger logger = LoggerFactory + .getLogger(SentinelDataSourcePostProcessor.class); + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private ConfigurableEnvironment environment; + + private final Map> dataSourceFieldCache = new ConcurrentHashMap<>( + 64); + + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, + Class beanType, String beanName) { + // find all fields using by @SentinelDataSource annotation + ReflectionUtils.doWithFields(beanType, new ReflectionUtils.FieldCallback() { + @Override + public void doWith(Field field) + throws IllegalArgumentException, IllegalAccessException { + SentinelDataSource annotation = getAnnotation(field, + SentinelDataSource.class); + if (annotation != null) { + if (Modifier.isStatic(field.getModifiers())) { + if (logger.isWarnEnabled()) { + logger.warn( + "@SentinelDataSource annotation is not supported on static fields: " + + field); + } + return; + } + if (dataSourceFieldCache.containsKey(beanName)) { + dataSourceFieldCache.get(beanName) + .add(new SentinelDataSourceField(annotation, field)); + } + else { + List list = new ArrayList<>(); + list.add(new SentinelDataSourceField(annotation, field)); + dataSourceFieldCache.put(beanName, list); + } + } + } + }); + } + + @Override + public PropertyValues postProcessPropertyValues(PropertyValues pvs, + PropertyDescriptor[] pds, Object bean, String beanName) + throws BeanCreationException { + if (dataSourceFieldCache.containsKey(beanName)) { + List sentinelDataSourceFields = dataSourceFieldCache + .get(beanName); + sentinelDataSourceFields.forEach(sentinelDataSourceField -> { + try { + // construct DataSource field annotated by @SentinelDataSource + Field field = sentinelDataSourceField.getField(); + ReflectionUtils.makeAccessible(field); + String dataSourceBeanName = constructDataSource( + sentinelDataSourceField.getSentinelDataSource()); + field.set(bean, applicationContext.getBean(dataSourceBeanName)); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + catch (Exception e) { + e.printStackTrace(); + } + }); + } + return pvs; + } + + private String constructDataSource(SentinelDataSource annotation) { + String prefix = annotation.value(); + if (StringUtils.isEmpty(prefix)) { + prefix = SentinelConstants.PROPERTY_DATASOURCE_PREFIX; + } + Map propertyMap = PropertySourcesUtils + .getSubProperties(environment.getPropertySources(), prefix); + String alias = propertyMap.get("type").toString(); + Class dataSourceClass = DataSourceLoader.loadClass(alias); + + String beanName = StringUtils.isEmpty(annotation.name()) + ? StringUtils.uncapitalize(dataSourceClass.getSimpleName()) + "_" + prefix + : annotation.name(); + if (applicationContext.containsBean(beanName)) { + return beanName; + } + + Class targetClass = null; + // if alias exists in SentinelDataSourceRegistry, wired properties into + // FactoryBean + if (SentinelDataSourceRegistry.checkFactoryBean(alias)) { + targetClass = SentinelDataSourceRegistry.getFactoryBean(alias); + } + else { + // if alias not exists in SentinelDataSourceRegistry, wired properties into + // raw class + targetClass = dataSourceClass; + } + + registerDataSource(beanName, targetClass, propertyMap); + + return beanName; + } + + private void registerDataSource(String beanName, Class targetClass, + Map propertyMap) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(targetClass); + for (String propertyName : propertyMap.keySet()) { + Field field = ReflectionUtils.findField(targetClass, propertyName); + if (field != null) { + if (field.getType().isAssignableFrom(ConfigParser.class)) { + // ConfigParser get from ApplicationContext + builder.addPropertyReference(propertyName, + propertyMap.get(propertyName).toString()); + } + else { + // wired properties + builder.addPropertyValue(propertyName, propertyMap.get(propertyName)); + } + } + } + + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext + .getAutowireCapableBeanFactory(); + beanFactory.registerBeanDefinition(beanName, builder.getBeanDefinition()); + } + + class SentinelDataSourceField { + private SentinelDataSource sentinelDataSource; + private Field field; + + public SentinelDataSourceField(SentinelDataSource sentinelDataSource, + Field field) { + this.sentinelDataSource = sentinelDataSource; + this.field = field; + } + + public SentinelDataSource getSentinelDataSource() { + return sentinelDataSource; + } + + public void setSentinelDataSource(SentinelDataSource sentinelDataSource) { + this.sentinelDataSource = sentinelDataSource; + } + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + } + } + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java new file mode 100644 index 000000000..9bd92eee5 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 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.springframework.cloud.alibaba.sentinel.datasource; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean; +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean; +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.NacosDataSourceFactoryBean; +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean; + +/** + * Registry to save DataSource FactoryBean + * + * @author fangjian + * @see com.alibaba.csp.sentinel.datasource.DataSource + * @see FileRefreshableDataSourceFactoryBean + * @see ZookeeperDataSourceFactoryBean + * @see NacosDataSourceFactoryBean + * @see ApolloDataSourceFactoryBean + */ +public class SentinelDataSourceRegistry { + + private static ConcurrentHashMap> cache = new ConcurrentHashMap<>( + 32); + + static { + SentinelDataSourceRegistry.registerFactoryBean("file", + FileRefreshableDataSourceFactoryBean.class); + SentinelDataSourceRegistry.registerFactoryBean("zk", + ZookeeperDataSourceFactoryBean.class); + SentinelDataSourceRegistry.registerFactoryBean("nacos", + NacosDataSourceFactoryBean.class); + SentinelDataSourceRegistry.registerFactoryBean("apollo", + ApolloDataSourceFactoryBean.class); + } + + public static synchronized void registerFactoryBean(String alias, + Class clazz) { + cache.putIfAbsent(alias, clazz); + cache.put(alias, clazz); + } + + public static Class getFactoryBean(String alias) { + return cache.get(alias); + } + + public static boolean checkFactoryBean(String alias) { + return cache.containsKey(alias); + } + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java new file mode 100644 index 000000000..48458f82b --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java @@ -0,0 +1,61 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; + +import org.springframework.beans.factory.FactoryBean; + +import com.alibaba.csp.sentinel.datasource.ConfigParser; +import com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource; + +/** + * @author fangjian + * @see ApolloDataSource + */ +public class ApolloDataSourceFactoryBean implements FactoryBean { + + private String namespaceName; + private String flowRulesKey; + private String defaultFlowRuleValue; + private ConfigParser configParser; + + @Override + public ApolloDataSource getObject() throws Exception { + return new ApolloDataSource(namespaceName, flowRulesKey, defaultFlowRuleValue, + configParser); + } + + @Override + public Class getObjectType() { + return ApolloDataSource.class; + } + + public String getNamespaceName() { + return namespaceName; + } + + public void setNamespaceName(String namespaceName) { + this.namespaceName = namespaceName; + } + + public String getFlowRulesKey() { + return flowRulesKey; + } + + public void setFlowRulesKey(String flowRulesKey) { + this.flowRulesKey = flowRulesKey; + } + + public String getDefaultFlowRuleValue() { + return defaultFlowRuleValue; + } + + public void setDefaultFlowRuleValue(String defaultFlowRuleValue) { + this.defaultFlowRuleValue = defaultFlowRuleValue; + } + + public ConfigParser getConfigParser() { + return configParser; + } + + public void setConfigParser(ConfigParser configParser) { + this.configParser = configParser; + } +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java new file mode 100644 index 000000000..610fb801e --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java @@ -0,0 +1,74 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; + +import java.io.File; +import java.nio.charset.Charset; + +import org.springframework.beans.factory.FactoryBean; + +import com.alibaba.csp.sentinel.datasource.ConfigParser; +import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; + +/** + * @author fangjian + * @see FileRefreshableDataSource + */ +public class FileRefreshableDataSourceFactoryBean + implements FactoryBean { + + private String file; + private String charset; + private long recommendRefreshMs; + private int bufSize; + private ConfigParser configParser; + + @Override + public FileRefreshableDataSource getObject() throws Exception { + return new FileRefreshableDataSource(new File(file), configParser, + recommendRefreshMs, bufSize, Charset.forName(charset)); + } + + @Override + public Class getObjectType() { + return FileRefreshableDataSource.class; + } + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public long getRecommendRefreshMs() { + return recommendRefreshMs; + } + + public void setRecommendRefreshMs(long recommendRefreshMs) { + this.recommendRefreshMs = recommendRefreshMs; + } + + public int getBufSize() { + return bufSize; + } + + public void setBufSize(int bufSize) { + this.bufSize = bufSize; + } + + public ConfigParser getConfigParser() { + return configParser; + } + + public void setConfigParser(ConfigParser configParser) { + this.configParser = configParser; + } +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java new file mode 100644 index 000000000..0fdeeebf8 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java @@ -0,0 +1,60 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; + +import org.springframework.beans.factory.FactoryBean; + +import com.alibaba.csp.sentinel.datasource.ConfigParser; +import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; + +/** + * @author fangjian + * @see NacosDataSource + */ +public class NacosDataSourceFactoryBean implements FactoryBean { + + private String serverAddr; + private String groupId; + private String dataId; + private ConfigParser configParser; + + @Override + public NacosDataSource getObject() throws Exception { + return new NacosDataSource(serverAddr, groupId, dataId, configParser); + } + + @Override + public Class getObjectType() { + return NacosDataSource.class; + } + + public String getServerAddr() { + return serverAddr; + } + + public void setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public ConfigParser getConfigParser() { + return configParser; + } + + public void setConfigParser(ConfigParser configParser) { + this.configParser = configParser; + } +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java new file mode 100644 index 000000000..b0ea5aaa0 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java @@ -0,0 +1,80 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.FactoryBean; + +import com.alibaba.csp.sentinel.datasource.ConfigParser; +import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource; + +/** + * @author fangjian + * @see ZookeeperDataSource + */ +public class ZookeeperDataSourceFactoryBean implements FactoryBean { + + private String serverAddr; + + private String path; + + private String groupId; + private String dataId; + + private ConfigParser configParser; + + @Override + public ZookeeperDataSource getObject() throws Exception { + if (StringUtils.isNotEmpty(groupId) && StringUtils.isNotEmpty(dataId)) { + // the path will be /{groupId}/{dataId} + return new ZookeeperDataSource(serverAddr, groupId, dataId, configParser); + } + else { + // using path directly + return new ZookeeperDataSource(serverAddr, path, configParser); + } + } + + @Override + public Class getObjectType() { + return ZookeeperDataSource.class; + } + + public String getServerAddr() { + return serverAddr; + } + + public void setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public ConfigParser getConfigParser() { + return configParser; + } + + public void setConfigParser(ConfigParser configParser) { + this.configParser = configParser; + } +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/util/PropertySourcesUtils.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/util/PropertySourcesUtils.java new file mode 100644 index 000000000..6eafbd63b --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/util/PropertySourcesUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 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.springframework.cloud.alibaba.sentinel.util; + +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.PropertySources; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +/** + * {@link PropertySources} Utilities + * + * @author Mercy + */ +public abstract class PropertySourcesUtils { + + /** + * Get Sub {@link Properties} + * + * @param propertySources {@link PropertySource} Iterable + * @param prefix the prefix of property name + * @return Map + * @see Properties + */ + public static Map getSubProperties(Iterable> propertySources, String prefix) { + + Map subProperties = new LinkedHashMap(); + + String normalizedPrefix = normalizePrefix(prefix); + + for (PropertySource source : propertySources) { + if (source instanceof EnumerablePropertySource) { + for (String name : ((EnumerablePropertySource) source).getPropertyNames()) { + if (!subProperties.containsKey(name) && name.startsWith(normalizedPrefix)) { + String subName = name.substring(normalizedPrefix.length()); + if (!subProperties.containsKey(subName)) { // take first one + Object value = source.getProperty(name); + subProperties.put(subName, value); + } + } + } + } + } + + return subProperties; + + } + + /** + * Normalize the prefix + * + * @param prefix the prefix + * @return the prefix + */ + public static String normalizePrefix(String prefix) { + return prefix.endsWith(".") ? prefix : prefix + "."; + } +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/sentinel-datasource.properties b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/sentinel-datasource.properties new file mode 100644 index 000000000..8326eff63 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/sentinel-datasource.properties @@ -0,0 +1,4 @@ +nacos = com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource +file =com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource +apollo = com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource +zk = com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource \ No newline at end of file diff --git a/spring-cloud-starter-sentinel/pom.xml b/spring-cloud-starter-sentinel/pom.xml index 5d9217e03..5399ff130 100644 --- a/spring-cloud-starter-sentinel/pom.xml +++ b/spring-cloud-starter-sentinel/pom.xml @@ -15,6 +15,18 @@ org.springframework.cloud spring-cloud-alibaba-sentinel-autoconfigure + + com.alibaba.csp + sentinel-datasource-nacos + + + com.alibaba.csp + sentinel-datasource-zookeeper + + + com.alibaba.csp + sentinel-datasource-apollo +