1
0
mirror of https://gitee.com/mirrors/Spring-Cloud-Alibaba.git synced 2021-06-26 13:25:11 +08:00

Merge pull request #143 from fangjian0423/1.x

Sentinel upgrade for 1.x branch
This commit is contained in:
xiaojing 2018-12-06 19:10:39 +08:00 committed by GitHub
commit fc9d016d0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 906 additions and 286 deletions

View File

@ -71,6 +71,11 @@
<artifactId>sentinel-core</artifactId> <artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version> <version>${sentinel.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba.csp</groupId> <groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId> <artifactId>sentinel-datasource-extension</artifactId>

View File

@ -40,6 +40,11 @@
<artifactId>spring-cloud-starter-feign</artifactId> <artifactId>spring-cloud-starter-feign</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -2,6 +2,7 @@ package org.springframework.cloud.alibaba.cloud.examples;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.EnableFeignClients;
@ -10,6 +11,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
/** /**
@ -20,22 +22,50 @@ import org.springframework.web.client.RestTemplate;
@EnableFeignClients @EnableFeignClients
public class ConsumerApplication { public class ConsumerApplication {
@LoadBalanced @LoadBalanced
@Bean @Bean
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
return new RestTemplate(); return new RestTemplate();
} }
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@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);
public static void main(String[] args) { @RequestMapping(value = "/notFound", method = RequestMethod.GET)
SpringApplication.run(ConsumerApplication.class, args); String notFound();
} }
@FeignClient(name = "service-provider") }
public interface EchoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) class FeignConfiguration {
String echo(@PathVariable("str") String str); @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";
}
} }

View File

@ -6,6 +6,7 @@ import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; 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.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -15,31 +16,42 @@ import org.springframework.web.client.RestTemplate;
@RestController @RestController
public class TestController { public class TestController {
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Autowired @Autowired
private EchoService echoService; private EchoService echoService;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET)
private DiscoveryClient discoveryClient; public String rest(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str,
String.class);
}
@RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET) @RequestMapping(value = "/notFound-feign", method = RequestMethod.GET)
public String rest(@PathVariable String str) { public String notFound() {
return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); return echoService.notFound();
} }
@RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
public String feign(@PathVariable String str) {
return echoService.echo(str);
}
@RequestMapping(value = "/divide-feign", method = RequestMethod.GET)
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
return echoService.divide(a, b);
}
@RequestMapping(value="/services/{service}",method = RequestMethod.GET) @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
public Object client(@PathVariable String service){ public String feign(@PathVariable String str) {
return discoveryClient.getInstances(service); return echoService.echo(str);
} }
@RequestMapping(value="/services",method = RequestMethod.GET)
public Object services(){ @RequestMapping(value = "/services/{service}", method = RequestMethod.GET)
return discoveryClient.getServices(); public Object client(@PathVariable String service) {
} return discoveryClient.getInstances(service);
}
@RequestMapping(value = "/services", method = RequestMethod.GET)
public Object services() {
return discoveryClient.getServices();
}
} }

View File

@ -1,4 +1,12 @@
spring.application.name=service-consumer spring.application.name=service-consumer
server.port=18083 server.port=18083
management.endpoints.web.exposure.include=* management.security.enabled=false
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

View File

@ -0,0 +1,10 @@
[
{
"resource": "GET:http://service-provider/echo/{str}",
"controlBehavior": 0,
"count": 1,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]

View File

@ -6,6 +6,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; 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.bind.annotation.RestController;
/** /**
@ -25,5 +26,10 @@ public class ProviderApplication {
public String echo(@PathVariable String string) { public String echo(@PathVariable String string) {
return "hello Nacos Discovery " + 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);
}
} }
} }

View File

@ -2,7 +2,7 @@ package org.springframework.cloud.alibaba.cloud.examples;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.context.annotation.Bean;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -15,7 +15,7 @@ import com.alibaba.csp.sentinel.datasource.Converter;
public class ServiceApplication { public class ServiceApplication {
@Bean @Bean
@SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
return new RestTemplate(); return new RestTemplate();
} }

View File

@ -18,6 +18,13 @@
<dependency> <dependency>
<groupId>com.alibaba.csp</groupId> <groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId> <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>
<dependency> <dependency>

View File

@ -17,6 +17,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.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; 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.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -31,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @see DegradeRule * @see DegradeRule
* @see SystemRule * @see SystemRule
* @see AuthorityRule * @see AuthorityRule
* @see ParamFlowRule
* @see ObjectMapper * @see ObjectMapper
*/ */
public class JsonConverter implements Converter<String, List<AbstractRule>> { public class JsonConverter implements Converter<String, List<AbstractRule>> {
@ -47,7 +49,7 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
public List<AbstractRule> convert(String source) { public List<AbstractRule> convert(String source) {
List<AbstractRule> ruleList = new ArrayList<>(); List<AbstractRule> ruleList = new ArrayList<>();
if (StringUtils.isEmpty(source)) { if (StringUtils.isEmpty(source)) {
logger.info( logger.warn(
"Sentinel JsonConverter can not convert rules because source is empty"); "Sentinel JsonConverter can not convert rules because source is empty");
return ruleList; return ruleList;
} }
@ -67,7 +69,7 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemJson), List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemJson),
convertDegradeRule(itemJson), convertSystemRule(itemJson), convertDegradeRule(itemJson), convertSystemRule(itemJson),
convertAuthorityRule(itemJson)); convertAuthorityRule(itemJson), convertParamFlowRule(itemJson));
List<AbstractRule> convertRuleList = new ArrayList<>(); List<AbstractRule> convertRuleList = new ArrayList<>();
@ -105,8 +107,6 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
throw new RuntimeException( throw new RuntimeException(
"Sentinel JsonConverter convert error: " + e.getMessage(), e); "Sentinel JsonConverter convert error: " + e.getMessage(), e);
} }
logger.info("Sentinel JsonConverter convert {} rules: {}", ruleList.size(),
ruleList);
return ruleList; return ruleList;
} }
@ -158,4 +158,15 @@ public class JsonConverter implements Converter<String, List<AbstractRule>> {
return rule; return rule;
} }
private ParamFlowRule convertParamFlowRule(String json) {
ParamFlowRule rule = null;
try {
rule = objectMapper.readValue(json, ParamFlowRule.class);
}
catch (Exception e) {
// ignore
}
return rule;
}
} }

