mirror of
https://gitee.com/mirrors/Spring-Cloud-Alibaba.git
synced 2021-06-26 13:25:11 +08:00
Merge pull request #142 from fangjian0423/master
Sentinel integration with feign and handle some issues
This commit is contained in:
commit
d874592e3f
@ -89,6 +89,11 @@
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
<version>${sentinel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-extension</artifactId>
|
||||
|
@ -100,7 +100,53 @@ spring:
|
||||
|
||||
### Feign 支持
|
||||
|
||||
DOING
|
||||
Sentinel 适配了 https://github.com/OpenFeign/feign[Feign] 组件。如果想使用,除了引入 `sentinel-starter` 的依赖外还需要 3 个步骤:
|
||||
|
||||
* 配置文件打开 sentinel 对 feign 的支持:`feign.sentinel.enabled=true`
|
||||
* 加入 `feign starter` 依赖触发 `sentinel starter` 的配置类生效:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
* `feign` 内部的 `loadbalance` 功能依赖 Netflix 的 `ribbon` 模块(如果不使用 `loadbalance` 功能可不引入):
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
这是一个 `FeignClient` 对应的接口:
|
||||
|
||||
```java
|
||||
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
|
||||
public interface EchoService {
|
||||
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
|
||||
String echo(@PathVariable("str") String str);
|
||||
}
|
||||
|
||||
class FeignConfiguration {
|
||||
@Bean
|
||||
public EchoServiceFallback echoServiceFallback() {
|
||||
return new EchoServiceFallback();
|
||||
}
|
||||
}
|
||||
|
||||
class EchoServiceFallback implements EchoService {
|
||||
@Override
|
||||
public String echo(@PathVariable("str") String str) {
|
||||
return "echo fallback";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: Feign 对应的接口中的资源名策略定义:httpmethod:http://requesturl
|
||||
|
||||
`EchoService` 接口中方法 `echo` 对应的资源名为 `GET:http://service-provider/echo/{str}`。
|
||||
|
||||
请注意:`@FeignClient` 注解中的所有属性,Sentinel 都做了兼容。
|
||||
|
||||
### RestTemplate 支持
|
||||
|
||||
@ -124,10 +170,11 @@ public RestTemplate restTemplate() {
|
||||
|
||||
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个步骤:
|
||||
#### 在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前
|
||||
|
||||
需要3个步骤才可完成数据源的配置:
|
||||
|
||||
* 配置文件中定义数据源信息。比如使用文件:
|
||||
|
||||
@ -173,7 +220,9 @@ private ReadableDataSource dataSource;
|
||||
[Sentinel Starter] load 3 flow rules
|
||||
```
|
||||
|
||||
*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后*,要在 Spring Cloud Alibaba Sentinel 下使用动态数据源,只需要1个步骤:
|
||||
#### 在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后
|
||||
|
||||
只需要1个步骤就可完成数据源的配置:
|
||||
|
||||
* 直接在 `application.properties` 配置文件中配置数据源信息即可
|
||||
|
||||
@ -236,6 +285,16 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo
|
||||
|
||||
### More
|
||||
|
||||
下表显示当应用的 `ApplicationContext` 中存在对应的Bean的类型时,会进行的一些操作:
|
||||
|
||||
:frame: topbot
|
||||
[width="60%",options="header"]
|
||||
|====
|
||||
^|存在Bean的类型 ^|操作 ^|作用
|
||||
|`UrlCleaner`|`WebCallbackManager.setUrlCleaner(urlCleaner)`|资源清理(资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下))
|
||||
|`UrlBlockHandler`|`WebCallbackManager.setUrlBlockHandler(urlBlockHandler)`|自定义限流处理逻辑
|
||||
|====
|
||||
|
||||
下表显示 Spring Cloud Alibaba Sentinel 的所有配置信息:
|
||||
|
||||
:frame: topbot
|
||||
@ -244,14 +303,16 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo
|
||||
^|配置项 ^|含义 ^|默认值
|
||||
|`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.charset`|metric文件字符集|UTF-8
|
||||
|`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric 单个文件的大小|
|
||||
|`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric 总文件数量|
|
||||
|`spring.cloud.sentinel.log.dir`|Sentinel 日志文件所在的目录|
|
||||
|`spring.cloud.sentinel.log.switch-pid`|Sentinel 日志文件名是否需要带上pid|false
|
||||
|`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
|
||||
|====
|
||||
|
@ -40,6 +40,11 @@
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -2,6 +2,7 @@ package org.springframework.cloud.alibaba.cloud.examples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
@ -10,6 +11,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
@ -30,9 +32,40 @@ public class ConsumerApplication {
|
||||
SpringApplication.run(ConsumerApplication.class, args);
|
||||
}
|
||||
|
||||
@FeignClient(name = "service-provider")
|
||||
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
|
||||
public interface EchoService {
|
||||
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
|
||||
String echo(@PathVariable("str") String str);
|
||||
|
||||
@RequestMapping(value = "/divide", method = RequestMethod.GET)
|
||||
String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b);
|
||||
|
||||
@RequestMapping(value = "/notFound", method = RequestMethod.GET)
|
||||
String notFound();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FeignConfiguration {
|
||||
@Bean
|
||||
public EchoServiceFallback echoServiceFallback() {
|
||||
return new EchoServiceFallback();
|
||||
}
|
||||
}
|
||||
|
||||
class EchoServiceFallback implements EchoService {
|
||||
@Override
|
||||
public String echo(@PathVariable("str") String str) {
|
||||
return "echo fallback";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
|
||||
return "divide fallback";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String notFound() {
|
||||
return "notFound fallback";
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@ -29,6 +30,16 @@ public class TestController {
|
||||
String.class);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/notFound-feign", method = RequestMethod.GET)
|
||||
public String notFound() {
|
||||
return echoService.notFound();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/divide-feign", method = RequestMethod.GET)
|
||||
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
|
||||
return echoService.divide(a, b);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
|
||||
public String feign(@PathVariable String str) {
|
||||
return echoService.echo(str);
|
||||
|
@ -1,4 +1,12 @@
|
||||
spring.application.name=service-consumer
|
||||
server.port=18083
|
||||
management.endpoints.web.exposure.include=*
|
||||
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
|
||||
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
|
||||
|
||||
feign.sentinel.enabled=true
|
||||
|
||||
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
|
@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"resource": "GET:http://service-provider/echo/{str}",
|
||||
"controlBehavior": 0,
|
||||
"count": 1,
|
||||
"grade": 1,
|
||||
"limitApp": "default",
|
||||
"strategy": 0
|
||||
}
|
||||
]
|
@ -6,6 +6,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@ -25,5 +26,10 @@ public class ProviderApplication {
|
||||
public String echo(@PathVariable String string) {
|
||||
return "hello Nacos Discovery " + string;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/divide", method = RequestMethod.GET)
|
||||
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
|
||||
return String.valueOf(a / b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package org.springframework.cloud.alibaba.cloud.examples;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
|
||||
/**
|
||||
* @author xiaojing
|
||||
*/
|
||||
@ -14,7 +15,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
public class ServiceApplication {
|
||||
|
||||
@Bean
|
||||
@SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
|
||||
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
@ -25,9 +26,9 @@ public class ServiceApplication {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Converter myConverter() {
|
||||
return new JsonFlowRuleListConverter();
|
||||
}
|
||||
public Converter myConverter() {
|
||||
return new JsonFlowRuleListConverter();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ServiceApplication.class, args);
|
||||
|
@ -18,6 +18,13 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-extension</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -18,6 +18,7 @@ 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.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@ -32,6 +33,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* @see DegradeRule
|
||||
* @see SystemRule
|
||||
* @see AuthorityRule
|
||||
* @see ParamFlowRule
|
||||
* @see ObjectMapper
|
||||
*/
|
||||
public class JsonConverter implements Converter<String, List<AbstractRule>> {
|
||||
@ -48,7 +50,7 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
|
||||
public List<AbstractRule> convert(String source) {
|
||||
List<AbstractRule> ruleList = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(source)) {
|
||||
logger.info(
|
||||
logger.warn(
|
||||
"Sentinel JsonConverter can not convert rules because source is empty");
|
||||
return ruleList;
|
||||
}
|
||||
@ -68,7 +70,7 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
|
||||
|
||||
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemJson),
|
||||
convertDegradeRule(itemJson), convertSystemRule(itemJson),
|
||||
convertAuthorityRule(itemJson));
|
||||
convertAuthorityRule(itemJson), convertParamFlowRule(itemJson));
|
||||
|
||||
List<AbstractRule> convertRuleList = rules.stream()
|
||||
.filter(rule -> !ObjectUtils.isEmpty(rule))
|
||||
@ -101,8 +103,6 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
|
||||
throw new RuntimeException(
|
||||
"Sentinel JsonConverter convert error: " + e.getMessage(), e);
|
||||
}
|
||||
logger.info("Sentinel JsonConverter convert {} rules: {}", ruleList.size(),
|
||||
ruleList);
|
||||
return ruleList;
|
||||
}
|
||||
|
||||
@ -154,4 +154,15 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
|
||||
return rule;
|
||||
}
|
||||
|
||||
private ParamFlowRule convertParamFlowRule(String json) {
|
||||
ParamFlowRule rule = null;
|
||||
try {
|
||||
rule = objectMapper.readValue(json, ParamFlowRule.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ 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.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@ -32,7 +33,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
* @see DegradeRule
|
||||
* @see SystemRule
|
||||
* @see AuthorityRule
|
||||
* @see XmlMapper
|
||||
* @see ParamFlowRule
|
||||
*/
|
||||
public class XmlConverter implements Converter<String, List<AbstractRule>> {
|
||||
|
||||
@ -48,7 +49,7 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
|
||||
public List<AbstractRule> convert(String source) {
|
||||
List<AbstractRule> ruleList = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(source)) {
|
||||
logger.info(
|
||||
logger.warn(
|
||||
"Sentinel XmlConverter can not convert rules because source is empty");
|
||||
return ruleList;
|
||||
}
|
||||
@ -68,7 +69,7 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
|
||||
|
||||
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemXml),
|
||||
convertDegradeRule(itemXml), convertSystemRule(itemXml),
|
||||
convertAuthorityRule(itemXml));
|
||||
convertAuthorityRule(itemXml), convertParamFlowRule(itemXml));
|
||||
|
||||
List<AbstractRule> convertRuleList = rules.stream()
|
||||
.filter(rule -> !ObjectUtils.isEmpty(rule))
|
||||
@ -101,8 +102,6 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
|
||||
throw new RuntimeException(
|
||||
"Sentinel XmlConverter convert error: " + e.getMessage(), e);
|
||||
}
|
||||
logger.info("Sentinel XmlConverter convert {} rules: {}", ruleList.size(),
|
||||
ruleList);
|
||||
return ruleList;
|
||||
}
|
||||
|
||||
@ -154,4 +153,15 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
|
||||
return rule;
|
||||
}
|
||||
|
||||
private ParamFlowRule convertParamFlowRule(String json) {
|
||||
ParamFlowRule rule = null;
|
||||
try {
|
||||
rule = xmlMapper.readValue(json, ParamFlowRule.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,18 @@
|
||||
<artifactId>sentinel-dubbo-adapter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
|
||||
|
@ -26,6 +26,7 @@ import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePr
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
|
||||
/**
|
||||
@ -48,11 +49,6 @@ public class SentinelProperties {
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* charset when sentinel write or search metric file {@link SentinelConfig#CHARSET}
|
||||
*/
|
||||
private String charset = "UTF-8";
|
||||
|
||||
/**
|
||||
* configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper'
|
||||
*/
|
||||
@ -91,6 +87,12 @@ public class SentinelProperties {
|
||||
@NestedConfigurationProperty
|
||||
private Flow flow = new Flow();
|
||||
|
||||
/**
|
||||
* sentinel log configuration {@link LogBase}
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private Log log = new Log();
|
||||
|
||||
public boolean isEager() {
|
||||
return eager;
|
||||
}
|
||||
@ -107,14 +109,6 @@ public class SentinelProperties {
|
||||
this.flow = flow;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public Transport getTransport() {
|
||||
return transport;
|
||||
}
|
||||
@ -163,6 +157,14 @@ public class SentinelProperties {
|
||||
this.datasource = datasource;
|
||||
}
|
||||
|
||||
public Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
public void setLog(Log log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public static class Flow {
|
||||
|
||||
/**
|
||||
@ -208,6 +210,12 @@ public class SentinelProperties {
|
||||
*/
|
||||
private String fileTotalCount;
|
||||
|
||||
/**
|
||||
* charset when sentinel write or search metric file
|
||||
* {@link SentinelConfig#CHARSET}
|
||||
*/
|
||||
private String charset = "UTF-8";
|
||||
|
||||
public String getFileSingleSize() {
|
||||
return fileSingleSize;
|
||||
}
|
||||
@ -223,6 +231,14 @@ public class SentinelProperties {
|
||||
public void setFileTotalCount(String fileTotalCount) {
|
||||
this.fileTotalCount = fileTotalCount;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Transport {
|
||||
@ -299,4 +315,34 @@ public class SentinelProperties {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Log {
|
||||
|
||||
/**
|
||||
* sentinel log base dir
|
||||
*/
|
||||
private String dir;
|
||||
|
||||
/**
|
||||
* distinguish the log file by pid number
|
||||
*/
|
||||
private boolean switchPid = false;
|
||||
|
||||
public String getDir() {
|
||||
return dir;
|
||||
}
|
||||
|
||||
public void setDir(String dir) {
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
public boolean isSwitchPid() {
|
||||
return switchPid;
|
||||
}
|
||||
|
||||
public void setSwitchPid(boolean switchPid) {
|
||||
this.switchPid = switchPid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.alibaba.sentinel.annotation;
|
||||
|
||||
import java.lang.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;
|
||||
|
||||
/**
|
||||
* @author fangjian
|
||||
@ -24,7 +28,7 @@ import java.lang.annotation.*;
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface SentinelProtect {
|
||||
public @interface SentinelRestTemplate {
|
||||
|
||||
String blockHandler() default "";
|
||||
|
@ -41,6 +41,7 @@ import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
|
||||
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.init.InitExecutor;
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.util.AppNameUtil;
|
||||
|
||||
@ -71,7 +72,6 @@ public class SentinelAutoConfiguration {
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
|
||||
if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME))
|
||||
&& StringUtils.hasText(projectName)) {
|
||||
System.setProperty(AppNameUtil.APP_NAME, projectName);
|
||||
@ -93,8 +93,9 @@ public class SentinelAutoConfiguration {
|
||||
properties.getTransport().getHeartbeatIntervalMs());
|
||||
}
|
||||
if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET))
|
||||
&& StringUtils.hasText(properties.getCharset())) {
|
||||
System.setProperty(SentinelConfig.CHARSET, properties.getCharset());
|
||||
&& StringUtils.hasText(properties.getMetric().getCharset())) {
|
||||
System.setProperty(SentinelConfig.CHARSET,
|
||||
properties.getMetric().getCharset());
|
||||
}
|
||||
if (StringUtils
|
||||
.isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE))
|
||||
@ -113,10 +114,19 @@ public class SentinelAutoConfiguration {
|
||||
System.setProperty(SentinelConfig.COLD_FACTOR,
|
||||
properties.getFlow().getColdFactor());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(properties.getServlet().getBlockPage())) {
|
||||
WebServletConfig.setBlockPage(properties.getServlet().getBlockPage());
|
||||
}
|
||||
if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR))
|
||||
&& StringUtils.hasText(properties.getLog().getDir())) {
|
||||
System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir());
|
||||
}
|
||||
if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID))
|
||||
&& properties.getLog().isSwitchPid()) {
|
||||
System.setProperty(LogBase.LOG_NAME_USE_PID,
|
||||
String.valueOf(properties.getLog().isSwitchPid()));
|
||||
}
|
||||
|
||||
urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler);
|
||||
urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner);
|
||||
|
||||
|
@ -25,17 +25,17 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.type.StandardMethodMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* PostProcessor handle @SentinelProtect Annotation, add interceptor for RestTemplate
|
||||
* PostProcessor handle @SentinelRestTemplate Annotation, add interceptor for RestTemplate
|
||||
*
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
* @see SentinelProtect
|
||||
* @see SentinelRestTemplate
|
||||
* @see SentinelProtectInterceptor
|
||||
*/
|
||||
public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor {
|
||||
@ -43,16 +43,16 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ConcurrentHashMap<String, SentinelProtect> cache = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<String, SentinelRestTemplate> cache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition,
|
||||
Class<?> beanType, String beanName) {
|
||||
if (checkSentinelProtect(beanDefinition, beanType)) {
|
||||
SentinelProtect sentinelProtect = ((StandardMethodMetadata) beanDefinition
|
||||
SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition
|
||||
.getSource()).getIntrospectedMethod()
|
||||
.getAnnotation(SentinelProtect.class);
|
||||
cache.put(beanName, sentinelProtect);
|
||||
.getAnnotation(SentinelRestTemplate.class);
|
||||
cache.put(beanName, sentinelRestTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,26 +61,26 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces
|
||||
return beanType == RestTemplate.class
|
||||
&& beanDefinition.getSource() instanceof StandardMethodMetadata
|
||||
&& ((StandardMethodMetadata) beanDefinition.getSource())
|
||||
.isAnnotated(SentinelProtect.class.getName());
|
||||
.isAnnotated(SentinelRestTemplate.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (cache.containsKey(beanName)) {
|
||||
// add interceptor for each RestTemplate with @SentinelProtect annotation
|
||||
// add interceptor for each RestTemplate with @SentinelRestTemplate annotation
|
||||
StringBuilder interceptorBeanName = new StringBuilder();
|
||||
SentinelProtect sentinelProtect = cache.get(beanName);
|
||||
SentinelRestTemplate sentinelRestTemplate = cache.get(beanName);
|
||||
interceptorBeanName
|
||||
.append(StringUtils.uncapitalize(
|
||||
SentinelProtectInterceptor.class.getSimpleName()))
|
||||
.append("_")
|
||||
.append(sentinelProtect.blockHandlerClass().getSimpleName())
|
||||
.append(sentinelProtect.blockHandler()).append("_")
|
||||
.append(sentinelProtect.fallbackClass().getSimpleName())
|
||||
.append(sentinelProtect.fallback());
|
||||
.append(sentinelRestTemplate.blockHandlerClass().getSimpleName())
|
||||
.append(sentinelRestTemplate.blockHandler()).append("_")
|
||||
.append(sentinelRestTemplate.fallbackClass().getSimpleName())
|
||||
.append(sentinelRestTemplate.fallback());
|
||||
RestTemplate restTemplate = (RestTemplate) bean;
|
||||
registerBean(interceptorBeanName.toString(), sentinelProtect);
|
||||
registerBean(interceptorBeanName.toString(), sentinelRestTemplate);
|
||||
SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext
|
||||
.getBean(interceptorBeanName.toString(),
|
||||
SentinelProtectInterceptor.class);
|
||||
@ -90,13 +90,13 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces
|
||||
}
|
||||
|
||||
private void registerBean(String interceptorBeanName,
|
||||
SentinelProtect sentinelProtect) {
|
||||
SentinelRestTemplate sentinelRestTemplate) {
|
||||
// register SentinelProtectInterceptor bean
|
||||
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
|
||||
.getAutowireCapableBeanFactory();
|
||||
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(SentinelProtectInterceptor.class);
|
||||
beanDefinitionBuilder.addConstructorArgValue(sentinelProtect);
|
||||
beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate);
|
||||
BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
|
||||
.getRawBeanDefinition();
|
||||
beanFactory.registerBeanDefinition(interceptorBeanName,
|
||||
|
@ -22,7 +22,7 @@ import java.net.URI;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
@ -37,19 +37,19 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Interceptor using by SentinelProtect and SentinelProtectInterceptor
|
||||
* Interceptor using by SentinelRestTemplate
|
||||
*
|
||||
* @author fangjian
|
||||
*/
|
||||
public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private static final Logger logger = LoggerFactory
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(SentinelProtectInterceptor.class);
|
||||
|
||||
private SentinelProtect sentinelProtect;
|
||||
private SentinelRestTemplate sentinelRestTemplate;
|
||||
|
||||
public SentinelProtectInterceptor(SentinelProtect sentinelProtect) {
|
||||
this.sentinelProtect = sentinelProtect;
|
||||
public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate) {
|
||||
this.sentinelRestTemplate = sentinelRestTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -92,15 +92,16 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
|
||||
Object[] args = new Object[] { ex };
|
||||
// handle degrade
|
||||
if (isDegradeFailure(ex)) {
|
||||
Method method = extractFallbackMethod(sentinelProtect.fallback(),
|
||||
sentinelProtect.fallbackClass());
|
||||
Method method = extractFallbackMethod(sentinelRestTemplate.fallback(),
|
||||
sentinelRestTemplate.fallbackClass());
|
||||
if (method != null) {
|
||||
method.invoke(null, args);
|
||||
}
|
||||
}
|
||||
// handle block
|
||||
Method blockHandler = extractBlockHandlerMethod(sentinelProtect.blockHandler(),
|
||||
sentinelProtect.blockHandlerClass());
|
||||
Method blockHandler = extractBlockHandlerMethod(
|
||||
sentinelRestTemplate.blockHandler(),
|
||||
sentinelRestTemplate.blockHandlerClass());
|
||||
if (blockHandler != null) {
|
||||
blockHandler.invoke(null, args);
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ 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.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
|
||||
|
||||
@ -58,10 +60,12 @@ public class SentinelEndpoint {
|
||||
List<FlowRule> flowRules = FlowRuleManager.getRules();
|
||||
List<DegradeRule> degradeRules = DegradeRuleManager.getRules();
|
||||
List<SystemRule> systemRules = SystemRuleManager.getRules();
|
||||
List<ParamFlowRule> paramFlowRules = ParamFlowRuleManager.getRules();
|
||||
result.put("properties", sentinelProperties);
|
||||
result.put("FlowRules", flowRules);
|
||||
result.put("DegradeRules", degradeRules);
|
||||
result.put("SystemRules", systemRules);
|
||||
result.put("ParamFlowRule", paramFlowRules);
|
||||
result.put("datasources", new HashMap<String, Object>());
|
||||
dataSourceHandler.getDataSourceBeanNameList().forEach(dataSourceBeanName -> {
|
||||
ReadableDataSource dataSource = applicationContext.getBean(dataSourceBeanName,
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.feign;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.MethodMetadata;
|
||||
|
||||
/**
|
||||
*
|
||||
* Using static field {@link SentinelContractHolder#metadataMap} to hold
|
||||
* {@link MethodMetadata} data
|
||||
*
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
public class SentinelContractHolder implements Contract {
|
||||
|
||||
private final Contract delegate;
|
||||
|
||||
/**
|
||||
* map key is constructed by ClassFullName + configKey. configKey is constructed by
|
||||
* {@link feign.Feign#configKey}
|
||||
*/
|
||||
public final static Map<String, MethodMetadata> metadataMap = new HashMap();
|
||||
|
||||
public SentinelContractHolder(Contract delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
|
||||
List<MethodMetadata> metadatas = delegate.parseAndValidatateMetadata(targetType);
|
||||
metadatas.forEach(metadata -> metadataMap
|
||||
.put(targetType.getName() + metadata.configKey(), metadata));
|
||||
return metadatas;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.feign;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cloud.openfeign.FeignContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
import feign.InvocationHandlerFactory;
|
||||
import feign.Target;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
|
||||
/**
|
||||
* {@link Feign.Builder} like {@link HystrixFeign.Builder}
|
||||
*
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
public class SentinelFeign {
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder extends Feign.Builder
|
||||
implements ApplicationContextAware {
|
||||
|
||||
private Contract contract = new Contract.Default();
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private FeignContext feignContext;
|
||||
|
||||
@Override
|
||||
public Feign.Builder invocationHandlerFactory(
|
||||
InvocationHandlerFactory invocationHandlerFactory) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder contract(Contract contract) {
|
||||
this.contract = contract;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Feign build() {
|
||||
super.invocationHandlerFactory(new InvocationHandlerFactory() {
|
||||
@Override
|
||||
public InvocationHandler create(Target target,
|
||||
Map<Method, MethodHandler> dispatch) {
|
||||
// using reflect get fallback and fallbackFactory properties from
|
||||
// FeignClientFactoryBean because FeignClientFactoryBean is a package
|
||||
// level class, we can not use it in our package
|
||||
Object feignClientFactoryBean = Builder.this.applicationContext
|
||||
.getBean("&" + target.type().getName());
|
||||
|
||||
Class fallback = (Class) getFieldValue(feignClientFactoryBean,
|
||||
"fallback");
|
||||
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
|
||||
"fallbackFactory");
|
||||
String name = (String) getFieldValue(feignClientFactoryBean, "name");
|
||||
|
||||
Object fallbackInstance;
|
||||
FallbackFactory fallbackFactoryInstance;
|
||||
// check fallback and fallbackFactory properties
|
||||
if (void.class != fallback) {
|
||||
fallbackInstance = getFromContext(name, "fallback", fallback,
|
||||
target);
|
||||
return new SentinelInvocationHandler(target, dispatch,
|
||||
new FallbackFactory.Default(fallbackInstance));
|
||||
}
|
||||
if (void.class != fallbackFactory) {
|
||||
fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
|
||||
"fallbackFactory", fallbackFactory, target);
|
||||
return new SentinelInvocationHandler(target, dispatch,
|
||||
fallbackFactoryInstance);
|
||||
}
|
||||
return new SentinelInvocationHandler(target, dispatch);
|
||||
}
|
||||
|
||||
private Object getFromContext(String name, String type,
|
||||
Class fallbackType, Target target) {
|
||||
Object fallbackInstance = feignContext.getInstance(name,
|
||||
fallbackType);
|
||||
if (fallbackInstance == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"No %s instance of type %s found for feign client %s",
|
||||
type, fallbackType, name));
|
||||
}
|
||||
|
||||
if (!target.type().isAssignableFrom(fallbackType)) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
|
||||
type, fallbackType, target.type(), name));
|
||||
}
|
||||
return fallbackInstance;
|
||||
}
|
||||
});
|
||||
|
||||
super.contract(new SentinelContractHolder(contract));
|
||||
return super.build();
|
||||
}
|
||||
|
||||
private Object getFieldValue(Object instance, String fieldName) {
|
||||
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
return field.get(instance);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
feignContext = this.applicationContext.getBean(FeignContext.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.feign;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
|
||||
import feign.Feign;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ SphU.class, Feign.class })
|
||||
public class SentinelFeignAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = "feign.sentinel.enabled")
|
||||
public Feign.Builder feignSentinelBuilder() {
|
||||
return SentinelFeign.builder();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.feign;
|
||||
|
||||
import static feign.Util.checkNotNull;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.Tracer;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.InvocationHandlerFactory.MethodHandler;
|
||||
import feign.MethodMetadata;
|
||||
import feign.Target;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
|
||||
/**
|
||||
* {@link InvocationHandler} handle invocation that protected by Sentinel
|
||||
*
|
||||
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
|
||||
*/
|
||||
public class SentinelInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Target<?> target;
|
||||
private final Map<Method, MethodHandler> dispatch;
|
||||
|
||||
private FallbackFactory fallbackFactory;
|
||||
private Map<Method, Method> fallbackMethodMap;
|
||||
|
||||
SentinelInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch,
|
||||
FallbackFactory fallbackFactory) {
|
||||
this.target = checkNotNull(target, "target");
|
||||
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||
this.fallbackFactory = fallbackFactory;
|
||||
this.fallbackMethodMap = toFallbackMethod(dispatch);
|
||||
}
|
||||
|
||||
SentinelInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch) {
|
||||
this.target = checkNotNull(target, "target");
|
||||
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final Object proxy, final Method method, final Object[] args)
|
||||
throws Throwable {
|
||||
if ("equals".equals(method.getName())) {
|
||||
try {
|
||||
Object otherHandler = args.length > 0 && args[0] != null
|
||||
? Proxy.getInvocationHandler(args[0])
|
||||
: null;
|
||||
return equals(otherHandler);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ("hashCode".equals(method.getName())) {
|
||||
return hashCode();
|
||||
}
|
||||
else if ("toString".equals(method.getName())) {
|
||||
return toString();
|
||||
}
|
||||
|
||||
Object result;
|
||||
MethodHandler methodHandler = this.dispatch.get(method);
|
||||
// only handle by HardCodedTarget
|
||||
if (target instanceof Target.HardCodedTarget) {
|
||||
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
|
||||
MethodMetadata methodMetadata = SentinelContractHolder.metadataMap
|
||||
.get(method.getDeclaringClass().getName()
|
||||
+ Feign.configKey(method.getDeclaringClass(), method));
|
||||
// resource default is HttpMethod:protocol://url
|
||||
String resourceName = methodMetadata.template().method().toUpperCase() + ":"
|
||||
+ hardCodedTarget.url() + methodMetadata.template().url();
|
||||
Entry entry = null;
|
||||
try {
|
||||
ContextUtil.enter(resourceName);
|
||||
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
|
||||
result = methodHandler.invoke(args);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// fallback handle
|
||||
if (!BlockException.isBlockException(ex)) {
|
||||
Tracer.trace(ex);
|
||||
}
|
||||
if (fallbackFactory != null) {
|
||||
try {
|
||||
Object fallbackResult = fallbackMethodMap.get(method)
|
||||
.invoke(fallbackFactory.create(ex), args);
|
||||
return fallbackResult;
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
// shouldn't happen as method is public due to being an interface
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new AssertionError(e.getCause());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// throw exception if fallbackFactory is null
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (entry != null) {
|
||||
entry.exit();
|
||||
}
|
||||
ContextUtil.exit();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// other target type using default strategy
|
||||
result = methodHandler.invoke(args);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof SentinelInvocationHandler) {
|
||||
SentinelInvocationHandler other = (SentinelInvocationHandler) obj;
|
||||
return target.equals(other.target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return target.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return target.toString();
|
||||
}
|
||||
|
||||
static Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch) {
|
||||
Map<Method, Method> result = new LinkedHashMap<>();
|
||||
for (Method method : dispatch.keySet()) {
|
||||
method.setAccessible(true);
|
||||
result.put(method, method);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -12,12 +12,6 @@
|
||||
"defaultValue": false,
|
||||
"description": "earlier initialize heart-beat when the spring container starts when the transport dependency is on classpath, the configuration is effective."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.charset",
|
||||
"type": "java.lang.String",
|
||||
"defaultValue": "UTF-8",
|
||||
"description": "charset when sentinel write or search metric file."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.transport.port",
|
||||
"type": "java.lang.String",
|
||||
@ -40,6 +34,12 @@
|
||||
"defaultValue": "Integer.MIN_VALUE",
|
||||
"description": "sentinel filter chain order, will be set to FilterRegistrationBean."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.metric.charset",
|
||||
"type": "java.lang.String",
|
||||
"defaultValue": "UTF-8",
|
||||
"description": "charset when sentinel write or search metric file."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.metric.fileSingleSize",
|
||||
"type": "java.lang.String",
|
||||
@ -50,6 +50,17 @@
|
||||
"type": "java.lang.String",
|
||||
"description": "the total metric file count."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.log.dir",
|
||||
"type": "java.lang.String",
|
||||
"description": "log base directory."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.log.switch-pid",
|
||||
"type": "java.lang.Boolean",
|
||||
"defaultValue": false,
|
||||
"description": "log file should with pid."
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.sentinel.servlet.blockPage",
|
||||
"type": "java.lang.String",
|
||||
|
@ -1,4 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\
|
||||
org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
|
||||
org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration
|
||||
org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\
|
||||
org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration
|
||||
|
@ -22,7 +22,7 @@ import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect;
|
||||
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate;
|
||||
import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration;
|
||||
import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor;
|
||||
import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor;
|
||||
@ -92,13 +92,13 @@ public class SentinelAutoConfigurationTests {
|
||||
static class SentinelTestConfiguration {
|
||||
|
||||
@Bean
|
||||
@SentinelProtect
|
||||
@SentinelRestTemplate
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SentinelProtect(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException")
|
||||
@SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException")
|
||||
RestTemplate restTemplateWithBlockClass() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user