diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc index 59eaa589..e4c9817f 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc @@ -1 +1,257 @@ == Spring Cloud Alibaba Sentinel + +### Sentinel介绍 + +随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 + +Sentinel 具有以下特征: + + +* *丰富的应用场景*:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。 +* *完备的实时监控*:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 +* *广泛的开源生态*:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 +* *完善的 SPI 扩展点*:Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。 + +### 如何使用Sentinel + +如果要在您的项目中引入Sentinel,使用group ID为 `org.springframework.cloud` 和artifact ID为 `spring-cloud-starter-alibaba-sentinel` 的starter。 + +```xml + + org.springframework.cloud + spring-cloud-starter-alibaba-sentinel + +``` + +下面这个例子就是一个最简单的使用Sentinel的例子: + +```java +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(ServiceApplication.class, args); + } + +} + +@RestController +public class TestController { + + @GetMapping(value = "/hello") + @SentinelResource("hello") + public String hello() { + return "Hello Sentinel"; + } + +} +``` + +@SentinelResource注解用来标识资源是否被限流、降级。上述例子上该注解的属性'hello'表示资源名。 + +@SentinelResource还提供了其它额外的属性如 `blockHandler`,`blockHandlerClass`,`fallback` 用于表示限流或降级的操作,更多内容可以参考 https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81[Sentinel注解支持]。 + +##### Sentinel 控制台 + +Sentinel 控制台提供一个轻量级的控制台,它提供机器发现、单机资源实时监控、集群资源汇总,以及规则管理的功能。您只需要对应用进行简单的配置,就可以使用这些功能。 + +*注意*: 集群资源汇总仅支持 500 台以下的应用集群,有大概 1 - 2 秒的延时。 + +.Sentinel Dashboard +image::https://github.com/alibaba/Sentinel/wiki/image/dashboard.png[] + +开启该功能需要3个步骤: + +###### 获取控制台 + +您可以从 https://github.com/alibaba/Sentinel/releases[release 页面] 下载最新版本的控制台 jar 包。 + +您也可以从最新版本的源码自行构建 Sentinel 控制台: + +* 下载 https://github.com/alibaba/Sentinel/tree/master/sentinel-dashboard[控制台] 工程 +* 使用以下命令将代码打包成一个 fat jar: `mvn clean package` + + +###### 启动控制台 + +Sentinel 控制台是一个标准的SpringBoot应用,以SpringBoot的方式运行jar包即可。 + +```shell +java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar +``` + +如若8080端口冲突,可使用 `-Dserver.port=新端口` 进行设置。 + +#### 配置控制台信息 + +.application.yml +---- +spring: + cloud: + sentinel: + transport: + port: 8719 + dashboard: localhost:8080 +---- + +这里的 `spring.cloud.sentinel.transport.port` 端口配置会在应用对应的机器上启动一个Http Server,该Serve会与Sentinel控制台做交互。比如Sentinel控制台添加了1个限流规则,会把规则数据push给这个Http Server接受,Http Server再将规则注册到Sentinel中。 + +更多Sentinel控制台的使用及问题参考: https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0[Sentinel控制台] + +### Feign支持 + +DOING + +### RestTemplate支持 + +Spring Cloud Alibaba Sentinel支持对 `RestTemplate` 的服务调用使用Sentinel进行保护,在构造 `RestTemplate` bean的时候需要加上 `@SentinelProtect` 注解。 + +```java +@Bean +@SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) +public RestTemplate restTemplate() { + return new RestTemplate(); +} +``` + +`@SentinelProtect` 注解的参数跟 `@SentinelResource` 一致,使用方式也一致。 + +限流的资源规则提供两种粒度: + +* `schema://host:port/path`:协议、主机、端口和路径 + +* `schema://host:port`:协议、主机和端口 + +NOTE: 以 `https://www.taobao.com/test` 这个url为例。对应的资源名有两种粒度,分别是 `https://www.taobao.com:80` 以及 `https://www.taobao.com:80/test` + + +### 动态数据源支持 + +*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前*,要在Spring Cloud Alibaba Sentinel下使用动态数据源,需要3个步骤: + +* 配置文件中定义数据源信息。比如使用文件: + +.application.properties +---- +spring.cloud.sentinel.datasource.type=file +spring.cloud.sentinel.datasource.recommendRefreshMs=3000 +spring.cloud.sentinel.datasource.bufSize=4056196 +spring.cloud.sentinel.datasource.charset=utf-8 +spring.cloud.sentinel.datasource.converter=flowConverter +spring.cloud.sentinel.datasource.file=/Users/you/yourrule.json +---- + +* 创建一个Converter类实现 `com.alibaba.csp.sentinel.datasource.Converter` 接口,并且需要在 `ApplicationContext` 中有该类的一个Bean + +```java +@Component("flowConverter") +public class JsonFlowRuleListParser implements Converter> { + @Override + public List convert(String source) { + return JSON.parseObject(source, new TypeReference>() { + }); + } +} +``` + +这个Converter的bean name需要跟 `application.properties` 配置文件中的converter配置一致 + +* 在任意一个 Spring Bean 中定义一个被 `@SentinelDataSource` 注解修饰的 `ReadableDataSource` 属性 + +```java +@SentinelDataSource("spring.cloud.sentinel.datasource") +private ReadableDataSource dataSource; +``` + +`@SentinelDataSource` 注解的value属性表示数据源在 `application.properties` 配置文件中前缀。 该在例子中,前缀为 `spring.cloud.sentinel.datasource` 。 + +如果 `ApplicationContext` 中存在超过1个 `ReadableDataSource` bean,那么不会加载这些 `ReadableDataSource` 中的任意一个。 只有在 `ApplicationContext` 存在一个 `ReadableDataSource` 的情况下才会生效。 + +如果数据源生效并且规则成功加载,控制台会打印类似如下信息: + +``` +[Sentinel Starter] load 3 flow rules +``` + +*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后*,要在Spring Cloud Alibaba Sentinel下使用动态数据源,只需要1个步骤: + +* 直接在 `application.properties` 配置文件中配置数据源信息即可 + +比如配置了4个数据源: + +``` +spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json + +spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848 +spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel +spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP +spring.cloud.sentinel.datasource.ds2.nacos.data-type=json + +spring.cloud.sentinel.datasource.ds3.zk.path = /Sentinel-Demo/SYSTEM-CODE-DEMO-FLOW +spring.cloud.sentinel.datasource.ds3.zk.server-addr = localhost:2181 + +spring.cloud.sentinel.datasource.ds4.apollo.namespace-name = application +spring.cloud.sentinel.datasource.ds4.apollo.flow-rules-key = sentinel +spring.cloud.sentinel.datasource.ds4.apollo.default-flow-rule-value = test + +``` + +这样配置方式参考了Spring Cloud Stream Binder的配置,内部使用了 `TreeMap` 进行存储,comparator为 `String.CASE_INSENSITIVE_ORDER` 。 + +NOTE: d1, ds2, ds3, ds4 是 `ReadableDataSource` 的名字,可随意编写。后面的 `file` ,`zk` ,`nacos` , `apollo` 就是对应具体的数据源。 它们后面的配置就是这些数据源各自的配置。 + +每种数据源都有两个共同的配置项: `data-type` 和 `converter-class` 。 + +`data-type` 配置项表示 `Converter`,Spring Cloud Alibaba Sentinel默认提供两种内置的值,分别是 `json` 和 `xml` (不填默认是json)。 如果不想使用内置的 `json` 或 `xml` 这两种 `Converter`,可以填写 `custom` 表示自定义 `Converter`,然后再配置 `converter-class` 配置项,该配置项需要写类的全路径名。 + +这两种内置的 `Converter` 只支持解析 json数组 或 xml数组。内部解析的时候会自动判断每个json对象或xml对象属于哪4种Sentinel规则(`FlowRule`,`DegradeRule`,`SystemRule`,`AuthorityRule`)。 + +比如10个规则数组里解析出5个限流规则和5个降级规则。 这种情况下该数据源不会注册,日志里页会进行警告。 + +如果10个规则里有9个限流规则,1个解析报错了。这种情况下日志会警告有个规则格式错误,另外9个限流规则会注册上去。 + +这里json或xml解析用的是 `jackson`。`ObjectMapper` 或 `XmlMapper` 使用默认的配置,遇到不认识的字段会解析报错。 因为不这样做的话限流 json 也会解析成 系统规则(系统规则所有的配置都有默认值),所以需要这样严格解析。 + +当然还有一种情况是json对象或xml对象可能会匹配上所有4种规则(比如json对象里只配了 `resource` 字段,那么会匹配上4种规则),这种情况下日志里会警告,并且过滤掉这个对象。 + +用户使用这种配置的时候只需要填写正确的json或xml就行,有任何不合理的信息都会在日志里打印出来。 + +如果数据源生效并且规则成功加载,控制台会打印类似如下信息: + +``` +[Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule +[Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule +``` + +NOTE: 默认情况下,xml格式是不支持的。需要添加 `jackson-dataformat-xml` 依赖后才会自动生效。 + +关于Sentinel动态数据源的实现原理,参考: https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95[动态规则扩展] + +### Endpoint支持 + +在使用Endpoint特性之前需要在 Maven 中添加 `spring-boot-starter-actuator` 依赖,并在配置中允许 Endpoints 的访问。 + +* Spring Boot 1.x 中添加配置 `management.security.enabled=false`。暴露的endpoint路径为 `/sentinel` +* Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*`。暴露的endpoint路径为 `/actuator/sentinel` + +### More + +下表显示 Spring Cloud Alibaba Sentinel 的所有配置信息: + +:frame: topbot +[width="60%",options="header"] +|==== +^|配置项 ^|含义 ^|默认值 +|`spring.cloud.sentinel.enabled`|Sentinel自动化配置是否生效|true +|`spring.cloud.sentinel.eager`|取消Sentinel控制台懒加载|false +|`spring.cloud.sentinel.charset`|metric文件字符集|UTF-8 +|`spring.cloud.sentinel.transport.port`|应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer|8721 +|`spring.cloud.sentinel.transport.dashboard`|Sentinel 控制台地址| +|`spring.cloud.sentinel.transport.heartbeatIntervalMs`|应用与Sentinel控制台的心跳间隔时间| +|`spring.cloud.sentinel.filter.order`|Servlet Filter的加载顺序。Starter内部会构造这个filter|Integer.MIN_VALUE +|`spring.cloud.sentinel.filter.spring.url-patterns`|数据类型是数组。表示Servlet Filter的url pattern集合|/* +|`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric 单个文件的大小| +|`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric 总文件数量| +|`spring.cloud.sentinel.servlet.blockPage`| 自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL | +|`spring.cloud.sentinel.flow.coldFactor`| https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8[冷启动因子] |3 +|==== diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml index e8ffc685..3fe3b98b 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml @@ -31,6 +31,24 @@ spring-boot-starter-actuator + + + + + + + + + + + + + + + + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md index c07c20e3..af535e6d 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md @@ -196,77 +196,33 @@ Sentinel 控制台支持实时监控查看,您可以通过 Sentinel 控制台 Sentinel 内部提供了[动态规则的扩展实现 ReadableDataSource](https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95#datasource-%E6%89%A9%E5%B1%95)。 -Sentinel starter 整合了目前存在的几类 DataSource。只需要在配置文件中进行相关配置,即可在 Spring 容器中自动注册 DataSource。 +Sentinel starter 整合了目前存在的几类 ReadableDataSource。只需要在配置文件中进行相关配置,即可在 Spring 容器中自动注册 DataSource。 -比如要定义一个 `FileRefreshableDataSource`,配置如下: +比如要定义两个ReadableDataSource,分别是 `FileRefreshableDataSource` 和 `NacosDataSource`,配置如下: - spring.cloud.sentinel.datasource.type=file - spring.cloud.sentinel.datasource.recommendRefreshMs=2000 - spring.cloud.sentinel.datasource.bufSize=2048 - spring.cloud.sentinel.datasource.charset=utf-8 - spring.cloud.sentinel.datasource.converter=myParser - spring.cloud.sentinel.datasource.file=/Users/you/rule.json +```properties +spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json +spring.cloud.sentinel.datasource.ds1.file.data-type=json -然后使用`@SentinelDataSource` 注解修饰 DataSource 即可注入: +spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848 +spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel +spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP +spring.cloud.sentinel.datasource.ds2.nacos.data-type=json +``` - @SentinelDataSource("spring.cloud.sentinel.datasource") - private ReadableDataSource dataSource; +`ds1` 和 `ds2` 表示ReadableDataSource的名称,可随意编写。`ds1` 和 `ds2` 后面的 `file` 和 `nacos` 表示ReadableDataSource的类型。 -`@SentinelDataSource` 注解的 value 属性可以不填。默认值就是 `spring.cloud.sentinel.datasource`。 +目前支持`file`, `nacos`, `zk`, `apollo` 这4种类型。 -`value` 属性代表配置前缀。示例中会去找 `spring.cloud.sentinel.datasource.xxx` 相关的配置。 +其中`nacos`,`zk`,`apollo`这3种类型的使用需要加上对应的依赖`sentinel-datasource-nacos`, `sentinel-datasource-zookeeper`, `sentinel-datasource-apollo`。 -`spring.cloud.sentinel.datasource.type` 就是对应的 DataSource 类型。 +当ReadableDataSource加载规则数据成功的时候,控制台会打印出相应的日志信息: -`spring.cloud.sentinel.datasource.recommendRefreshMs` 里的 `recommendRefreshMs` 对应相关 DataSource 的属性。 +``` +[Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule +[Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule +``` -`spring.cloud.sentinel.datasource.converter`代表 `Converter` 在 Spring 容器里的 name。如果没找到,会抛出异常。 - -type目前支持file, nacos, zk, apollo。其中nacos,zk,apollo的使用需要加上对应的依赖`sentinel-datasource-nacos`, `sentinel-datasource-zookeeper`, `sentinel-datasource-apollo` - -### 自定义DataSource - -自定义DataSource只需要两步。 - -1. 定义DataSource - - public class CustomDataSource implements ReadableDataSource { - private String fieldA; - private String fieldB; - ... - } - -2. 装配DataSource。有两种方式处理。 - - * 直接构造DataSource - - @Bean - public CustomDataSource customDataSource() { - CustomDataSource customDataSource = - new CustomDataSource(); - customDataSource.setFieldA("valueA"); - customDataSource.setFieldB("valueB"); - ... - return customDataSource; - } - - * 在classpath:/META-INF/sentinel-datasource.properties中管理DataSource信息 - - custom = yourpackage.CustomDataSource - - 在application.properties中定义DataSource - - spring.cloud.sentinel.datasource.type = custom - spring.cloud.sentinel.datasource.fieldA = valueA - spring.cloud.sentinel.datasource.fieldB = valueB - - 注意:由于目前Sentinel的AbstractDataSource需要有个Converter作为构造函数中的参数,并且它的子类的构造都是通过多个参数的构造函数构造的。 - 所以目前所有的Sentinel starter中的DataSource都是基于FactoryBean并且通过设置属性构造的。如果有这方面的需求,需要再多加一个registerFactoryBean过程。 - - SentinelDataSourceRegistry.registerFactoryBean("custeom", CustomDataSourceFactoryBean.class); - - 如果自定义DataSource可以注入属性,那么没有必要使用SentinelDataSourceRegistry注册FactoryBean。 - ## More Sentinel 是一款功能强大的中间件,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。此 Demo 仅演示了 使用 Sentinel 作为限流工具的使用,更多 Sentinel 相关的信息,请参考 [Sentinel 项目](https://github.com/alibaba/Sentinel)。 diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme.md b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme.md index 157ad0e8..e3135729 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme.md +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme.md @@ -71,7 +71,7 @@ Before we start the demo, let's learn how to connect Sentinel to a Spring Cloud 2. Start the application in IDE or by building a fatjar. 1. Start in IDE: Find main class `ServiceApplication`, and execute the main method. - 2. Build a fatjar:Execute command `mvn clean package` to build a fatjar,and run command `java -jar sentinel-core-example.jar` to start the application. + 2. Build a fatjar:Execute command `mvn clean package` to build a fatjar, and run command `java -jar sentinel-core-example.jar` to start the application. ### Invoke Service @@ -86,7 +86,7 @@ The screenshot belows shows invoke success: 1. Open http://localhost:8080 in browser, and you can find a Sentinel-Example Application has been registered to the dashboard. - **Note: If you can't find your application in the dashboard, invoke a method that has been defined as a Sentinel Resource,for Sentinel uses lazy load strategy.** + **Note: If you can't find your application in the dashboard, invoke a method that has been defined as a Sentinel Resource, for Sentinel uses lazy load strategy.**