View File

@ -17,6 +17,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.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; 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.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -31,6 +32,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper;
* @see DegradeRule * @see DegradeRule
* @see SystemRule * @see SystemRule
* @see AuthorityRule * @see AuthorityRule
* @see ParamFlowRule
* @see XmlMapper * @see XmlMapper
*/ */
public class XmlConverter implements Converter<String, List<AbstractRule>> { public class XmlConverter implements Converter<String, List<AbstractRule>> {
@ -47,7 +49,7 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
public List<AbstractRule> convert(String source) { public List<AbstractRule> convert(String source) {
List<AbstractRule> ruleList = new ArrayList<>(); List<AbstractRule> ruleList = new ArrayList<>();
if (StringUtils.isEmpty(source)) { if (StringUtils.isEmpty(source)) {
logger.info( logger.warn(
"Sentinel XmlConverter can not convert rules because source is empty"); "Sentinel XmlConverter can not convert rules because source is empty");
return ruleList; return ruleList;
} }
@ -67,7 +69,7 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemXml), List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemXml),
convertDegradeRule(itemXml), convertSystemRule(itemXml), convertDegradeRule(itemXml), convertSystemRule(itemXml),
convertAuthorityRule(itemXml)); convertAuthorityRule(itemXml), convertParamFlowRule(itemXml));
List<AbstractRule> convertRuleList = new ArrayList<>(); List<AbstractRule> convertRuleList = new ArrayList<>();
@ -104,8 +106,6 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
throw new RuntimeException( throw new RuntimeException(
"Sentinel XmlConverter convert error: " + e.getMessage(), e); "Sentinel XmlConverter convert error: " + e.getMessage(), e);
} }
logger.info("Sentinel XmlConverter convert {} rules: {}", ruleList.size(),
ruleList);
return ruleList; return ruleList;
} }
@ -157,4 +157,15 @@ public class XmlConverter implements Converter<String, List<AbstractRule>> {
return rule; return rule;
} }
private ParamFlowRule convertParamFlowRule(String json) {
ParamFlowRule rule = null;
try {
rule = xmlMapper.readValue(json, ParamFlowRule.class);
}
catch (Exception e) {
// ignore
}
return rule;
}
} }

View File

