diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 356418ff..f438ccf0 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -71,6 +71,11 @@ sentinel-core ${sentinel.version} + + com.alibaba.csp + sentinel-parameter-flow-control + ${sentinel.version} + com.alibaba.csp sentinel-datasource-extension diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml index 03896924..383703cb 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml @@ -40,6 +40,11 @@ spring-cloud-starter-feign + + org.springframework.cloud + spring-cloud-starter-alibaba-sentinel + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java index 96a62331..dddd3368 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ConsumerApplication.java @@ -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.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.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; /** @@ -20,22 +22,50 @@ import org.springframework.web.client.RestTemplate; @EnableFeignClients public class ConsumerApplication { - @LoadBalanced - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } + @LoadBalanced + @Bean + public RestTemplate 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) { - SpringApplication.run(ConsumerApplication.class, args); - } + @RequestMapping(value = "/notFound", method = RequestMethod.GET) + String notFound(); + } - @FeignClient(name = "service-provider") - 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"; + } + + @Override + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return "divide fallback"; + } + + @Override + public String notFound() { + return "notFound fallback"; + } } diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java index b896691b..55c751c7 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestController.java @@ -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; @@ -15,31 +16,42 @@ import org.springframework.web.client.RestTemplate; @RestController public class TestController { - @Autowired - private RestTemplate restTemplate; - @Autowired - private EchoService echoService; + @Autowired + private RestTemplate restTemplate; + @Autowired + private EchoService echoService; + @Autowired + private DiscoveryClient discoveryClient; - @Autowired - private DiscoveryClient discoveryClient; + @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET) + public String rest(@PathVariable String str) { + return restTemplate.getForObject("http://service-provider/echo/" + str, + String.class); + } - @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET) - public String rest(@PathVariable String str) { - return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); - } - @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET) - public String feign(@PathVariable String str) { - return echoService.echo(str); - } + @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="/services/{service}",method = RequestMethod.GET) - public Object client(@PathVariable String service){ - return discoveryClient.getInstances(service); - } - @RequestMapping(value="/services",method = RequestMethod.GET) - public Object services(){ - return discoveryClient.getServices(); - } + @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET) + public String feign(@PathVariable String str) { + return echoService.echo(str); + } + + @RequestMapping(value = "/services/{service}", method = RequestMethod.GET) + public Object client(@PathVariable String service) { + return discoveryClient.getInstances(service); + } + + @RequestMapping(value = "/services", method = RequestMethod.GET) + public Object services() { + return discoveryClient.getServices(); + } } diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties index 1df160fc..530d1a27 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties @@ -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 \ No newline at end of file +management.security.enabled=false +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 \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json new file mode 100644 index 00000000..3dd01162 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json @@ -0,0 +1,10 @@ +[ + { + "resource": "GET:http://service-provider/echo/{str}", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + } +] \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java index 169bccd6..843f5cbe 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java @@ -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); + } } } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java index 93183382..f1fe28e6 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ServiceApplication.java @@ -2,7 +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.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @@ -15,7 +15,7 @@ import com.alibaba.csp.sentinel.datasource.Converter; public class ServiceApplication { @Bean - @SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) + @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); } diff --git a/spring-cloud-alibaba-sentinel-datasource/pom.xml b/spring-cloud-alibaba-sentinel-datasource/pom.xml index db4ed997..b3c636ef 100644 --- a/spring-cloud-alibaba-sentinel-datasource/pom.xml +++ b/spring-cloud-alibaba-sentinel-datasource/pom.xml @@ -18,6 +18,13 @@ com.alibaba.csp sentinel-datasource-extension + true + + + + com.alibaba.csp + sentinel-parameter-flow-control + true diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java index b05626a5..ebe58acc 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/JsonConverter.java @@ -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.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; @@ -31,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @see DegradeRule * @see SystemRule * @see AuthorityRule + * @see ParamFlowRule * @see ObjectMapper */ public class JsonConverter implements Converter> { @@ -47,7 +49,7 @@ public class JsonConverter implements Converter> { public List convert(String source) { List ruleList = new ArrayList<>(); if (StringUtils.isEmpty(source)) { - logger.info( + logger.warn( "Sentinel JsonConverter can not convert rules because source is empty"); return ruleList; } @@ -67,7 +69,7 @@ public class JsonConverter implements Converter> { List rules = Arrays.asList(convertFlowRule(itemJson), convertDegradeRule(itemJson), convertSystemRule(itemJson), - convertAuthorityRule(itemJson)); + convertAuthorityRule(itemJson), convertParamFlowRule(itemJson)); List convertRuleList = new ArrayList<>(); @@ -105,8 +107,6 @@ public class JsonConverter implements Converter> { throw new RuntimeException( "Sentinel JsonConverter convert error: " + e.getMessage(), e); } - logger.info("Sentinel JsonConverter convert {} rules: {}", ruleList.size(), - ruleList); return ruleList; } @@ -158,4 +158,15 @@ public class JsonConverter implements Converter> { return rule; } + private ParamFlowRule convertParamFlowRule(String json) { + ParamFlowRule rule = null; + try { + rule = objectMapper.readValue(json, ParamFlowRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java index c67cc645..af62df74 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/XmlConverter.java @@ -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.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; @@ -31,6 +32,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; * @see DegradeRule * @see SystemRule * @see AuthorityRule + * @see ParamFlowRule * @see XmlMapper */ public class XmlConverter implements Converter> { @@ -47,7 +49,7 @@ public class XmlConverter implements Converter> { public List convert(String source) { List ruleList = new ArrayList<>(); if (StringUtils.isEmpty(source)) { - logger.info( + logger.warn( "Sentinel XmlConverter can not convert rules because source is empty"); return ruleList; } @@ -67,7 +69,7 @@ public class XmlConverter implements Converter> { List rules = Arrays.asList(convertFlowRule(itemXml), convertDegradeRule(itemXml), convertSystemRule(itemXml), - convertAuthorityRule(itemXml)); + convertAuthorityRule(itemXml), convertParamFlowRule(itemXml)); List convertRuleList = new ArrayList<>(); @@ -104,8 +106,6 @@ public class XmlConverter implements Converter> { throw new RuntimeException( "Sentinel XmlConverter convert error: " + e.getMessage(), e); } - logger.info("Sentinel XmlConverter convert {} rules: {}", ruleList.size(), - ruleList); return ruleList; } @@ -157,4 +157,15 @@ public class XmlConverter implements Converter> { return rule; } + private ParamFlowRule convertParamFlowRule(String json) { + ParamFlowRule rule = null; + try { + rule = xmlMapper.readValue(json, ParamFlowRule.class); + } + catch (Exception e) { + // ignore + } + return rule; + } + } diff --git a/spring-cloud-alibaba-sentinel/pom.xml b/spring-cloud-alibaba-sentinel/pom.xml index 31824be2..dba86dfb 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -35,6 +35,18 @@ sentinel-dubbo-adapter + + org.springframework.cloud + spring-cloud-starter-openfeign + provided + true + + + + com.alibaba.csp + sentinel-parameter-flow-control + + org.springframework.cloud spring-cloud-alibaba-sentinel-datasource diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java index feac8acc..d827222e 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java @@ -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 { @@ -298,4 +314,35 @@ public class SentinelProperties { 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; + } + + } + } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelRestTemplate.java similarity index 79% rename from spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java rename to spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelRestTemplate.java index 6586c8d3..da1a3c26 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelRestTemplate.java @@ -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 ""; diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java index ea74c449..924f415c 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java @@ -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.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; @@ -89,8 +90,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)) @@ -109,6 +111,15 @@ public class SentinelAutoConfiguration { System.setProperty(SentinelConfig.COLD_FACTOR, 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())) { WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java index fdad9664..4fb9a6cf 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java @@ -25,87 +25,88 @@ 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 Jim - * @see SentinelProtect + * @see SentinelRestTemplate * @see SentinelProtectInterceptor */ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { - @Autowired - private ApplicationContext applicationContext; + @Autowired + private ApplicationContext applicationContext; - private ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private ConcurrentHashMap cache = new ConcurrentHashMap<>(); - @Override - public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, - Class beanType, String beanName) { - if (checkSentinelProtect(beanDefinition, beanType)) { - SentinelProtect sentinelProtect = ((StandardMethodMetadata) beanDefinition - .getSource()).getIntrospectedMethod() - .getAnnotation(SentinelProtect.class); - cache.put(beanName, sentinelProtect); - } - } + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, + Class beanType, String beanName) { + if (checkSentinelProtect(beanDefinition, beanType)) { + SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition + .getSource()).getIntrospectedMethod() + .getAnnotation(SentinelRestTemplate.class); + cache.put(beanName, sentinelRestTemplate); + } + } - private boolean checkSentinelProtect(RootBeanDefinition beanDefinition, - Class beanType) { - return beanType == RestTemplate.class - && beanDefinition.getSource() instanceof StandardMethodMetadata - && ((StandardMethodMetadata) beanDefinition.getSource()) - .isAnnotated(SentinelProtect.class.getName()); - } + private boolean checkSentinelProtect(RootBeanDefinition beanDefinition, + Class beanType) { + return beanType == RestTemplate.class + && beanDefinition.getSource() instanceof StandardMethodMetadata + && ((StandardMethodMetadata) beanDefinition.getSource()) + .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 - StringBuilder interceptorBeanName = new StringBuilder(); - SentinelProtect sentinelProtect = 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()); - RestTemplate restTemplate = (RestTemplate) bean; - registerBean(interceptorBeanName.toString(), sentinelProtect); - SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext - .getBean(interceptorBeanName.toString(), - SentinelProtectInterceptor.class); - restTemplate.getInterceptors().add(sentinelProtectInterceptor); - } - return bean; - } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (cache.containsKey(beanName)) { + // add interceptor for each RestTemplate with @SentinelRestTemplate annotation + StringBuilder interceptorBeanName = new StringBuilder(); + SentinelRestTemplate sentinelRestTemplate = cache.get(beanName); + interceptorBeanName + .append(StringUtils.uncapitalize( + SentinelProtectInterceptor.class.getSimpleName())) + .append("_") + .append(sentinelRestTemplate.blockHandlerClass().getSimpleName()) + .append(sentinelRestTemplate.blockHandler()).append("_") + .append(sentinelRestTemplate.fallbackClass().getSimpleName()) + .append(sentinelRestTemplate.fallback()); + RestTemplate restTemplate = (RestTemplate) bean; + registerBean(interceptorBeanName.toString(), sentinelRestTemplate); + SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext + .getBean(interceptorBeanName.toString(), + SentinelProtectInterceptor.class); + restTemplate.getInterceptors().add(sentinelProtectInterceptor); + } + return bean; + } - private void registerBean(String interceptorBeanName, - SentinelProtect sentinelProtect) { - // register SentinelProtectInterceptor bean - DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext - .getAutowireCapableBeanFactory(); - BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(SentinelProtectInterceptor.class); - beanDefinitionBuilder.addConstructorArgValue(sentinelProtect); - BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder - .getRawBeanDefinition(); - beanFactory.registerBeanDefinition(interceptorBeanName, - interceptorBeanDefinition); - } + private void registerBean(String interceptorBeanName, + SentinelRestTemplate sentinelRestTemplate) { + // register SentinelProtectInterceptor bean + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext + .getAutowireCapableBeanFactory(); + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(SentinelProtectInterceptor.class); + beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate); + BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder + .getRawBeanDefinition(); + beanFactory.registerBeanDefinition(interceptorBeanName, + interceptorBeanDefinition); + } - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } } \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java index dc22262f..f8fa0820 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java @@ -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,103 +37,104 @@ 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 and SentinelProtectInterceptor * * @author fangjian */ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { - private static final Logger logger = LoggerFactory - .getLogger(SentinelProtectInterceptor.class); + 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 - public ClientHttpResponse intercept(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution) throws IOException { - URI uri = request.getURI(); - String hostResource = uri.getScheme() + "://" + uri.getHost() + ":" - + (uri.getPort() == -1 ? 80 : uri.getPort()); - String hostWithPathResource = hostResource + uri.getPath(); - Entry hostEntry = null, hostWithPathEntry = null; - ClientHttpResponse response = null; - try { - ContextUtil.enter(hostWithPathResource); - hostWithPathEntry = SphU.entry(hostWithPathResource); - hostEntry = SphU.entry(hostResource); - response = execution.execute(request, body); - } - catch (BlockException e) { - logger.error("RestTemplate block", e); - try { - handleBlockException(e); - } - catch (Exception ex) { - logger.error("sentinel handle BlockException error.", e); - } - } - finally { - if (hostEntry != null) { - hostEntry.exit(); - } - if (hostWithPathEntry != null) { - hostWithPathEntry.exit(); - } - ContextUtil.exit(); - } - return response; - } + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + URI uri = request.getURI(); + String hostResource = uri.getScheme() + "://" + uri.getHost() + ":" + + (uri.getPort() == -1 ? 80 : uri.getPort()); + String hostWithPathResource = hostResource + uri.getPath(); + Entry hostEntry = null, hostWithPathEntry = null; + ClientHttpResponse response = null; + try { + ContextUtil.enter(hostWithPathResource); + hostWithPathEntry = SphU.entry(hostWithPathResource); + hostEntry = SphU.entry(hostResource); + response = execution.execute(request, body); + } + catch (BlockException e) { + logger.error("RestTemplate block", e); + try { + handleBlockException(e); + } + catch (Exception ex) { + logger.error("sentinel handle BlockException error.", e); + } + } + finally { + if (hostEntry != null) { + hostEntry.exit(); + } + if (hostWithPathEntry != null) { + hostWithPathEntry.exit(); + } + ContextUtil.exit(); + } + return response; + } - private void handleBlockException(BlockException ex) throws Exception { - Object[] args = new Object[] { ex }; - // handle degrade - if (isDegradeFailure(ex)) { - Method method = extractFallbackMethod(sentinelProtect.fallback(), - sentinelProtect.fallbackClass()); - if (method != null) { - method.invoke(null, args); - } - } - // handle block - Method blockHandler = extractBlockHandlerMethod(sentinelProtect.blockHandler(), - sentinelProtect.blockHandlerClass()); - if (blockHandler != null) { - blockHandler.invoke(null, args); - } - } + private void handleBlockException(BlockException ex) throws Exception { + Object[] args = new Object[] { ex }; + // handle degrade + if (isDegradeFailure(ex)) { + Method method = extractFallbackMethod(sentinelRestTemplate.fallback(), + sentinelRestTemplate.fallbackClass()); + if (method != null) { + method.invoke(null, args); + } + } + // handle block + Method blockHandler = extractBlockHandlerMethod( + sentinelRestTemplate.blockHandler(), + sentinelRestTemplate.blockHandlerClass()); + if (blockHandler != null) { + blockHandler.invoke(null, args); + } + } - private Method extractFallbackMethod(String fallback, Class fallbackClass) { - if (StringUtil.isBlank(fallback) || fallbackClass == void.class) { - return null; - } - Method cachedMethod = BlockClassRegistry.lookupFallback(fallbackClass, fallback); - if (cachedMethod == null) { - cachedMethod = ClassUtils.getStaticMethod(fallbackClass, fallback, - BlockException.class); - BlockClassRegistry.updateFallbackFor(fallbackClass, fallback, cachedMethod); - } - return cachedMethod; - } + private Method extractFallbackMethod(String fallback, Class fallbackClass) { + if (StringUtil.isBlank(fallback) || fallbackClass == void.class) { + return null; + } + Method cachedMethod = BlockClassRegistry.lookupFallback(fallbackClass, fallback); + if (cachedMethod == null) { + cachedMethod = ClassUtils.getStaticMethod(fallbackClass, fallback, + BlockException.class); + BlockClassRegistry.updateFallbackFor(fallbackClass, fallback, cachedMethod); + } + return cachedMethod; + } - private Method extractBlockHandlerMethod(String block, Class blockClass) { - if (StringUtil.isBlank(block) || blockClass == void.class) { - return null; - } - Method cachedMethod = BlockClassRegistry.lookupBlockHandler(blockClass, block); - if (cachedMethod == null) { - cachedMethod = ClassUtils.getStaticMethod(blockClass, block, - BlockException.class); - BlockClassRegistry.updateBlockHandlerFor(blockClass, block, cachedMethod); - } - return cachedMethod; - } + private Method extractBlockHandlerMethod(String block, Class blockClass) { + if (StringUtil.isBlank(block) || blockClass == void.class) { + return null; + } + Method cachedMethod = BlockClassRegistry.lookupBlockHandler(blockClass, block); + if (cachedMethod == null) { + cachedMethod = ClassUtils.getStaticMethod(blockClass, block, + BlockException.class); + BlockClassRegistry.updateBlockHandlerFor(blockClass, block, cachedMethod); + } + return cachedMethod; + } - private boolean isDegradeFailure(BlockException ex) { - return ex instanceof DegradeException; - } + private boolean isDegradeFailure(BlockException ex) { + return ex instanceof DegradeException; + } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java index 05262314..d7e3e9da 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java @@ -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.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; @@ -60,10 +62,12 @@ public class SentinelEndpoint extends AbstractEndpoint> { List flowRules = FlowRuleManager.getRules(); List degradeRules = DegradeRuleManager.getRules(); List systemRules = SystemRuleManager.getRules(); + List 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()); for (String dataSourceBeanName : dataSourceHandler.getDataSourceBeanNameList()) { diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java new file mode 100644 index 00000000..d5cfb47f --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java @@ -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 Jim + */ +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 metadataMap = new HashMap(); + + public SentinelContractHolder(Contract delegate) { + this.delegate = delegate; + } + + @Override + public List parseAndValidatateMetadata(Class targetType) { + List metadatas = delegate.parseAndValidatateMetadata(targetType); + for (MethodMetadata metadata : metadatas) { + metadataMap.put(targetType.getName() + metadata.configKey(), metadata); + } + return metadatas; + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java new file mode 100644 index 00000000..44a7370a --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java @@ -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 Jim + */ +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 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); + } + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java new file mode 100644 index 00000000..1ed20e1b --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java @@ -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 Jim + */ +@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(); + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java new file mode 100644 index 00000000..809879cc --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java @@ -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 Jim + */ +public class SentinelInvocationHandler implements InvocationHandler { + + private final Target target; + private final Map dispatch; + + private FallbackFactory fallbackFactory; + private Map fallbackMethodMap; + + SentinelInvocationHandler(Target target, Map dispatch, + FallbackFactory fallbackFactory) { + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + this.fallbackFactory = fallbackFactory; + this.fallbackMethodMap = toFallbackMethod(dispatch); + } + + SentinelInvocationHandler(Target target, Map 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 toFallbackMethod(Map dispatch) { + Map result = new LinkedHashMap<>(); + for (Method method : dispatch.keySet()) { + method.setAccessible(true); + result.put(method, method); + } + return result; + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json index b3e40d08..88fe9e5a 100644 --- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories index ae146be4..702bb5bb 100644 --- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories @@ -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 diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java index 957e2a6a..eaa682ca 100644 --- a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java @@ -16,14 +16,14 @@ 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.Before; import org.junit.Test; import org.springframework.boot.test.util.EnvironmentTestUtils; 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; @@ -32,84 +32,86 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; 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 */ public class SentinelAutoConfigurationTests { - private final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + private final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); - @Before - public void init() { - context.register(SentinelAutoConfiguration.class, SentinelWebAutoConfiguration.class, - SentinelTestConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, - "spring.cloud.sentinel.transport.port=8888", - "spring.cloud.sentinel.filter.order=123", - "spring.cloud.sentinel.filter.urlPatterns=/*,/test"); - this.context.refresh(); - } + @Before + public void init() { + context.register(SentinelAutoConfiguration.class, + SentinelWebAutoConfiguration.class, SentinelTestConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.cloud.sentinel.transport.port=8888", + "spring.cloud.sentinel.filter.order=123", + "spring.cloud.sentinel.filter.urlPatterns=/*,/test"); + this.context.refresh(); + } - @After - public void closeContext() { - this.context.close(); - } + @After + public void closeContext() { + this.context.close(); + } - @Test - public void testFilter() { - assertThat(context.getBean( - "servletRequestListener").getClass() == FilterRegistrationBean.class).isTrue(); - } + @Test + public void testFilter() { + assertThat(context.getBean("servletRequestListener") + .getClass() == FilterRegistrationBean.class).isTrue(); + } - @Test - public void testBeanPostProcessor() { - assertThat(context.getBean("sentinelBeanPostProcessor") - .getClass() == SentinelBeanPostProcessor.class).isTrue(); - } + @Test + public void testBeanPostProcessor() { + assertThat(context.getBean("sentinelBeanPostProcessor") + .getClass() == SentinelBeanPostProcessor.class).isTrue(); + } - @Test - public void testProperties() { - SentinelProperties sentinelProperties = context.getBean(SentinelProperties.class); - assertThat(sentinelProperties).isNotNull(); - assertThat(sentinelProperties.getTransport().getPort()).isEqualTo("8888"); - assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(2); - assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)).isEqualTo("/*"); - assertThat(sentinelProperties.getFilter().getUrlPatterns().get(1)).isEqualTo("/test"); - } + @Test + public void testProperties() { + SentinelProperties sentinelProperties = context.getBean(SentinelProperties.class); + assertThat(sentinelProperties).isNotNull(); + assertThat(sentinelProperties.getTransport().getPort()).isEqualTo("8888"); + assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(2); + assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)) + .isEqualTo("/*"); + assertThat(sentinelProperties.getFilter().getUrlPatterns().get(1)) + .isEqualTo("/test"); + } - @Test - public void testRestTemplate() { - assertThat(context.getBeansOfType(RestTemplate.class).size()).isEqualTo(2); - RestTemplate restTemplate = context.getBean("restTemplateWithBlockClass", - RestTemplate.class); - assertThat(restTemplate.getInterceptors().size()).isEqualTo(1); - assertThat(restTemplate.getInterceptors().get(0).getClass()) - .isEqualTo(SentinelProtectInterceptor.class); - } + @Test + public void testRestTemplate() { + assertThat(context.getBeansOfType(RestTemplate.class).size()).isEqualTo(2); + RestTemplate restTemplate = context.getBean("restTemplateWithBlockClass", + RestTemplate.class); + assertThat(restTemplate.getInterceptors().size()).isEqualTo(1); + assertThat(restTemplate.getInterceptors().get(0).getClass()) + .isEqualTo(SentinelProtectInterceptor.class); + } - @Configuration - static class SentinelTestConfiguration { + @Configuration + static class SentinelTestConfiguration { - @Bean - @SentinelProtect - RestTemplate restTemplate() { - return new RestTemplate(); - } + @Bean + @SentinelRestTemplate + RestTemplate restTemplate() { + return new RestTemplate(); + } - @Bean - @SentinelProtect(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") - RestTemplate restTemplateWithBlockClass() { - return new RestTemplate(); - } + @Bean + @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") + RestTemplate restTemplateWithBlockClass() { + return new RestTemplate(); + } - } + } - static class ExceptionUtil { - public static void handleException(BlockException ex) { - System.out.println("Oops: " + ex.getClass().getCanonicalName()); - } - } + static class ExceptionUtil { + public static void handleException(BlockException ex) { + System.out.println("Oops: " + ex.getClass().getCanonicalName()); + } + } }