mirror of
https://gitee.com/mirrors/Spring-Cloud-Alibaba.git
synced 2021-06-26 13:25:11 +08:00
sentinel datasource refactor: including code refactor and update examples, docs
This commit is contained in:
parent
5403fe3165
commit
10bb95c11a
@ -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
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
下面这个例子就是一个最简单的使用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<String, List<FlowRule>> {
|
||||
@Override
|
||||
public List<FlowRule> convert(String source) {
|
||||
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个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
|
||||
|====
|
||||
|
@ -31,6 +31,24 @@
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.alibaba.csp</groupId>-->
|
||||
<!--<artifactId>sentinel-datasource-nacos</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.alibaba.csp</groupId>-->
|
||||
<!--<artifactId>sentinel-datasource-zookeeper</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.alibaba.csp</groupId>-->
|
||||
<!--<artifactId>sentinel-datasource-apollo</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
<!-- define in spring-boot-autoconfigure module -->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.fasterxml.jackson.dataformat</groupId>-->
|
||||
<!--<artifactId>jackson-dataformat-xml</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -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)。
|
||||
|
@ -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.**
|
||||
|
||||
<p align="center"><img src="https://cdn.nlark.com/lark/0/2018/png/54319/1532315951819-9ffd959e-0547-4f61-8f06-91374cfe7f21.png" width="1000" heigh='400' ></p>
|
||||
|
||||
@ -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).
|
||||
|
@ -10,7 +10,7 @@ import com.alibaba.fastjson.TypeReference;
|
||||
/**
|
||||
* @author fangjian
|
||||
*/
|
||||
public class JsonFlowRuleListParser implements Converter<String, List<FlowRule>> {
|
||||
public class JsonFlowRuleListConverter implements Converter<String, List<FlowRule>> {
|
||||
@Override
|
||||
public List<FlowRule> convert(String source) {
|
||||
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
|
@ -27,7 +27,7 @@ public class ServiceApplication {
|
||||
|
||||
@Bean
|
||||
public Converter myParser() {
|
||||
return new JsonFlowRuleListParser();
|
||||
return new JsonFlowRuleListConverter();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -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
|
||||
#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
|
@ -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
|
||||
}
|
||||
]
|
@ -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
|
||||
}
|
||||
]
|
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Rules>
|
||||
<FlowRule>
|
||||
<resource>resource</resource>
|
||||
<controlBehavior>0</controlBehavior>
|
||||
<count>1</count>
|
||||
<grade>1</grade>
|
||||
<limitApp>default</limitApp>
|
||||
<strategy>0</strategy>
|
||||
</FlowRule>
|
||||
<FlowRule>
|
||||
<resource>test</resource>
|
||||
<controlBehavior>0</controlBehavior>
|
||||
<count>1</count>
|
||||
<grade>1</grade>
|
||||
<limitApp>default</limitApp>
|
||||
<strategy>0</strategy>
|
||||
</FlowRule>
|
||||
</Rules>
|
||||
|
||||
|
@ -41,8 +41,27 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--spring boot-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
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<String, Class<? extends ReadableDataSource>> dataSourceClassesCache
|
||||
= new ConcurrentHashMap<String, Class<? extends ReadableDataSource>>(
|
||||
4);
|
||||
|
||||
static void loadAllDataSourceClassesCache() {
|
||||
Map<String, Class<? extends ReadableDataSource>> dataSourceClassesMap = loadAllDataSourceClassesCache(
|
||||
ALL_PROPERTIES_RESOURCES_LOCATION);
|
||||
|
||||
dataSourceClassesCache.putAll(dataSourceClassesMap);
|
||||
}
|
||||
|
||||
static Map<String, Class<? extends ReadableDataSource>> loadAllDataSourceClassesCache(
|
||||
String resourcesLocation) {
|
||||
|
||||
Map<String, Class<? extends ReadableDataSource>> dataSourcesMap
|
||||
= new HashMap<String, Class<? extends ReadableDataSource>>(
|
||||
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<Object, Object> 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<? extends ReadableDataSource>)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<? extends ReadableDataSource> loadClass(String type)
|
||||
throws IllegalArgumentException {
|
||||
|
||||
Class<? extends ReadableDataSource> 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @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<String, List<SentinelDataSourceField>> 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<SentinelDataSourceField> 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<SentinelDataSourceField> 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<String, Object> 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<String, Object> 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<String, ReadableDataSource> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @see ReadableDataSource
|
||||
* @see FileRefreshableDataSourceFactoryBean
|
||||
* @see ZookeeperDataSourceFactoryBean
|
||||
* @see NacosDataSourceFactoryBean
|
||||
* @see ApolloDataSourceFactoryBean
|
||||
*/
|
||||
public class SentinelDataSourceRegistry {
|
||||
|
||||
private static HashMap<String, Class<? extends FactoryBean>> 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<? extends FactoryBean> clazz) {
|
||||
cache.put(alias, clazz);
|
||||
}
|
||||
|
||||
public static Class<? extends FactoryBean> getFactoryBean(String alias) {
|
||||
return cache.get(alias);
|
||||
}
|
||||
|
||||
public static boolean checkFactoryBean(String alias) {
|
||||
return cache.containsKey(alias);
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @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
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package org.springframework.cloud.alibaba.sentinel.datasource.config;
|
||||
|
||||
/**
|
||||
* Abstract class Using by {@link DataSourcePropertiesConfiguration}
|
||||
*
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @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<String> getInvalidField() {
|
||||
List<String> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @see FlowRule
|
||||
* @see DegradeRule
|
||||
* @see SystemRule
|
||||
* @see AuthorityRule
|
||||
* @see ObjectMapper
|
||||
*/
|
||||
public class JsonConverter implements Converter<String, List<AbstractRule>> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JsonConverter.class);
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JsonConverter(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbstractRule> convert(String source) {
|
||||
List<AbstractRule> 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<List<HashMap>>() {
|
||||
});
|
||||
|
||||
for (Object obj : jsonArray) {
|
||||
String itemJson = null;
|
||||
try {
|
||||
itemJson = objectMapper.writeValueAsString(obj);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
// won't be happen
|
||||
}
|
||||
|
||||
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemJson),
|
||||
convertDegradeRule(itemJson), convertSystemRule(itemJson),
|
||||
convertAuthorityRule(itemJson));
|
||||
|
||||
List<AbstractRule> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @see FlowRule
|
||||
* @see DegradeRule
|
||||
* @see SystemRule
|
||||
* @see AuthorityRule
|
||||
* @see XmlMapper
|
||||
*/
|
||||
public class XmlConverter implements Converter<String, List<AbstractRule>> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(XmlConverter.class);
|
||||
|
||||
private final XmlMapper xmlMapper;
|
||||
|
||||
public XmlConverter(XmlMapper xmlMapper) {
|
||||
this.xmlMapper = xmlMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbstractRule> convert(String source) {
|
||||
List<AbstractRule> 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<List<HashMap>>() {
|
||||
});
|
||||
|
||||
for (Object obj : xmlArray) {
|
||||
String itemXml = null;
|
||||
try {
|
||||
itemXml = xmlMapper.writeValueAsString(obj);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
// won't be happen
|
||||
}
|
||||
|
||||
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemXml),
|
||||
convertDegradeRule(itemXml), convertSystemRule(itemXml),
|
||||
convertAuthorityRule(itemXml));
|
||||
|
||||
List<AbstractRule> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
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<String, Object> getSubProperties(Iterable<PropertySource<?>> propertySources, String prefix) {
|
||||
|
||||
Map<String, Object> subProperties = new LinkedHashMap<String, Object>();
|
||||
|
||||
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 + ".";
|
||||
}
|
||||
}
|
@ -40,6 +40,12 @@
|
||||
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--spring boot-->
|
||||
|
||||
<dependency>
|
||||
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
@ConfigurationProperties(prefix = SentinelConstants.PROPERTY_PREFIX)
|
||||
public class SentinelProperties {
|
||||
|
||||
/**
|
||||
* 是否提前初始化心跳连接
|
||||
*/
|
||||
private boolean eager = false;
|
||||
/**
|
||||
* earlier initialize heart-beat when the spring container starts <note> when the
|
||||
* transport dependency is on classpath ,the configuration is effective </note>
|
||||
*/
|
||||
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<String, DataSourcePropertiesConfiguration> 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 <note> when the application is web ,the configuration is
|
||||
* effective </note>
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private Servlet servlet = new Servlet();
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private Flow flow = new Flow();
|
||||
/**
|
||||
* sentinel filter <note> when the application is web ,the configuration is effective
|
||||
* </note>
|
||||
*/
|
||||
@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<String, DataSourcePropertiesConfiguration> getDatasource() {
|
||||
return datasource;
|
||||
}
|
||||
|
||||
public String getColdFactor() {
|
||||
return coldFactor;
|
||||
}
|
||||
public void setDatasource(Map<String, DataSourcePropertiesConfiguration> 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<String> 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<String> getUrlPatterns() {
|
||||
return urlPatterns;
|
||||
}
|
||||
/**
|
||||
* URL pattern for sentinel filter,default is /*
|
||||
*/
|
||||
private List<String> urlPatterns;
|
||||
|
||||
public void setUrlPatterns(List<String> urlPatterns) {
|
||||
this.urlPatterns = urlPatterns;
|
||||
}
|
||||
}
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public List<String> getUrlPatterns() {
|
||||
return urlPatterns;
|
||||
}
|
||||
|
||||
public void setUrlPatterns(List<String> urlPatterns) {
|
||||
this.urlPatterns = urlPatterns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
/**
|
||||
* PostProcessor handle @SentinelProtect Annotation, add interceptor for RestTemplate
|
||||
*
|
||||
* @author fangjian
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @see SentinelProtect
|
||||
* @see SentinelProtectInterceptor
|
||||
*/
|
||||
|
@ -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 <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @see SentinelProperties#datasource
|
||||
* @see JsonConverter
|
||||
* @see XmlConverter
|
||||
*/
|
||||
public class SentinelDataSourceHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(SentinelDataSourceHandler.class);
|
||||
|
||||
private List<String> dataTypeList = Arrays.asList("json", "xml");
|
||||
|
||||
private List<Class<? extends AbstractRule>> rulesList = Arrays.asList(FlowRule.class,
|
||||
DegradeRule.class, SystemRule.class, AuthorityRule.class);
|
||||
|
||||
private List<String> dataSourceBeanNameList = Collections
|
||||
.synchronizedList(new ArrayList<String>());
|
||||
|
||||
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<String, DataSourcePropertiesConfiguration> 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<String, Object> 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<String, Object> 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<Class> 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<String> getDataSourceBeanNameList() {
|
||||
return dataSourceBeanNameList;
|
||||
}
|
||||
|
||||
}
|
@ -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<Map<String, Object>> {
|
||||
@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<Map<String, Object>> {
|
||||
result.put("FlowRules", flowRules);
|
||||
result.put("DegradeRules", degradeRules);
|
||||
result.put("SystemRules", systemRules);
|
||||
result.put("datasources", new HashMap<String, Object>());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user