@ -35,6 +35,18 @@
<artifactId>sentinel-dubbo-adapter</artifactId> <artifactId>sentinel-dubbo-adapter</artifactId>
</dependency> </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> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId> <artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>

View File

@ -26,6 +26,7 @@ import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePr
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.log.LogBase;
import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.config.TransportConfig;
/** /**
@ -48,11 +49,6 @@ public class SentinelProperties {
*/ */
private boolean enabled = true; 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' * configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper'
*/ */
@ -91,6 +87,12 @@ public class SentinelProperties {
@NestedConfigurationProperty @NestedConfigurationProperty
private Flow flow = new Flow(); private Flow flow = new Flow();
/**
* sentinel log configuration {@link LogBase}
*/
@NestedConfigurationProperty
private Log log = new Log();
public boolean isEager() { public boolean isEager() {
return eager; return eager;
} }
@ -107,14 +109,6 @@ public class SentinelProperties {
this.flow = flow; this.flow = flow;
} }
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public Transport getTransport() { public Transport getTransport() {
return transport; return transport;
} }
@ -163,6 +157,14 @@ public class SentinelProperties {
this.datasource = datasource; this.datasource = datasource;
} }
public Log getLog() {
return log;
}
public void setLog(Log log) {
this.log = log;
}
public static class Flow { public static class Flow {
/** /**
@ -208,6 +210,12 @@ public class SentinelProperties {
*/ */
private String fileTotalCount; private String fileTotalCount;
/**
* charset when sentinel write or search metric file
* {@link SentinelConfig#CHARSET}
*/
private String charset = "UTF-8";
public String getFileSingleSize() { public String getFileSingleSize() {
return fileSingleSize; return fileSingleSize;
} }
@ -223,6 +231,14 @@ public class SentinelProperties {
public void setFileTotalCount(String fileTotalCount) { public void setFileTotalCount(String fileTotalCount) {
this.fileTotalCount = fileTotalCount; this.fileTotalCount = fileTotalCount;
} }
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
} }
public static class Transport { public static class Transport {
@ -298,4 +314,35 @@ public class SentinelProperties {
this.urlPatterns = urlPatterns; this.urlPatterns = urlPatterns;
} }
} }
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;
}
}
} }

View File