@@ -125,8 +125,8 @@ The screenshot belows shows invoke success: 2. When a custom resource is blocked by Sentinel, the default logic is throw BlockException. - If you want to customize your flow control logic, implement interface `SentinelExceptionHandler`, set @SentinelResource's blockHandler() and blockHandlerClass(). See the code below: - + If you want to customize your flow control logic, implement interface `SentinelExceptionHandler`, set @SentinelResource's blockHandler() and blockHandlerClass(). See the code below: + @SentinelResource(value = "resource", blockHandler = "", blockHandlerClass = ExceptionUtil.class) public String hello() { return "Hello"; @@ -171,71 +171,30 @@ Sentinel provide [ReadableDataSource](https://github.com/alibaba/Sentinel/blob/m Sentinel starter integrated 4 DataSources provided by Sentinel. It will be register into Spring Context if you write some configs in `application.properties`. -If you want to define FileRefreshableDataSource: +If you want to define `FileRefreshableDataSource` and `NacosDataSource`, see the code below: - spring.cloud.sentinel.datasource.type=file - spring.cloud.sentinel.datasource.recommendRefreshMs=2000 - spring.cloud.sentinel.datasource.bufSize=2048 - spring.cloud.sentinel.datasource.charset=utf-8 - spring.cloud.sentinel.datasource.converter=myParser - spring.cloud.sentinel.datasource.file=/Users/you/rule.json - -then use `@SentinelDataSource` to annotate DataSource: - - @SentinelDataSource("spring.cloud.sentinel.datasource") - private ReadableDataSource dataSource; - -The value() of `@SentinelDataSource` is not required, it means the prefix of configuration. Default value is `spring.cloud.sentinel.datasource`. +```properties +spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json +spring.cloud.sentinel.datasource.ds1.file.data-type=json -spring.cloud.sentinel.datasource.type means the type of DataSource. +spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848 +spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel +spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP +spring.cloud.sentinel.datasource.ds2.nacos.data-type=json +``` -spring.cloud.sentinel.datasource.recommendRefreshMs means the recommendRefreshMs property of specified DataSource. +`ds1` and `ds2` means the name of ReadableDataSource, you can write whatever you want. The `file` and `nacos` after name `ds1` and `ds2` means the type of ReadableDataSource. -spring.cloud.sentinel.datasource.converter means the name of spring bean that type is Converter. If the bean is not exists, will throw exception. - -Now datasource type support 4 categories: file, nacos, zk, apollo. If you want to using nacos, zk or apollo, you should add `sentinel-datasource-nacos`, `sentinel-datasource-zookeeper` or `sentinel-datasource-apollo` dependency. +Now ReadableDataSource type support 4 categories: `file`, `nacos`, `zk` and `apollo`. -### User-defined DataSource +If you want to use `nacos`, `zk` or `apollo` ReadableDataSource, you could add `sentinel-datasource-nacos`, `sentinel-datasource-zookeeper` or `sentinel-datasource-apollo` dependency. -User-defined DataSource need 2 steps. +When ReadableDataSource load rule data successfully, console will print some logs: -1. Define DataSource - - public class CustomDataSource implements ReadableDataSource { - private String fieldA; - private String fieldB; - ... - } - -2. Assemble DataSource. There are 2 ways to do this. - - * Construct DataSource directly - - @Bean - public CustomDataSource customDataSource() { - CustomDataSource customDataSource = new CustomDataSource(); - customDataSource.setFieldA("valueA"); - customDataSource.setFieldB("valueB"); - ... - return customDataSource; - } - - * define DataSource metadata in `classpath:/META-INF/sentinel-datasource.properties` - - custom = yourpackage.CustomDataSource - - define configuration in `application.properties` - - spring.cloud.sentinel.datasource.type = custom - spring.cloud.sentinel.datasource.fieldA = valueA - spring.cloud.sentinel.datasource.fieldB = valueB - -Note: The AbstractDataSource of Sentinel need a Converter as a constructor param and the subclass of AbstractDataSource was construct by multi-param constructor. -Now All DataSources in starter was construct by FactoryBean. If you want to do it in this way, you should register FactoryBean by SentinelDataSourceRegistry. - - SentinelDataSourceRegistry.registerFactoryBean("custeom", CustomDataSourceFactoryBean.class); - -It is no need to using SentinelDataSourceRegistry to register FactoryBean if your User-defined DataSource can inject fields. +``` +[Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule +[Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule +``` ## More For more information about Sentinel, see [Sentinel Project](https://github.com/alibaba/Sentinel). diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/JsonFlowRuleListParser.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/JsonFlowRuleListConverter.java similarity index 83% rename from spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/JsonFlowRuleListParser.java rename to spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/JsonFlowRuleListConverter.java index b205c96c..c5cea94d 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/JsonFlowRuleListParser.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/JsonFlowRuleListConverter.java @@ -10,7 +10,7 @@ import com.alibaba.fastjson.TypeReference; /** * @author fangjian */ -public class JsonFlowRuleListParser implements Converter> { +public class JsonFlowRuleListConverter implements Converter> { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java index 8ced5aff..93183382 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java @@ -27,7 +27,7 @@ public class ServiceApplication { @Bean public Converter myParser() { - return new JsonFlowRuleListParser(); + return new JsonFlowRuleListConverter(); } public static void main(String[] args) { diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties index b6117190..19c2868e 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties @@ -1,14 +1,16 @@ spring.application.name=sentinel-example server.port=18083 management.security.enabled=false -spring.cloud.sentinel.transport.port=8721 spring.cloud.sentinel.transport.dashboard=localhost:8080 +spring.cloud.sentinel.eager=true +spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json +spring.cloud.sentinel.datasource.ds1.file.data-type=json -spring.cloud.sentinel.datasource.type=file -spring.cloud.sentinel.datasource.recommendRefreshMs=3000 -spring.cloud.sentinel.datasource.bufSize=4056196 -spring.cloud.sentinel.datasource.charset=utf-8 -spring.cloud.sentinel.datasource.converter=myParser -spring.cloud.sentinel.datasource.file=/Users/you/rule.json \ No newline at end of file +#spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json +#spring.cloud.sentinel.datasource.ds1.file.data-type=custom +#spring.cloud.sentinel.datasource.ds1.file.converter-class=org.springframework.cloud.alibaba.cloud.examples.JsonFlowRuleListConverter + +spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json +spring.cloud.sentinel.datasource.ds2.file.data-type=json \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/degraderule.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/degraderule.json new file mode 100644 index 00000000..5977c5fc --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/degraderule.json @@ -0,0 +1,16 @@ +[ + { + "resource": "abc0", + "count": 20.0, + "grade": 0, + "passCount": 0, + "timeWindow": 10 + }, + { + "resource": "abc1", + "count": 15.0, + "grade": 0, + "passCount": 0, + "timeWindow": 10 + } +] \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json new file mode 100644 index 00000000..679dff4a --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json @@ -0,0 +1,26 @@ +[ + { + "resource": "resource", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + }, + { + "resource": "p", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + }, + { + "resource": "abc", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + } +] diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.xml new file mode 100644 index 00000000..7f1926d3 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.xml @@ -0,0 +1,21 @@ + + + + resource + 0 + 1 + 1 + default + 0 + + + test + 0 + 1 + 1 + default + 0 + + + + diff --git a/spring-cloud-alibaba-sentinel-datasource/pom.xml b/spring-cloud-alibaba-sentinel-datasource/pom.xml index af959677..db4ed997 100644 --- a/spring-cloud-alibaba-sentinel-datasource/pom.xml +++ b/spring-cloud-alibaba-sentinel-datasource/pom.xml @@ -41,8 +41,27 @@ true + + com.fasterxml.jackson.core + jackson-databind + provided + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + provided + + + + org.springframework.boot + spring-boot-configuration-processor + provided + true + + org.springframework.boot spring-boot diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java deleted file mode 100644 index da894ae5..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/DataSourceLoader.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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.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 com.alibaba.csp.sentinel.datasource.ReadableDataSource; - -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 static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; - -/** - * {@link ReadableDataSource} Loader - * - * @author Jim - */ -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(ReadableDataSource.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; - - } - -} \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java deleted file mode 100644 index 73ece0ec..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourcePostProcessor.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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.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 com.alibaba.csp.sentinel.datasource.Converter; -import com.alibaba.csp.sentinel.datasource.ReadableDataSource; -import com.alibaba.csp.sentinel.property.SentinelProperty; -import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; -import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; -import com.alibaba.csp.sentinel.slots.system.SystemRule; -import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; - -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.boot.context.event.ApplicationReadyEvent; -import org.springframework.cloud.alibaba.sentinel.datasource.annotation.SentinelDataSource; -import org.springframework.cloud.alibaba.sentinel.datasource.util.PropertySourcesUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.EventListener; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - -import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; - -/** - * {@link SentinelDataSource @SentinelDataSource} Post Processor - * - * @author Jim - * @see ReadableDataSource - * @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, final 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); - for (SentinelDataSourceField sentinelDataSourceField : sentinelDataSourceFields) { - 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 = SentinelDataSourceConstants.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(Converter.class)) { - // Converter 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()); - } - - @EventListener(classes = ApplicationReadyEvent.class) - public void appStartedListener(ApplicationReadyEvent event) throws Exception { - logger.info("[Sentinel Starter] Start to find ReadableDataSource"); - Map dataSourceMap = event.getApplicationContext().getBeansOfType( - ReadableDataSource.class); - if (dataSourceMap.size() == 1) { - logger.info("[Sentinel Starter] There exists only one ReadableDataSource named {}, start to load rules", - dataSourceMap.keySet().iterator().next()); - ReadableDataSource dataSource = dataSourceMap.values().iterator().next(); - Object ruleConfig = dataSource.loadConfig(); - SentinelProperty sentinelProperty = dataSource.getProperty(); - Integer rulesNum; - if ((rulesNum = checkRuleType(ruleConfig, FlowRule.class)) > 0) { - FlowRuleManager.register2Property(sentinelProperty); - logger.info("[Sentinel Starter] load {} flow rules", rulesNum); - } - if ((rulesNum = checkRuleType(ruleConfig, DegradeRule.class)) > 0) { - DegradeRuleManager.register2Property(sentinelProperty); - logger.info("[Sentinel Starter] load {} degrade rules", rulesNum); - } - if ((rulesNum = checkRuleType(ruleConfig, SystemRule.class)) > 0) { - SystemRuleManager.register2Property(sentinelProperty); - logger.info("[Sentinel Starter] load {} system rules", rulesNum); - } - if ((rulesNum = checkRuleType(ruleConfig, AuthorityRule.class)) > 0) { - AuthorityRuleManager.register2Property(sentinelProperty); - logger.info("[Sentinel Starter] load {} authority rules", rulesNum); - } - } else if (dataSourceMap.size() > 1) { - logger.warn( - "[Sentinel Starter] There exists more than one ReadableDataSource, can not choose which one to load"); - } else { - logger.warn( - "[Sentinel Starter] No ReadableDataSource exists"); - } - } - - private Integer checkRuleType(Object ruleConfig, Class type) { - if (ruleConfig.getClass() == type) { - return 1; - } else if (ruleConfig instanceof List) { - List ruleList = (List)ruleConfig; - List checkList = new ArrayList(); - for (Object rule : ruleList) { - if (rule.getClass() == type) { - checkList.add(rule); - } - } - if (ruleList.size() == checkList.size()) { - return ruleList.size(); - } - } - return -1; - } - - 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-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java deleted file mode 100644 index 5108196b..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelDataSourceRegistry.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.HashMap; - -import com.alibaba.csp.sentinel.datasource.ReadableDataSource; - -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 Jim - * @see ReadableDataSource - * @see FileRefreshableDataSourceFactoryBean - * @see ZookeeperDataSourceFactoryBean - * @see NacosDataSourceFactoryBean - * @see ApolloDataSourceFactoryBean - */ -public class SentinelDataSourceRegistry { - - private static HashMap> cache = new HashMap<>( - 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.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-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/annotation/SentinelDataSource.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/annotation/SentinelDataSource.java deleted file mode 100644 index 1925fbef..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/annotation/SentinelDataSource.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.alibaba.csp.sentinel.datasource.ReadableDataSource; - -import org.springframework.core.annotation.AliasFor; - -/** - * An annotation to inject {@link ReadableDataSource} instance - * into a Spring Bean. The Properties of DataSource bean get from config files with - * specific prefix. - * - * @author Jim - * @see ReadableDataSource - */ -@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-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java new file mode 100644 index 00000000..3aa05be6 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java @@ -0,0 +1,38 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.config; + +/** + * Abstract class Using by {@link DataSourcePropertiesConfiguration} + * + * @author Jim + */ +public class AbstractDataSourceProperties { + + private String dataType = "json"; + private String converterClass; + private final String factoryBeanName; + + public AbstractDataSourceProperties(String factoryBeanName) { + this.factoryBeanName = factoryBeanName; + } + + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public String getConverterClass() { + return converterClass; + } + + public void setConverterClass(String converterClass) { + this.converterClass = converterClass; + } + + public String getFactoryBeanName() { + return factoryBeanName; + } + +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ApolloDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ApolloDataSourceProperties.java new file mode 100644 index 00000000..28d803ea --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ApolloDataSourceProperties.java @@ -0,0 +1,44 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.config; + +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean; + +/** + * Apollo Properties class Using by {@link DataSourcePropertiesConfiguration} and + * {@link ApolloDataSourceFactoryBean} + * + * @author Jim + */ +public class ApolloDataSourceProperties extends AbstractDataSourceProperties { + + private String namespaceName; + private String flowRulesKey; + private String defaultFlowRuleValue; + + public ApolloDataSourceProperties() { + super(ApolloDataSourceFactoryBean.class.getName()); + } + + 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; + } +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/DataSourcePropertiesConfiguration.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/DataSourcePropertiesConfiguration.java new file mode 100644 index 00000000..e6bf51bd --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/DataSourcePropertiesConfiguration.java @@ -0,0 +1,75 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.config; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.util.ObjectUtils; + +/** + * Using By ConfigurationProperties. + * + * @author Jim + * @see NacosDataSourceProperties + * @see ApolloDataSourceProperties + * @see ZookeeperDataSourceProperties + * @see FileDataSourceProperties + */ +public class DataSourcePropertiesConfiguration { + + private FileDataSourceProperties file; + + private NacosDataSourceProperties nacos; + + private ZookeeperDataSourceProperties zk; + + private ApolloDataSourceProperties apollo; + + public FileDataSourceProperties getFile() { + return file; + } + + public void setFile(FileDataSourceProperties file) { + this.file = file; + } + + public NacosDataSourceProperties getNacos() { + return nacos; + } + + public void setNacos(NacosDataSourceProperties nacos) { + this.nacos = nacos; + } + + public ZookeeperDataSourceProperties getZk() { + return zk; + } + + public void setZk(ZookeeperDataSourceProperties zk) { + this.zk = zk; + } + + public ApolloDataSourceProperties getApollo() { + return apollo; + } + + public void setApollo(ApolloDataSourceProperties apollo) { + this.apollo = apollo; + } + + public List getInvalidField() { + List fieldList = new ArrayList<>(); + for (Field field : this.getClass().getDeclaredFields()) { + try { + if (!ObjectUtils.isEmpty(field.get(this))) { + fieldList.add(field.getName()); + } + } + catch (IllegalAccessException e) { + // won't happen + } + } + return fieldList; + } + +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/FileDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/FileDataSourceProperties.java new file mode 100644 index 00000000..ab70b816 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/FileDataSourceProperties.java @@ -0,0 +1,53 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.config; + +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean; + +/** + * File Properties class Using by {@link DataSourcePropertiesConfiguration} and + * {@link FileRefreshableDataSourceFactoryBean} + * + * @author Jim + */ +public class FileDataSourceProperties extends AbstractDataSourceProperties { + + private String file; + private String charset = "utf-8"; + private long recommendRefreshMs = 3000L; + private int bufSize = 1024 * 1024; + + public FileDataSourceProperties() { + super(FileRefreshableDataSourceFactoryBean.class.getName()); + } + + 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; + } +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java new file mode 100644 index 00000000..3448b410 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java @@ -0,0 +1,44 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.config; + +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.NacosDataSourceFactoryBean; + +/** + * Nacos Properties class Using by {@link DataSourcePropertiesConfiguration} and + * {@link NacosDataSourceFactoryBean} + * + * @author Jim + */ +public class NacosDataSourceProperties extends AbstractDataSourceProperties { + + private String serverAddr; + private String groupId; + private String dataId; + + public NacosDataSourceProperties() { + super(NacosDataSourceFactoryBean.class.getName()); + } + + 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; + } +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java new file mode 100644 index 00000000..a1d61667 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java @@ -0,0 +1,56 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.config; + +import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean; + +/** + * Zookeeper Properties class Using by {@link DataSourcePropertiesConfiguration} and + * {@link ZookeeperDataSourceFactoryBean} + * + * @author Jim + */ +public class ZookeeperDataSourceProperties extends AbstractDataSourceProperties { + + public ZookeeperDataSourceProperties() { + super(ZookeeperDataSourceFactoryBean.class.getName()); + } + + private String serverAddr; + + private String path; + + private String groupId; + + private String dataId; + + 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; + } +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java new file mode 100644 index 00000000..b05626a5 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java @@ -0,0 +1,161 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.converter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.alibaba.csp.sentinel.datasource.Converter; +import com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.system.SystemRule; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Convert sentinel rules for json array Using strict mode to parse json + * + * @author Jim + * @see FlowRule + * @see DegradeRule + * @see SystemRule + * @see AuthorityRule + * @see ObjectMapper + */ +public class JsonConverter implements Converter> { + + private static final Logger logger = LoggerFactory.getLogger(JsonConverter.class); + + private final ObjectMapper objectMapper; + + public JsonConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public List convert(String source) { + List ruleList = new ArrayList<>(); + if (StringUtils.isEmpty(source)) { + logger.info( + "Sentinel JsonConverter can not convert rules because source is empty"); + return ruleList; + } + try { + List jsonArray = objectMapper.readValue(source, + new TypeReference>() { + }); + + for (Object obj : jsonArray) { + String itemJson = null; + try { + itemJson = objectMapper.writeValueAsString(obj); + } + catch (JsonProcessingException e) { + // won't be happen + } + + List rules = Arrays.asList(convertFlowRule(itemJson), + convertDegradeRule(itemJson), convertSystemRule(itemJson), + convertAuthorityRule(itemJson)); + + List convertRuleList = new ArrayList<>(); + + for (AbstractRule rule : rules) { + if (!ObjectUtils.isEmpty(rule)) { + convertRuleList.add(rule); + } + } + + if (convertRuleList.size() == 0) { + logger.warn( + "Sentinel JsonConverter can not convert {} to any rules, ignore", + itemJson); + } + else if (convertRuleList.size() > 1) { + logger.warn( + "Sentinel JsonConverter convert {} and match multi rules, ignore", + itemJson); + } + else { + ruleList.add(convertRuleList.get(0)); + } + + } + + if (jsonArray.size() != ruleList.size()) { + logger.warn( + "Sentinel JsonConverter Source list size is not equals to Target List, maybe a " + + "part of json is invalid. Source List: " + jsonArray + + ", Target List: " + ruleList); + } + } + catch (Exception e) { + logger.error("Sentinel JsonConverter convert error: " + e.getMessage()); + throw new RuntimeException( + "Sentinel JsonConverter convert error: " + e.getMessage(), e); + } + logger.info("Sentinel JsonConverter convert {} rules: {}", ruleList.size(), + ruleList); + return ruleList; + } + + private FlowRule convertFlowRule(String json) { + try { + FlowRule rule = objectMapper.readValue(json, FlowRule.class); + if (FlowRuleManager.isValidRule(rule)) { + return rule; + } + } + catch (Exception e) { + // ignore + } + return null; + } + + private DegradeRule convertDegradeRule(String json) { + try { + DegradeRule rule = objectMapper.readValue(json, DegradeRule.class); + if (DegradeRuleManager.isValidRule(rule)) { + return rule; + } + } + catch (Exception e) { + // ignore + } + return null; + } + + private SystemRule convertSystemRule(String json) { + SystemRule rule = null; + try { + rule = objectMapper.readValue(json, SystemRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + + private AuthorityRule convertAuthorityRule(String json) { + AuthorityRule rule = null; + try { + rule = objectMapper.readValue(json, AuthorityRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java new file mode 100644 index 00000000..c67cc645 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java @@ -0,0 +1,160 @@ +package org.springframework.cloud.alibaba.sentinel.datasource.converter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.alibaba.csp.sentinel.datasource.Converter; +import com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.system.SystemRule; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +/** + * Convert sentinel rules for xml array Using strict mode to parse xml + * + * @author Jim + * @see FlowRule + * @see DegradeRule + * @see SystemRule + * @see AuthorityRule + * @see XmlMapper + */ +public class XmlConverter implements Converter> { + + private static final Logger logger = LoggerFactory.getLogger(XmlConverter.class); + + private final XmlMapper xmlMapper; + + public XmlConverter(XmlMapper xmlMapper) { + this.xmlMapper = xmlMapper; + } + + @Override + public List convert(String source) { + List ruleList = new ArrayList<>(); + if (StringUtils.isEmpty(source)) { + logger.info( + "Sentinel XmlConverter can not convert rules because source is empty"); + return ruleList; + } + try { + List xmlArray = xmlMapper.readValue(source, + new TypeReference>() { + }); + + for (Object obj : xmlArray) { + String itemXml = null; + try { + itemXml = xmlMapper.writeValueAsString(obj); + } + catch (JsonProcessingException e) { + // won't be happen + } + + List rules = Arrays.asList(convertFlowRule(itemXml), + convertDegradeRule(itemXml), convertSystemRule(itemXml), + convertAuthorityRule(itemXml)); + + List convertRuleList = new ArrayList<>(); + + for (AbstractRule rule : rules) { + if (!ObjectUtils.isEmpty(rule)) { + convertRuleList.add(rule); + } + } + + if (convertRuleList.size() == 0) { + logger.warn( + "Sentinel XmlConverter can not convert {} to any rules, ignore", + itemXml); + } + else if (convertRuleList.size() > 1) { + logger.warn( + "Sentinel XmlConverter convert {} and match multi rules, ignore", + itemXml); + } + else { + ruleList.add(convertRuleList.get(0)); + } + } + + if (xmlArray.size() != ruleList.size()) { + logger.warn( + "Sentinel XmlConverter Source list size is not equals to Target List, maybe a " + + "part of xml is invalid. Source List: " + xmlArray + + ", Target List: " + ruleList); + } + } + catch (Exception e) { + logger.error("Sentinel XmlConverter convert error: " + e.getMessage()); + throw new RuntimeException( + "Sentinel XmlConverter convert error: " + e.getMessage(), e); + } + logger.info("Sentinel XmlConverter convert {} rules: {}", ruleList.size(), + ruleList); + return ruleList; + } + + private FlowRule convertFlowRule(String xml) { + try { + FlowRule rule = xmlMapper.readValue(xml, FlowRule.class); + if (FlowRuleManager.isValidRule(rule)) { + return rule; + } + } + catch (Exception e) { + // ignore + } + return null; + } + + private DegradeRule convertDegradeRule(String xml) { + try { + DegradeRule rule = xmlMapper.readValue(xml, DegradeRule.class); + if (DegradeRuleManager.isValidRule(rule)) { + return rule; + } + } + catch (Exception e) { + // ignore + } + return null; + } + + private SystemRule convertSystemRule(String xml) { + SystemRule rule = null; + try { + rule = xmlMapper.readValue(xml, SystemRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + + private AuthorityRule convertAuthorityRule(String xml) { + AuthorityRule rule = null; + try { + rule = xmlMapper.readValue(xml, AuthorityRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + +} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/util/PropertySourcesUtils.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/util/PropertySourcesUtils.java deleted file mode 100644 index fde8fc44..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/util/PropertySourcesUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.util; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Properties; - -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.core.env.PropertySources; - -/** - * {@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/pom.xml b/spring-cloud-alibaba-sentinel/pom.xml index a06d6689..31824be2 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -40,6 +40,12 @@ spring-cloud-alibaba-sentinel-datasource + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + provided + + diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java index 49460a98..feac8acc 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java @@ -17,256 +17,285 @@ package org.springframework.cloud.alibaba.sentinel; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration; import org.springframework.core.Ordered; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; + /** * @author xiaojing * @author hengyunabc + * @author jiashuai.xie + * @author Jim */ @ConfigurationProperties(prefix = SentinelConstants.PROPERTY_PREFIX) public class SentinelProperties { - /** - * 是否提前初始化心跳连接 - */ - private boolean eager = false; + /** + * earlier initialize heart-beat when the spring container starts when the + * transport dependency is on classpath ,the configuration is effective + */ + private boolean eager = false; - /** - * Enable sentinel auto configure, the default value is true - */ - private boolean enabled = true; + /** + * enable sentinel auto configure, the default value is true + */ + private boolean enabled = true; - /** - * 字符编码集 - */ - private String charset = "UTF-8"; + /** + * charset when sentinel write or search metric file {@link SentinelConfig#CHARSET} + */ + private String charset = "UTF-8"; - /** - * 通信相关配置 - */ - @NestedConfigurationProperty - private Transport transport = new Transport(); + /** + * configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper' + */ + private Map datasource = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); - /** - * 监控数据相关配置 - */ - @NestedConfigurationProperty - private Metric metric = new Metric(); + /** + * transport configuration about dashboard and client + */ + @NestedConfigurationProperty + private Transport transport = new Transport(); - /** - * web 相关配置 - */ - @NestedConfigurationProperty - private Servlet servlet = new Servlet(); + /** + * metric configuration about resource + */ + @NestedConfigurationProperty + private Metric metric = new Metric(); - /** - * 限流相关 - */ - @NestedConfigurationProperty - private Filter filter = new Filter(); + /** + * web servlet configuration when the application is web ,the configuration is + * effective + */ + @NestedConfigurationProperty + private Servlet servlet = new Servlet(); - @NestedConfigurationProperty - private Flow flow = new Flow(); + /** + * sentinel filter when the application is web ,the configuration is effective + * + */ + @NestedConfigurationProperty + private Filter filter = new Filter(); - public boolean isEager() { - return eager; - } + /** + * flow configuration + */ + @NestedConfigurationProperty + private Flow flow = new Flow(); - public void setEager(boolean eager) { - this.eager = eager; - } + public boolean isEager() { + return eager; + } - public Flow getFlow() { - return flow; - } + public void setEager(boolean eager) { + this.eager = eager; + } - public void setFlow(Flow flow) { - this.flow = flow; - } + public Flow getFlow() { + return flow; + } - public String getCharset() { - return charset; - } + public void setFlow(Flow flow) { + this.flow = flow; + } - public void setCharset(String charset) { - this.charset = charset; - } + public String getCharset() { + return charset; + } - public Transport getTransport() { - return transport; - } + public void setCharset(String charset) { + this.charset = charset; + } - public void setTransport(Transport transport) { - this.transport = transport; - } + public Transport getTransport() { + return transport; + } - public Metric getMetric() { - return metric; - } + public void setTransport(Transport transport) { + this.transport = transport; + } - public void setMetric(Metric metric) { - this.metric = metric; - } + public Metric getMetric() { + return metric; + } - public Servlet getServlet() { - return servlet; - } + public void setMetric(Metric metric) { + this.metric = metric; + } - public void setServlet(Servlet servlet) { - this.servlet = servlet; - } + public Servlet getServlet() { + return servlet; + } - public boolean isEnabled() { - return enabled; - } + public void setServlet(Servlet servlet) { + this.servlet = servlet; + } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + public boolean isEnabled() { + return enabled; + } - public Filter getFilter() { - return filter; - } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } - public void setFilter(Filter filter) { - this.filter = filter; - } + public Filter getFilter() { + return filter; + } - public static class Flow { + public void setFilter(Filter filter) { + this.filter = filter; + } - /** - * 限流冷启动因子 - */ - private String coldFactor = "3"; + public Map getDatasource() { + return datasource; + } - public String getColdFactor() { - return coldFactor; - } + public void setDatasource(Map datasource) { + this.datasource = datasource; + } - public void setColdFactor(String coldFactor) { - this.coldFactor = coldFactor; - } + public static class Flow { - } + /** + * the cold factor {@link SentinelConfig#COLD_FACTOR} + */ + private String coldFactor = "3"; - public static class Servlet { + public String getColdFactor() { + return coldFactor; + } - /** - * url 限流后的处理页面 - */ - private String blockPage; + public void setColdFactor(String coldFactor) { + this.coldFactor = coldFactor; + } - public String getBlockPage() { - return blockPage; - } + } - public void setBlockPage(String blockPage) { - this.blockPage = blockPage; - } - } + public static class Servlet { - public static class Metric { + /** + * The process page when the flow control is triggered + */ + private String blockPage; - /** - * 监控数据写磁盘时单个文件的大小 - */ - private String fileSingleSize; + public String getBlockPage() { + return blockPage; + } - /** - * 监控数据在磁盘上的总数量 - */ - private String fileTotalCount; + public void setBlockPage(String blockPage) { + this.blockPage = blockPage; + } + } - public String getFileSingleSize() { - return fileSingleSize; - } + public static class Metric { - public void setFileSingleSize(String fileSingleSize) { - this.fileSingleSize = fileSingleSize; - } + /** + * the metric file size {@link SentinelConfig#SINGLE_METRIC_FILE_SIZE} + */ + private String fileSingleSize; - public String getFileTotalCount() { - return fileTotalCount; - } + /** + * the total metric file count {@link SentinelConfig#TOTAL_METRIC_FILE_COUNT} + */ + private String fileTotalCount; - public void setFileTotalCount(String fileTotalCount) { - this.fileTotalCount = fileTotalCount; - } - } + public String getFileSingleSize() { + return fileSingleSize; + } - public static class Transport { + public void setFileSingleSize(String fileSingleSize) { + this.fileSingleSize = fileSingleSize; + } - /** - * sentinel api port,default value is 8721 - */ - private String port = "8721"; + public String getFileTotalCount() { + return fileTotalCount; + } - /** - * Sentinel dashboard address, won't try to connect dashboard when address is - * empty - */ - private String dashboard = ""; + public void setFileTotalCount(String fileTotalCount) { + this.fileTotalCount = fileTotalCount; + } + } - /** - * 客户端和DashBord心跳发送时间 - */ - private String heartbeatIntervalMs; + public static class Transport { - public String getHeartbeatIntervalMs() { - return heartbeatIntervalMs; - } + /** + * sentinel api port,default value is 8721 {@link TransportConfig#SERVER_PORT} + */ + private String port = "8721"; - public void setHeartbeatIntervalMs(String heartbeatIntervalMs) { - this.heartbeatIntervalMs = heartbeatIntervalMs; - } + /** + * sentinel dashboard address, won't try to connect dashboard when address is + * empty {@link TransportConfig#CONSOLE_SERVER} + */ + private String dashboard = ""; - public String getPort() { - return port; - } + /** + * send heartbeat interval millisecond + * {@link TransportConfig#HEARTBEAT_INTERVAL_MS} + */ + private String heartbeatIntervalMs; - public void setPort(String port) { - this.port = port; - } + public String getHeartbeatIntervalMs() { + return heartbeatIntervalMs; + } - public String getDashboard() { - return dashboard; - } + public void setHeartbeatIntervalMs(String heartbeatIntervalMs) { + this.heartbeatIntervalMs = heartbeatIntervalMs; + } - public void setDashboard(String dashboard) { - this.dashboard = dashboard; - } + public String getPort() { + return port; + } - } + public void setPort(String port) { + this.port = port; + } - public static class Filter { + public String getDashboard() { + return dashboard; + } - /** - * Sentinel filter chain order. - */ - private int order = Ordered.HIGHEST_PRECEDENCE; + public void setDashboard(String dashboard) { + this.dashboard = dashboard; + } - /** - * URL pattern for sentinel filter,default is /* - */ - private List urlPatterns; + } - public int getOrder() { - return this.order; - } + public static class Filter { - public void setOrder(int order) { - this.order = order; - } + /** + * sentinel filter chain order. + */ + private int order = Ordered.HIGHEST_PRECEDENCE; - public List getUrlPatterns() { - return urlPatterns; - } + /** + * URL pattern for sentinel filter,default is /* + */ + private List urlPatterns; - public void setUrlPatterns(List urlPatterns) { - this.urlPatterns = urlPatterns; - } - } + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + + public List getUrlPatterns() { + return urlPatterns; + } + + public void setUrlPatterns(List urlPatterns) { + this.urlPatterns = urlPatterns; + } + } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java index 9112781b..ea74c449 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java @@ -18,6 +18,20 @@ package org.springframework.cloud.alibaba.sentinel.custom; import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +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.converter.JsonConverter; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; @@ -28,115 +42,131 @@ import com.alibaba.csp.sentinel.init.InitExecutor; 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 com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; /** * @author xiaojing + * @author Jim */ @Configuration @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelAutoConfiguration { - @Value("${project.name:${spring.application.name:}}") - private String projectName; + @Value("${project.name:${spring.application.name:}}") + private String projectName; - @Autowired - private SentinelProperties properties; + @Autowired + private SentinelProperties properties; - @Autowired(required = false) - private UrlCleaner urlCleaner; + @Autowired(required = false) + private UrlCleaner urlCleaner; - @Autowired(required = false) - private UrlBlockHandler urlBlockHandler; + @Autowired(required = false) + private UrlBlockHandler urlBlockHandler; - @PostConstruct - private void init() { - if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME)) - && StringUtils.hasText(projectName)) { - System.setProperty(AppNameUtil.APP_NAME, projectName); - } - if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT)) - && StringUtils.hasText(properties.getTransport().getPort())) { - System.setProperty(TransportConfig.SERVER_PORT, - properties.getTransport().getPort()); - } - if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER)) - && StringUtils.hasText(properties.getTransport().getDashboard())) { - System.setProperty(TransportConfig.CONSOLE_SERVER, - properties.getTransport().getDashboard()); - } - if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS)) - && StringUtils - .hasText(properties.getTransport().getHeartbeatIntervalMs())) { - System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS, - properties.getTransport().getHeartbeatIntervalMs()); - } - if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) - && StringUtils.hasText(properties.getCharset())) { - System.setProperty(SentinelConfig.CHARSET, properties.getCharset()); - } - if (StringUtils - .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) - && StringUtils.hasText(properties.getMetric().getFileSingleSize())) { - System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE, - properties.getMetric().getFileSingleSize()); - } - if (StringUtils - .isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT)) - && StringUtils.hasText(properties.getMetric().getFileTotalCount())) { - System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT, - properties.getMetric().getFileTotalCount()); - } - if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR)) - && StringUtils.hasText(properties.getFlow().getColdFactor())) { - System.setProperty(SentinelConfig.COLD_FACTOR, - properties.getFlow().getColdFactor()); - } + @PostConstruct + private void init() { + if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME)) + && StringUtils.hasText(projectName)) { + System.setProperty(AppNameUtil.APP_NAME, projectName); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT)) + && StringUtils.hasText(properties.getTransport().getPort())) { + System.setProperty(TransportConfig.SERVER_PORT, + properties.getTransport().getPort()); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER)) + && StringUtils.hasText(properties.getTransport().getDashboard())) { + System.setProperty(TransportConfig.CONSOLE_SERVER, + properties.getTransport().getDashboard()); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS)) + && StringUtils + .hasText(properties.getTransport().getHeartbeatIntervalMs())) { + System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS, + properties.getTransport().getHeartbeatIntervalMs()); + } + if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) + && StringUtils.hasText(properties.getCharset())) { + System.setProperty(SentinelConfig.CHARSET, properties.getCharset()); + } + if (StringUtils + .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) + && StringUtils.hasText(properties.getMetric().getFileSingleSize())) { + System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE, + properties.getMetric().getFileSingleSize()); + } + if (StringUtils + .isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT)) + && StringUtils.hasText(properties.getMetric().getFileTotalCount())) { + System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT, + properties.getMetric().getFileTotalCount()); + } + if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR)) + && StringUtils.hasText(properties.getFlow().getColdFactor())) { + System.setProperty(SentinelConfig.COLD_FACTOR, + properties.getFlow().getColdFactor()); + } - if (StringUtils.hasText(properties.getServlet().getBlockPage())) { - WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); - } - if (urlBlockHandler != null) { - WebCallbackManager.setUrlBlockHandler(urlBlockHandler); - } - if (urlCleaner != null) { - WebCallbackManager.setUrlCleaner(urlCleaner); - } + if (StringUtils.hasText(properties.getServlet().getBlockPage())) { + WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); + } + if (urlBlockHandler != null) { + WebCallbackManager.setUrlBlockHandler(urlBlockHandler); + } + if (urlCleaner != null) { + WebCallbackManager.setUrlCleaner(urlCleaner); + } - // earlier initialize - if (properties.isEager()) { - InitExecutor.doInit(); - } - } + // earlier initialize + if (properties.isEager()) { + InitExecutor.doInit(); + } + } - @Bean - @ConditionalOnMissingBean - public SentinelResourceAspect sentinelResourceAspect() { - return new SentinelResourceAspect(); - } + @Bean + @ConditionalOnMissingBean + public SentinelResourceAspect sentinelResourceAspect() { + return new SentinelResourceAspect(); + } - @Bean - @ConditionalOnMissingBean - @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") - public SentinelBeanPostProcessor sentinelBeanPostProcessor() { - return new SentinelBeanPostProcessor(); - } + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") + public SentinelBeanPostProcessor sentinelBeanPostProcessor() { + return new SentinelBeanPostProcessor(); + } - @Bean - @ConditionalOnMissingBean - public SentinelDataSourcePostProcessor sentinelDataSourcePostProcessor() { - return new SentinelDataSourcePostProcessor(); - } + @Bean + public SentinelDataSourceHandler sentinelDataSourceHandler() { + return new SentinelDataSourceHandler(); + } + + @Bean("sentinel-json-converter") + public JsonConverter jsonConverter( + @Qualifier("sentinel-object-mapper") ObjectMapper objectMapper) { + return new JsonConverter(objectMapper); + } + + @Bean("sentinel-object-mapper") + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @ConditionalOnClass(XmlMapper.class) + protected static class SentinelXmlConfiguration { + @Bean("sentinel-xml-converter") + public XmlConverter xmlConverter( + @Qualifier("sentinel-xml-mapper") XmlMapper xmlMapper) { + return new XmlConverter(xmlMapper); + } + + @Bean("sentinel-xml-mapper") + public XmlMapper xmlMapper() { + return new XmlMapper(); + } + } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java index 609cc8d2..fdad9664 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java @@ -34,7 +34,7 @@ import org.springframework.web.client.RestTemplate; /** * PostProcessor handle @SentinelProtect Annotation, add interceptor for RestTemplate * - * @author fangjian + * @author Jim * @see SentinelProtect * @see SentinelProtectInterceptor */ diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java new file mode 100644 index 00000000..4121d198 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java @@ -0,0 +1,353 @@ +/* + * 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.custom; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.cloud.alibaba.sentinel.SentinelProperties; +import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties; +import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; +import org.springframework.context.event.EventListener; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; +import com.alibaba.csp.sentinel.property.SentinelProperty; +import com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.system.SystemRule; +import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; + +/** + * Sentinel {@link ReadableDataSource} Handler Handle the configurations of + * 'spring.cloud.sentinel.datasource' + * + * @author Jim + * @see SentinelProperties#datasource + * @see JsonConverter + * @see XmlConverter + */ +public class SentinelDataSourceHandler { + + private static final Logger logger = LoggerFactory + .getLogger(SentinelDataSourceHandler.class); + + private List dataTypeList = Arrays.asList("json", "xml"); + + private List> rulesList = Arrays.asList(FlowRule.class, + DegradeRule.class, SystemRule.class, AuthorityRule.class); + + private List dataSourceBeanNameList = Collections + .synchronizedList(new ArrayList()); + + private final String DATATYPE_FIELD = "dataType"; + private final String CUSTOM_DATATYPE = "custom"; + private final String CONVERTERCLASS_FIELD = "converterClass"; + + @Autowired + private SentinelProperties sentinelProperties; + + @EventListener(classes = ApplicationReadyEvent.class) + public void buildDataSource(ApplicationReadyEvent event) throws Exception { + + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) event + .getApplicationContext().getAutowireCapableBeanFactory(); + + for (Map.Entry entry : sentinelProperties + .getDatasource().entrySet()) { + String dataSourceName = entry.getKey(); + DataSourcePropertiesConfiguration dataSourceProperties = entry.getValue(); + + if (dataSourceProperties.getInvalidField().size() != 1) { + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " multi datasource active and won't loaded: " + + dataSourceProperties.getInvalidField()); + return; + } + + if (dataSourceProperties.getFile() != null) { + try { + dataSourceProperties.getFile() + .setFile(ResourceUtils + .getFile(StringUtils.trimAllWhitespace( + dataSourceProperties.getFile().getFile())) + .getAbsolutePath()); + } + catch (IOException e) { + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " handle file error: " + e.getMessage()); + throw new RuntimeException("[Sentinel Starter] DataSource " + + dataSourceName + " handle file error: " + e.getMessage(), + e); + } + registerBean(beanFactory, dataSourceProperties.getFile(), + dataSourceName + "-sentinel-file-datasource"); + } + + if (dataSourceProperties.getNacos() != null) { + registerBean(beanFactory, dataSourceProperties.getNacos(), + dataSourceName + "-sentinel-nacos-datasource"); + } + + if (dataSourceProperties.getApollo() != null) { + registerBean(beanFactory, dataSourceProperties.getApollo(), + dataSourceName + "-sentinel-apollo-datasource"); + } + + if (dataSourceProperties.getZk() != null) { + registerBean(beanFactory, dataSourceProperties.getZk(), + dataSourceName + "-sentinel-zk-datasource"); + } + + } + + for (String beanName : dataSourceBeanNameList) { + ReadableDataSource dataSource = beanFactory.getBean(beanName, + ReadableDataSource.class); + Object ruleConfig; + try { + logger.info("[Sentinel Starter] DataSource " + beanName + + " start to loadConfig"); + ruleConfig = dataSource.loadConfig(); + } + catch (Exception e) { + logger.error("[Sentinel Starter] DataSource " + beanName + + " loadConfig error: " + e.getMessage(), e); + return; + } + SentinelProperty sentinelProperty = dataSource.getProperty(); + Class ruleType = getAndCheckRuleType(ruleConfig, beanName); + if (ruleType != null) { + if (ruleType == FlowRule.class) { + FlowRuleManager.register2Property(sentinelProperty); + } + else if (ruleType == DegradeRule.class) { + DegradeRuleManager.register2Property(sentinelProperty); + } + else if (ruleType == SystemRule.class) { + SystemRuleManager.register2Property(sentinelProperty); + } + else { + AuthorityRuleManager.register2Property(sentinelProperty); + } + } + } + } + + private void registerBean(DefaultListableBeanFactory beanFactory, + final AbstractDataSourceProperties dataSourceProperties, + String dataSourceName) { + + Map propertyMap = new HashMap<>(); + for (Field field : dataSourceProperties.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + Object fieldVal = field.get(dataSourceProperties); + if (fieldVal != null) { + propertyMap.put(field.getName(), fieldVal); + } + } + catch (IllegalAccessException e) { + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " field: " + field.getName() + " invoke error"); + throw new RuntimeException("[Sentinel Starter] DataSource " + + dataSourceName + " field: " + field.getName() + " invoke error", + e); + } + } + propertyMap.put(CONVERTERCLASS_FIELD, dataSourceProperties.getConverterClass()); + propertyMap.put(DATATYPE_FIELD, dataSourceProperties.getDataType()); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(dataSourceProperties.getFactoryBeanName()); + + for (Map.Entry entry : propertyMap.entrySet()) { + String propertyName = entry.getKey(); + Object propertyValue = entry.getValue(); + Field field = ReflectionUtils.findField(dataSourceProperties.getClass(), + propertyName); + if (field != null) { + if (DATATYPE_FIELD.equals(propertyName)) { + String dataType = StringUtils + .trimAllWhitespace(propertyValue.toString()); + if (CUSTOM_DATATYPE.equals(dataType)) { + try { + if (StringUtils + .isEmpty(dataSourceProperties.getConverterClass())) { + throw new RuntimeException( + "[Sentinel Starter] DataSource " + dataSourceName + + "dataType is custom, please set converter-class " + + "property"); + } + // construct custom Converter with 'converterClass' + // configuration and register + String customConvertBeanName = "sentinel-" + + dataSourceProperties.getConverterClass(); + if (!beanFactory.containsBean(customConvertBeanName)) { + beanFactory.registerBeanDefinition(customConvertBeanName, + BeanDefinitionBuilder + .genericBeanDefinition( + Class.forName(dataSourceProperties + .getConverterClass())) + .getBeanDefinition()); + } + builder.addPropertyReference("converter", + customConvertBeanName); + } + catch (ClassNotFoundException e) { + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " handle " + + dataSourceProperties.getClass().getSimpleName() + + " error, class name: " + + dataSourceProperties.getConverterClass()); + throw new RuntimeException( + "[Sentinel Starter] DataSource " + dataSourceName + + " handle " + + dataSourceProperties.getClass() + .getSimpleName() + + " error, class name: " + + dataSourceProperties.getConverterClass(), + e); + } + } + else { + if (!dataTypeList.contains(StringUtils + .trimAllWhitespace(propertyValue.toString()))) { + throw new RuntimeException("[Sentinel Starter] DataSource " + + dataSourceName + " dataType: " + propertyValue + + " is not support now. please using these types: " + + dataTypeList.toString()); + } + // converter type now support xml or json. + // The bean name of these converters wrapped by + // 'sentinel-{converterType}-converter' + builder.addPropertyReference("converter", + "sentinel-" + propertyValue.toString() + "-converter"); + } + } + else if (CONVERTERCLASS_FIELD.equals(propertyName)) { + continue; + } + else { + // wired properties + builder.addPropertyValue(propertyName, propertyValue); + } + } + } + + beanFactory.registerBeanDefinition(dataSourceName, builder.getBeanDefinition()); + // init in Spring + beanFactory.getBean(dataSourceName); + dataSourceBeanNameList.add(dataSourceName); + } + + private Class getAndCheckRuleType(Object ruleConfig, String dataSourceName) { + if (rulesList.contains(ruleConfig.getClass())) { + logger.info("[Sentinel Starter] DataSource {} load {} {}", dataSourceName, 1, + ruleConfig.getClass().getSimpleName()); + return ruleConfig.getClass(); + } + else if (ruleConfig instanceof List) { + List convertedRuleList = (List) ruleConfig; + if (CollectionUtils.isEmpty(convertedRuleList)) { + logger.warn("[Sentinel Starter] DataSource {} rule list is empty.", + dataSourceName); + return null; + } + + if (checkRuleTypeValid(convertedRuleList)) { + if (checkAllRuleTypeSame(convertedRuleList)) { + logger.info("[Sentinel Starter] DataSource {} load {} {}", + dataSourceName, convertedRuleList.size(), + convertedRuleList.get(0).getClass().getSimpleName()); + return convertedRuleList.get(0).getClass(); + } + else { + logger.warn( + "[Sentinel Starter] DataSource {} all rules are not same rule type and it will not be used. " + + "Rule List: {}", + dataSourceName, convertedRuleList.toString()); + } + } + else { + List classList = new ArrayList<>(); + for (Object rule : convertedRuleList) { + classList.add(rule.getClass()); + } + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " rule class is invalid. Class List: " + classList); + throw new RuntimeException( + "[Sentinel Starter] DataSource " + dataSourceName + + " rule class is invalid. Class List: " + classList); + } + } + else { + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " rule class is invalid. Class: " + ruleConfig.getClass()); + throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + + " rule class is invalid. Class: " + ruleConfig.getClass()); + } + return null; + } + + private boolean checkRuleTypeValid(List convertedRuleList) { + int matchCount = 0; + for (Object rule : convertedRuleList) { + if (rulesList.contains(rule.getClass())) { + matchCount++; + } + } + return matchCount == convertedRuleList.size(); + } + + private boolean checkAllRuleTypeSame(List convertedRuleList) { + int matchCount = 0; + for(Object rule : convertedRuleList) { + if(rulesList.contains(rule.getClass()) && rule.getClass() == convertedRuleList.get(0).getClass()) { + matchCount ++; + } + } + return matchCount == convertedRuleList.size(); + } + + public List getDataSourceBeanNameList() { + return dataSourceBeanNameList; + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java index 2a71f0b5..05262314 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java @@ -20,15 +20,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.cloud.alibaba.sentinel.SentinelProperties; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelDataSourceHandler; +import org.springframework.context.ApplicationContext; + +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.cloud.alibaba.sentinel.SentinelProperties; /** * Endpoint for Sentinel, contains ans properties and rules @@ -39,6 +43,12 @@ public class SentinelEndpoint extends AbstractEndpoint> { @Autowired private SentinelProperties sentinelProperties; + @Autowired + private SentinelDataSourceHandler dataSourceHandler; + + @Autowired + private ApplicationContext applicationContext; + public SentinelEndpoint() { super("sentinel"); } @@ -54,6 +64,20 @@ public class SentinelEndpoint extends AbstractEndpoint> { result.put("FlowRules", flowRules); result.put("DegradeRules", degradeRules); result.put("SystemRules", systemRules); + result.put("datasources", new HashMap()); + + for (String dataSourceBeanName : dataSourceHandler.getDataSourceBeanNameList()) { + ReadableDataSource dataSource = applicationContext.getBean(dataSourceBeanName, + ReadableDataSource.class); + try { + ((HashMap) result.get("datasources")).put(dataSourceBeanName, + dataSource.loadConfig()); + } + catch (Exception e) { + ((HashMap) result.get("datasources")).put(dataSourceBeanName, + "load error: " + e.getMessage()); + } + } return result; }