@ -16,7 +16,11 @@
package org.springframework.cloud.alibaba.sentinel.annotation; 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 * @author fangjian
@ -24,7 +28,7 @@ import java.lang.annotation.*;
@Target({ ElementType.METHOD }) @Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface SentinelProtect { public @interface SentinelRestTemplate {
String blockHandler() default ""; String blockHandler() default "";

View File

@ -39,6 +39,7 @@ import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.init.InitExecutor; 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.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.AppNameUtil;
@ -89,8 +90,9 @@ public class SentinelAutoConfiguration {
properties.getTransport().getHeartbeatIntervalMs()); properties.getTransport().getHeartbeatIntervalMs());
} }
if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET))
&& StringUtils.hasText(properties.getCharset())) { && StringUtils.hasText(properties.getMetric().getCharset())) {
System.setProperty(SentinelConfig.CHARSET, properties.getCharset()); System.setProperty(SentinelConfig.CHARSET,
properties.getMetric().getCharset());
} }
if (StringUtils if (StringUtils
.isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE))
@ -109,6 +111,15 @@ public class SentinelAutoConfiguration {
System.setProperty(SentinelConfig.COLD_FACTOR, System.setProperty(SentinelConfig.COLD_FACTOR,
properties.getFlow().getColdFactor()); properties.getFlow().getColdFactor());
} }
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()));
}
if (StringUtils.hasText(properties.getServlet().getBlockPage())) { if (StringUtils.hasText(properties.getServlet().getBlockPage())) {
WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); WebServletConfig.setBlockPage(properties.getServlet().getBlockPage());

View File

@ -25,87 +25,88 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition; 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.context.ApplicationContext;
import org.springframework.core.type.StandardMethodMetadata; import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; 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> * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @see SentinelProtect * @see SentinelRestTemplate
* @see SentinelProtectInterceptor * @see SentinelProtectInterceptor
*/ */
public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor {
@Autowired @Autowired
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private ConcurrentHashMap<String, SentinelProtect> cache = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, SentinelRestTemplate> cache = new ConcurrentHashMap<>();
@Override @Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition,
Class<?> beanType, String beanName) { Class<?> beanType, String beanName) {
if (checkSentinelProtect(beanDefinition, beanType)) { if (checkSentinelProtect(beanDefinition, beanType)) {
SentinelProtect sentinelProtect = ((StandardMethodMetadata) beanDefinition SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition
.getSource()).getIntrospectedMethod() .getSource()).getIntrospectedMethod()
.getAnnotation(SentinelProtect.class); .getAnnotation(SentinelRestTemplate.class);
cache.put(beanName, sentinelProtect); cache.put(beanName, sentinelRestTemplate);
} }
} }
private boolean checkSentinelProtect(RootBeanDefinition beanDefinition, private boolean checkSentinelProtect(RootBeanDefinition beanDefinition,
Class<?> beanType) { Class<?> beanType) {
return beanType == RestTemplate.class return beanType == RestTemplate.class
&& beanDefinition.getSource() instanceof StandardMethodMetadata && beanDefinition.getSource() instanceof StandardMethodMetadata
&& ((StandardMethodMetadata) beanDefinition.getSource()) && ((StandardMethodMetadata) beanDefinition.getSource())
.isAnnotated(SentinelProtect.class.getName()); .isAnnotated(SentinelRestTemplate.class.getName());
} }
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException { throws BeansException {
if (cache.containsKey(beanName)) { if (cache.containsKey(beanName)) {
// add interceptor for each RestTemplate with @SentinelProtect annotation // add interceptor for each RestTemplate with @SentinelRestTemplate annotation
StringBuilder interceptorBeanName = new StringBuilder(); StringBuilder interceptorBeanName = new StringBuilder();
SentinelProtect sentinelProtect = cache.get(beanName); SentinelRestTemplate sentinelRestTemplate = cache.get(beanName);
interceptorBeanName interceptorBeanName
.append(StringUtils.uncapitalize( .append(StringUtils.uncapitalize(
SentinelProtectInterceptor.class.getSimpleName())) SentinelProtectInterceptor.class.getSimpleName()))
.append("_") .append("_")
.append(sentinelProtect.blockHandlerClass().getSimpleName()) .append(sentinelRestTemplate.blockHandlerClass().getSimpleName())
.append(sentinelProtect.blockHandler()).append("_") .append(sentinelRestTemplate.blockHandler()).append("_")
.append(sentinelProtect.fallbackClass().getSimpleName()) .append(sentinelRestTemplate.fallbackClass().getSimpleName())
.append(sentinelProtect.fallback()); .append(sentinelRestTemplate.fallback());
RestTemplate restTemplate = (RestTemplate) bean; RestTemplate restTemplate = (RestTemplate) bean;
registerBean(interceptorBeanName.toString(), sentinelProtect); registerBean(interceptorBeanName.toString(), sentinelRestTemplate);
SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext
.getBean(interceptorBeanName.toString(), .getBean(interceptorBeanName.toString(),
SentinelProtectInterceptor.class); SentinelProtectInterceptor.class);
restTemplate.getInterceptors().add(sentinelProtectInterceptor); restTemplate.getInterceptors().add(sentinelProtectInterceptor);
} }
return bean; return bean;
} }
private void registerBean(String interceptorBeanName, private void registerBean(String interceptorBeanName,
SentinelProtect sentinelProtect) { SentinelRestTemplate sentinelRestTemplate) {
// register SentinelProtectInterceptor bean // register SentinelProtectInterceptor bean
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory(); .getAutowireCapableBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(SentinelProtectInterceptor.class); .genericBeanDefinition(SentinelProtectInterceptor.class);
beanDefinitionBuilder.addConstructorArgValue(sentinelProtect); beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate);
BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
.getRawBeanDefinition(); .getRawBeanDefinition();
beanFactory.registerBeanDefinition(interceptorBeanName, beanFactory.registerBeanDefinition(interceptorBeanName,
interceptorBeanDefinition); interceptorBeanDefinition);
} }
@Override @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { public Object postProcessBeforeInitialization(Object bean, String beanName)
return bean; throws BeansException {
} return bean;
}
} }

View File

@ -22,7 +22,7 @@ import java.net.URI;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
@ -37,103 +37,104 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.StringUtil;
/** /**
* Interceptor using by SentinelProtect and SentinelProtectInterceptor * Interceptor using by SentinelRestTemplate and SentinelProtectInterceptor
* *
* @author fangjian * @author fangjian
*/ */
public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor {
private static final Logger logger = LoggerFactory private static final Logger logger = LoggerFactory
.getLogger(SentinelProtectInterceptor.class); .getLogger(SentinelProtectInterceptor.class);
private SentinelProtect sentinelProtect; private SentinelRestTemplate sentinelRestTemplate;
public SentinelProtectInterceptor(SentinelProtect sentinelProtect) { public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate) {
this.sentinelProtect = sentinelProtect; this.sentinelRestTemplate = sentinelRestTemplate;
} }
@Override @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException { ClientHttpRequestExecution execution) throws IOException {
URI uri = request.getURI(); URI uri = request.getURI();
String hostResource = uri.getScheme() + "://" + uri.getHost() + ":" String hostResource = uri.getScheme() + "://" + uri.getHost() + ":"
+ (uri.getPort() == -1 ? 80 : uri.getPort()); + (uri.getPort() == -1 ? 80 : uri.getPort());
String hostWithPathResource = hostResource + uri.getPath(); String hostWithPathResource = hostResource + uri.getPath();
Entry hostEntry = null, hostWithPathEntry = null; Entry hostEntry = null, hostWithPathEntry = null;
ClientHttpResponse response = null; ClientHttpResponse response = null;
try { try {
ContextUtil.enter(hostWithPathResource); ContextUtil.enter(hostWithPathResource);
hostWithPathEntry = SphU.entry(hostWithPathResource); hostWithPathEntry = SphU.entry(hostWithPathResource);
hostEntry = SphU.entry(hostResource); hostEntry = SphU.entry(hostResource);
response = execution.execute(request, body); response = execution.execute(request, body);
} }
catch (BlockException e) { catch (BlockException e) {
logger.error("RestTemplate block", e); logger.error("RestTemplate block", e);
try { try {
handleBlockException(e); handleBlockException(e);
} }
catch (Exception ex) { catch (Exception ex) {
logger.error("sentinel handle BlockException error.", e); logger.error("sentinel handle BlockException error.", e);
} }
} }
finally { finally {
if (hostEntry != null) { if (hostEntry != null) {
hostEntry.exit(); hostEntry.exit();
} }
if (hostWithPathEntry != null) { if (hostWithPathEntry != null) {
hostWithPathEntry.exit(); hostWithPathEntry.exit();
} }
ContextUtil.exit(); ContextUtil.exit();
} }
return response; return response;
} }
private void handleBlockException(BlockException ex) throws Exception { private void handleBlockException(BlockException ex) throws Exception {
Object[] args = new Object[] { ex }; Object[] args = new Object[] { ex };
// handle degrade // handle degrade
if (isDegradeFailure(ex)) { if (isDegradeFailure(ex)) {
Method method = extractFallbackMethod(sentinelProtect.fallback(), Method method = extractFallbackMethod(sentinelRestTemplate.fallback(),
sentinelProtect.fallbackClass()); sentinelRestTemplate.fallbackClass());
if (method != null) { if (method != null) {
method.invoke(null, args); method.invoke(null, args);
} }
} }
// handle block // handle block
Method blockHandler = extractBlockHandlerMethod(sentinelProtect.blockHandler(), Method blockHandler = extractBlockHandlerMethod(
sentinelProtect.blockHandlerClass()); sentinelRestTemplate.blockHandler(),
if (blockHandler != null) { sentinelRestTemplate.blockHandlerClass());
blockHandler.invoke(null, args); if (blockHandler != null) {
} blockHandler.invoke(null, args);
} }
}
private Method extractFallbackMethod(String fallback, Class<?> fallbackClass) { private Method extractFallbackMethod(String fallback, Class<?> fallbackClass) {
if (StringUtil.isBlank(fallback) || fallbackClass == void.class) { if (StringUtil.isBlank(fallback) || fallbackClass == void.class) {
return null; return null;
} }
Method cachedMethod = BlockClassRegistry.lookupFallback(fallbackClass, fallback); Method cachedMethod = BlockClassRegistry.lookupFallback(fallbackClass, fallback);
if (cachedMethod == null) { if (cachedMethod == null) {
cachedMethod = ClassUtils.getStaticMethod(fallbackClass, fallback, cachedMethod = ClassUtils.getStaticMethod(fallbackClass, fallback,
BlockException.class); BlockException.class);
BlockClassRegistry.updateFallbackFor(fallbackClass, fallback, cachedMethod); BlockClassRegistry.updateFallbackFor(fallbackClass, fallback, cachedMethod);
} }
return cachedMethod; return cachedMethod;
} }
private Method extractBlockHandlerMethod(String block, Class<?> blockClass) { private Method extractBlockHandlerMethod(String block, Class<?> blockClass) {
if (StringUtil.isBlank(block) || blockClass == void.class) { if (StringUtil.isBlank(block) || blockClass == void.class) {
return null; return null;
} }
Method cachedMethod = BlockClassRegistry.lookupBlockHandler(blockClass, block); Method cachedMethod = BlockClassRegistry.lookupBlockHandler(blockClass, block);
if (cachedMethod == null) { if (cachedMethod == null) {
cachedMethod = ClassUtils.getStaticMethod(blockClass, block, cachedMethod = ClassUtils.getStaticMethod(blockClass, block,
BlockException.class); BlockException.class);
BlockClassRegistry.updateBlockHandlerFor(blockClass, block, cachedMethod); BlockClassRegistry.updateBlockHandlerFor(blockClass, block, cachedMethod);
} }
return cachedMethod; return cachedMethod;
} }
private boolean isDegradeFailure(BlockException ex) { private boolean isDegradeFailure(BlockException ex) {
return ex instanceof DegradeException; return ex instanceof DegradeException;
} }
} }

View File

@ -31,6 +31,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.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; 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.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.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
@ -60,10 +62,12 @@ public class SentinelEndpoint extends AbstractEndpoint<Map<String, Object>> {
List<FlowRule> flowRules = FlowRuleManager.getRules(); List<FlowRule> flowRules = FlowRuleManager.getRules();
List<DegradeRule> degradeRules = DegradeRuleManager.getRules(); List<DegradeRule> degradeRules = DegradeRuleManager.getRules();
List<SystemRule> systemRules = SystemRuleManager.getRules(); List<SystemRule> systemRules = SystemRuleManager.getRules();
List<ParamFlowRule> paramFlowRules = ParamFlowRuleManager.getRules();
result.put("properties", sentinelProperties); result.put("properties", sentinelProperties);
result.put("FlowRules", flowRules); result.put("FlowRules", flowRules);
result.put("DegradeRules", degradeRules); result.put("DegradeRules", degradeRules);
result.put("SystemRules", systemRules); result.put("SystemRules", systemRules);
result.put("ParamFlowRule", paramFlowRules);
result.put("datasources", new HashMap<String, Object>()); result.put("datasources", new HashMap<String, Object>());
for (String dataSourceBeanName : dataSourceHandler.getDataSourceBeanNameList()) { for (String dataSourceBeanName : dataSourceHandler.getDataSourceBeanNameList()) {

View File

@ -0,0 +1,56 @@
/*
* 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);
for (MethodMetadata metadata : metadatas) {
metadataMap.put(targetType.getName() + metadata.configKey(), metadata);
}
return metadatas;
}
}

View File

@ -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.netflix.feign.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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -12,12 +12,6 @@
"defaultValue": false, "defaultValue": false,
"description": "earlier initialize heart-beat when the spring container starts when the transport dependency is on classpath, the configuration is effective." "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", "name": "spring.cloud.sentinel.transport.port",
"type": "java.lang.String", "type": "java.lang.String",
@ -40,6 +34,12 @@
"defaultValue": "Integer.MIN_VALUE", "defaultValue": "Integer.MIN_VALUE",
"description": "sentinel filter chain order, will be set to FilterRegistrationBean." "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", "name": "spring.cloud.sentinel.metric.fileSingleSize",
"type": "java.lang.String", "type": "java.lang.String",
@ -50,6 +50,17 @@
"type": "java.lang.String", "type": "java.lang.String",
"description": "the total metric file count." "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", "name": "spring.cloud.sentinel.servlet.blockPage",
"type": "java.lang.String", "type": "java.lang.String",

View File

@ -1,4 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\ 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

View File

@ -16,14 +16,14 @@
package org.springframework.cloud.alibaba.sentinel; package org.springframework.cloud.alibaba.sentinel;
import com.alibaba.csp.sentinel.slots.block.BlockException; import static org.assertj.core.api.Assertions.assertThat;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.servlet.FilterRegistrationBean; 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.SentinelAutoConfiguration;
import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor; import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor;
import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor; import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor;
@ -32,84 +32,86 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import com.alibaba.csp.sentinel.slots.block.BlockException;
/** /**
* @author fangjian * @author fangjian
*/ */
public class SentinelAutoConfigurationTests { public class SentinelAutoConfigurationTests {
private final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); private final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
@Before @Before
public void init() { public void init() {
context.register(SentinelAutoConfiguration.class, SentinelWebAutoConfiguration.class, context.register(SentinelAutoConfiguration.class,
SentinelTestConfiguration.class); SentinelWebAutoConfiguration.class, SentinelTestConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, EnvironmentTestUtils.addEnvironment(this.context,
"spring.cloud.sentinel.transport.port=8888", "spring.cloud.sentinel.transport.port=8888",
"spring.cloud.sentinel.filter.order=123", "spring.cloud.sentinel.filter.order=123",
"spring.cloud.sentinel.filter.urlPatterns=/*,/test"); "spring.cloud.sentinel.filter.urlPatterns=/*,/test");
this.context.refresh(); this.context.refresh();
} }
@After @After
public void closeContext() { public void closeContext() {
this.context.close(); this.context.close();
} }
@Test @Test
public void testFilter() { public void testFilter() {
assertThat(context.getBean( assertThat(context.getBean("servletRequestListener")
"servletRequestListener").getClass() == FilterRegistrationBean.class).isTrue(); .getClass() == FilterRegistrationBean.class).isTrue();
} }
@Test @Test
public void testBeanPostProcessor() { public void testBeanPostProcessor() {
assertThat(context.getBean("sentinelBeanPostProcessor") assertThat(context.getBean("sentinelBeanPostProcessor")
.getClass() == SentinelBeanPostProcessor.class).isTrue(); .getClass() == SentinelBeanPostProcessor.class).isTrue();
} }
@Test @Test
public void testProperties() { public void testProperties() {
SentinelProperties sentinelProperties = context.getBean(SentinelProperties.class); SentinelProperties sentinelProperties = context.getBean(SentinelProperties.class);
assertThat(sentinelProperties).isNotNull(); assertThat(sentinelProperties).isNotNull();
assertThat(sentinelProperties.getTransport().getPort()).isEqualTo("8888"); assertThat(sentinelProperties.getTransport().getPort()).isEqualTo("8888");
assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(2); assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(2);
assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)).isEqualTo("/*"); assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0))
assertThat(sentinelProperties.getFilter().getUrlPatterns().get(1)).isEqualTo("/test"); .isEqualTo("/*");
} assertThat(sentinelProperties.getFilter().getUrlPatterns().get(1))
.isEqualTo("/test");
}
@Test @Test
public void testRestTemplate() { public void testRestTemplate() {
assertThat(context.getBeansOfType(RestTemplate.class).size()).isEqualTo(2); assertThat(context.getBeansOfType(RestTemplate.class).size()).isEqualTo(2);
RestTemplate restTemplate = context.getBean("restTemplateWithBlockClass", RestTemplate restTemplate = context.getBean("restTemplateWithBlockClass",
RestTemplate.class); RestTemplate.class);
assertThat(restTemplate.getInterceptors().size()).isEqualTo(1); assertThat(restTemplate.getInterceptors().size()).isEqualTo(1);
assertThat(restTemplate.getInterceptors().get(0).getClass()) assertThat(restTemplate.getInterceptors().get(0).getClass())
.isEqualTo(SentinelProtectInterceptor.class); .isEqualTo(SentinelProtectInterceptor.class);
} }
@Configuration @Configuration
static class SentinelTestConfiguration { static class SentinelTestConfiguration {
@Bean @Bean
@SentinelProtect @SentinelRestTemplate
RestTemplate restTemplate() { RestTemplate restTemplate() {
return new RestTemplate(); return new RestTemplate();
} }
@Bean @Bean
@SentinelProtect(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException")
RestTemplate restTemplateWithBlockClass() { RestTemplate restTemplateWithBlockClass() {
return new RestTemplate(); return new RestTemplate();
} }
} }
static class ExceptionUtil { static class ExceptionUtil {
public static void handleException(BlockException ex) { public static void handleException(BlockException ex) {
System.out.println("Oops: " + ex.getClass().getCanonicalName()); System.out.println("Oops: " + ex.getClass().getCanonicalName());
} }
} }
} }