From 53643b94cef2f472b83a0fc7e7c00bf89f835be2 Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Wed, 5 Dec 2018 12:45:02 +0800 Subject: [PATCH 1/6] fixes #9 --- .../cloud/examples/ServiceApplication.java | 13 +++---- ...Protect.java => SentinelRestTemplate.java} | 8 +++-- .../custom/SentinelBeanPostProcessor.java | 34 +++++++++---------- .../custom/SentinelProtectInterceptor.java | 21 ++++++------ .../SentinelAutoConfigurationTests.java | 6 ++-- 5 files changed, 44 insertions(+), 38 deletions(-) rename spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/{SentinelProtect.java => SentinelRestTemplate.java} (79%) 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 2f258d7b..c05c01af 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 @@ -1,12 +1,13 @@ package org.springframework.cloud.alibaba.cloud.examples; -import com.alibaba.csp.sentinel.datasource.Converter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; +import com.alibaba.csp.sentinel.datasource.Converter; + /** * @author xiaojing */ @@ -14,7 +15,7 @@ import org.springframework.web.client.RestTemplate; public class ServiceApplication { @Bean - @SentinelProtect(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) + @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); } @@ -25,9 +26,9 @@ public class ServiceApplication { } @Bean - public Converter myConverter() { - return new JsonFlowRuleListConverter(); - } + public Converter myConverter() { + return new JsonFlowRuleListConverter(); + } public static void main(String[] args) { SpringApplication.run(ServiceApplication.class, args); 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/SentinelBeanPostProcessor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java index 0932ac85..d9e9cd7e 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,17 +25,17 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.context.ApplicationContext; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; /** - * PostProcessor handle @SentinelProtect Annotation, add interceptor for RestTemplate + * PostProcessor handle @SentinelRestTemplate Annotation, add interceptor for RestTemplate * * @author Jim - * @see SentinelProtect + * @see SentinelRestTemplate * @see SentinelProtectInterceptor */ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { @@ -43,16 +43,16 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces @Autowired private ApplicationContext applicationContext; - private ConcurrentHashMap 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 + SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition .getSource()).getIntrospectedMethod() - .getAnnotation(SentinelProtect.class); - cache.put(beanName, sentinelProtect); + .getAnnotation(SentinelRestTemplate.class); + cache.put(beanName, sentinelRestTemplate); } } @@ -61,26 +61,26 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces return beanType == RestTemplate.class && beanDefinition.getSource() instanceof StandardMethodMetadata && ((StandardMethodMetadata) beanDefinition.getSource()) - .isAnnotated(SentinelProtect.class.getName()); + .isAnnotated(SentinelRestTemplate.class.getName()); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (cache.containsKey(beanName)) { - // add interceptor for each RestTemplate with @SentinelProtect annotation + // add interceptor for each RestTemplate with @SentinelRestTemplate annotation StringBuilder interceptorBeanName = new StringBuilder(); - SentinelProtect sentinelProtect = cache.get(beanName); + SentinelRestTemplate sentinelRestTemplate = cache.get(beanName); interceptorBeanName .append(StringUtils.uncapitalize( SentinelProtectInterceptor.class.getSimpleName())) .append("_") - .append(sentinelProtect.blockHandlerClass().getSimpleName()) - .append(sentinelProtect.blockHandler()).append("_") - .append(sentinelProtect.fallbackClass().getSimpleName()) - .append(sentinelProtect.fallback()); + .append(sentinelRestTemplate.blockHandlerClass().getSimpleName()) + .append(sentinelRestTemplate.blockHandler()).append("_") + .append(sentinelRestTemplate.fallbackClass().getSimpleName()) + .append(sentinelRestTemplate.fallback()); RestTemplate restTemplate = (RestTemplate) bean; - registerBean(interceptorBeanName.toString(), sentinelProtect); + registerBean(interceptorBeanName.toString(), sentinelRestTemplate); SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext .getBean(interceptorBeanName.toString(), SentinelProtectInterceptor.class); @@ -90,13 +90,13 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces } private void registerBean(String interceptorBeanName, - SentinelProtect sentinelProtect) { + SentinelRestTemplate sentinelRestTemplate) { // register SentinelProtectInterceptor bean DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext .getAutowireCapableBeanFactory(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(SentinelProtectInterceptor.class); - beanDefinitionBuilder.addConstructorArgValue(sentinelProtect); + beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate); BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder .getRawBeanDefinition(); beanFactory.registerBeanDefinition(interceptorBeanName, 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 844a5416..2d18beef 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,19 +37,19 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.util.StringUtil; /** - * Interceptor using by SentinelProtect and SentinelProtectInterceptor + * Interceptor using by SentinelRestTemplate * * @author fangjian */ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { - private static final Logger logger = LoggerFactory + private static final Logger logger = LoggerFactory .getLogger(SentinelProtectInterceptor.class); - private SentinelProtect sentinelProtect; + private SentinelRestTemplate sentinelRestTemplate; - public SentinelProtectInterceptor(SentinelProtect sentinelProtect) { - this.sentinelProtect = sentinelProtect; + public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate) { + this.sentinelRestTemplate = sentinelRestTemplate; } @Override @@ -92,15 +92,16 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor Object[] args = new Object[] { ex }; // handle degrade if (isDegradeFailure(ex)) { - Method method = extractFallbackMethod(sentinelProtect.fallback(), - sentinelProtect.fallbackClass()); + Method method = extractFallbackMethod(sentinelRestTemplate.fallback(), + sentinelRestTemplate.fallbackClass()); if (method != null) { method.invoke(null, args); } } // handle block - Method blockHandler = extractBlockHandlerMethod(sentinelProtect.blockHandler(), - sentinelProtect.blockHandlerClass()); + Method blockHandler = extractBlockHandlerMethod( + sentinelRestTemplate.blockHandler(), + sentinelRestTemplate.blockHandlerClass()); if (blockHandler != null) { blockHandler.invoke(null, args); } 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 647fde93..1ba2c170 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 @@ -22,7 +22,7 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration; import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor; import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor; @@ -92,13 +92,13 @@ public class SentinelAutoConfigurationTests { static class SentinelTestConfiguration { @Bean - @SentinelProtect + @SentinelRestTemplate RestTemplate restTemplate() { return new RestTemplate(); } @Bean - @SentinelProtect(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") + @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") RestTemplate restTemplateWithBlockClass() { return new RestTemplate(); } From a56077496649c9aa4838e085d729f536d15a0bda Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Thu, 6 Dec 2018 11:50:48 +0800 Subject: [PATCH 2/6] Sentinel support feign and close #6 --- spring-cloud-alibaba-sentinel/pom.xml | 7 + .../feign/SentinelContractHolder.java | 55 ++++++ .../alibaba/sentinel/feign/SentinelFeign.java | 148 +++++++++++++++ .../feign/SentinelFeignAutoConfiguration.java | 45 +++++ .../feign/SentinelInvocationHandler.java | 172 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- 6 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java create mode 100644 spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeign.java create mode 100644 spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelFeignAutoConfiguration.java create mode 100644 spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java diff --git a/spring-cloud-alibaba-sentinel/pom.xml b/spring-cloud-alibaba-sentinel/pom.xml index b4f63c9f..d635b2a0 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -35,6 +35,13 @@ sentinel-dubbo-adapter + + org.springframework.cloud + spring-cloud-starter-openfeign + provided + true + + org.springframework.cloud spring-cloud-alibaba-sentinel-datasource 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..6056e800 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.alibaba.sentinel.feign; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import feign.Contract; +import feign.MethodMetadata; + +/** + * + * Using static field {@link SentinelContractHolder#metadataMap} to hold + * {@link MethodMetadata} data + * + * @author 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); + metadatas.forEach(metadata -> 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..5da3c1bc --- /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.openfeign.FeignContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.ReflectionUtils; + +import feign.Contract; +import feign.Feign; +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.hystrix.FallbackFactory; +import feign.hystrix.HystrixFeign; + +/** + * {@link Feign.Builder} like {@link HystrixFeign.Builder} + * + * @author 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/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 From 0af67df3e3eea9f666fa2b450267b01febea3c32 Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Thu, 6 Dec 2018 14:16:47 +0800 Subject: [PATCH 3/6] using sentinel feign in nacos discovery example --- .../nacos-discovery-consumer-example/pom.xml | 5 +++ .../cloud/examples/ConsumerApplication.java | 35 ++++++++++++++++++- .../cloud/examples/TestController.java | 11 ++++++ .../src/main/resources/application.properties | 10 +++++- .../src/main/resources/flowrule.json | 10 ++++++ .../cloud/examples/ProviderApplication.java | 6 ++++ 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json 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 2699f7c3..11c588bd 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-openfeign + + 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 86be39c4..5dfd4eba 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.openfeign.EnableFeignClients; @@ -10,6 +11,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; /** @@ -30,9 +32,40 @@ public class ConsumerApplication { SpringApplication.run(ConsumerApplication.class, args); } - @FeignClient(name = "service-provider") + @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) String echo(@PathVariable("str") String str); + + @RequestMapping(value = "/divide", method = RequestMethod.GET) + String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b); + + @RequestMapping(value = "/notFound", method = RequestMethod.GET) + String notFound(); + } + +} + +class FeignConfiguration { + @Bean + public EchoServiceFallback echoServiceFallback() { + return new EchoServiceFallback(); + } +} + +class EchoServiceFallback implements EchoService { + @Override + public String echo(@PathVariable("str") String str) { + return "echo fallback"; + } + + @Override + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return "divide fallback"; + } + + @Override + public String notFound() { + return "notFound fallback"; } } 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 5186f2d8..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; @@ -29,6 +30,16 @@ public class TestController { String.class); } + @RequestMapping(value = "/notFound-feign", method = RequestMethod.GET) + public String notFound() { + return echoService.notFound(); + } + + @RequestMapping(value = "/divide-feign", method = RequestMethod.GET) + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return echoService.divide(a, b); + } + @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET) public String feign(@PathVariable String str) { return echoService.echo(str); 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..fbc9736e 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 +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); + } } } From cd6f79b16b171064b938f06fbe914ba1248ed84b Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Thu, 6 Dec 2018 16:02:18 +0800 Subject: [PATCH 4/6] Polish #115 --- spring-cloud-alibaba-dependencies/pom.xml | 5 +++++ .../pom.xml | 7 +++++++ .../datasource/converter/JsonConverter.java | 19 ++++++++++++++---- .../datasource/converter/XmlConverter.java | 20 ++++++++++++++----- spring-cloud-alibaba-sentinel/pom.xml | 5 +++++ .../sentinel/endpoint/SentinelEndpoint.java | 4 ++++ 6 files changed, 51 insertions(+), 9 deletions(-) diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 4d2b1ce2..edbcde12 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -89,6 +89,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-sentinel-datasource/pom.xml b/spring-cloud-alibaba-sentinel-datasource/pom.xml index 5d7d8515..2a77bf5a 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 0247ba80..591d61bd 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 @@ -18,6 +18,7 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,6 +33,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @see DegradeRule * @see SystemRule * @see AuthorityRule + * @see ParamFlowRule * @see ObjectMapper */ public class JsonConverter implements Converter> { @@ -48,7 +50,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; } @@ -68,7 +70,7 @@ public class JsonConverter implements Converter> { List rules = Arrays.asList(convertFlowRule(itemJson), convertDegradeRule(itemJson), convertSystemRule(itemJson), - convertAuthorityRule(itemJson)); + convertAuthorityRule(itemJson), convertParamFlowRule(itemJson)); List convertRuleList = rules.stream() .filter(rule -> !ObjectUtils.isEmpty(rule)) @@ -101,8 +103,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; } @@ -154,4 +154,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 b4a95a99..6ff3f8bb 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 @@ -18,6 +18,7 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,7 +33,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; * @see DegradeRule * @see SystemRule * @see AuthorityRule - * @see XmlMapper + * @see ParamFlowRule */ public class XmlConverter implements Converter> { @@ -48,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; } @@ -68,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 = rules.stream() .filter(rule -> !ObjectUtils.isEmpty(rule)) @@ -101,8 +102,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; } @@ -154,4 +153,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 d635b2a0..f2068eb3 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -42,6 +42,11 @@ 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/endpoint/SentinelEndpoint.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java index f20098c2..03b6c0f8 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 @@ -32,6 +32,8 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; @@ -58,10 +60,12 @@ public class SentinelEndpoint { List 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()); dataSourceHandler.getDataSourceBeanNameList().forEach(dataSourceBeanName -> { ReadableDataSource dataSource = applicationContext.getBean(dataSourceBeanName, From ac2eee154d54bd83ff199d53167372a963249d9b Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Thu, 6 Dec 2018 16:09:32 +0800 Subject: [PATCH 5/6] Add log configuration for Sentinel --- .../alibaba/sentinel/SentinelProperties.java | 72 +++++++++++++++---- .../custom/SentinelAutoConfiguration.java | 18 +++-- ...itional-spring-configuration-metadata.json | 23 ++++-- 3 files changed, 90 insertions(+), 23 deletions(-) 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 fb3cba2e..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 { @@ -299,4 +315,34 @@ public class SentinelProperties { } } + public static class Log { + + /** + * sentinel log base dir + */ + private String dir; + + /** + * distinguish the log file by pid number + */ + private boolean switchPid = false; + + public String getDir() { + return dir; + } + + public void setDir(String dir) { + this.dir = dir; + } + + public boolean isSwitchPid() { + return switchPid; + } + + public void setSwitchPid(boolean switchPid) { + this.switchPid = switchPid; + } + + } + } 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 093cab8b..a651c8f8 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 @@ -41,6 +41,7 @@ import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.AppNameUtil; @@ -71,7 +72,6 @@ public class SentinelAutoConfiguration { @PostConstruct private void init() { - if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME)) && StringUtils.hasText(projectName)) { System.setProperty(AppNameUtil.APP_NAME, projectName); @@ -93,8 +93,9 @@ public class SentinelAutoConfiguration { properties.getTransport().getHeartbeatIntervalMs()); } if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) - && StringUtils.hasText(properties.getCharset())) { - System.setProperty(SentinelConfig.CHARSET, properties.getCharset()); + && StringUtils.hasText(properties.getMetric().getCharset())) { + System.setProperty(SentinelConfig.CHARSET, + properties.getMetric().getCharset()); } if (StringUtils .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) @@ -113,10 +114,19 @@ public class SentinelAutoConfiguration { System.setProperty(SentinelConfig.COLD_FACTOR, properties.getFlow().getColdFactor()); } - if (StringUtils.hasText(properties.getServlet().getBlockPage())) { WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); } + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) + && StringUtils.hasText(properties.getLog().getDir())) { + System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir()); + } + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID)) + && properties.getLog().isSwitchPid()) { + System.setProperty(LogBase.LOG_NAME_USE_PID, + String.valueOf(properties.getLog().isSwitchPid())); + } + urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); 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", From 54dcd7eaec540b0ae97a33707b1da94c3a568e0b Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Thu, 6 Dec 2018 16:11:42 +0800 Subject: [PATCH 6/6] update sentinel docs --- .../src/main/asciidoc-zh/sentinel.adoc | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc index e8c575ac..ed1b0d83 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc @@ -100,7 +100,53 @@ spring: ### Feign 支持 -DOING +Sentinel 适配了 https://github.com/OpenFeign/feign[Feign] 组件。如果想使用,除了引入 `sentinel-starter` 的依赖外还需要 3 个步骤: + +* 配置文件打开 sentinel 对 feign 的支持:`feign.sentinel.enabled=true` +* 加入 `feign starter` 依赖触发 `sentinel starter` 的配置类生效: +```xml + + org.springframework.cloud + spring-cloud-starter-openfeign + +``` +* `feign` 内部的 `loadbalance` 功能依赖 Netflix 的 `ribbon` 模块(如果不使用 `loadbalance` 功能可不引入): +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + +``` + +这是一个 `FeignClient` 对应的接口: + +```java +@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) +public interface EchoService { + @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) + String echo(@PathVariable("str") String str); +} + +class FeignConfiguration { + @Bean + public EchoServiceFallback echoServiceFallback() { + return new EchoServiceFallback(); + } +} + +class EchoServiceFallback implements EchoService { + @Override + public String echo(@PathVariable("str") String str) { + return "echo fallback"; + } +} +``` + +NOTE: Feign 对应的接口中的资源名策略定义:httpmethod:http://requesturl + +`EchoService` 接口中方法 `echo` 对应的资源名为 `GET:http://service-provider/echo/{str}`。 + +请注意:`@FeignClient` 注解中的所有属性,Sentinel 都做了兼容。 ### RestTemplate 支持 @@ -124,10 +170,11 @@ public RestTemplate restTemplate() { NOTE: 以 `https://www.taobao.com/test` 这个 url 为例。对应的资源名有两种粒度,分别是 `https://www.taobao.com:80` 以及 `https://www.taobao.com:80/test` - ### 动态数据源支持 -*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前*,要在 Spring Cloud Alibaba Sentinel 下使用动态数据源,需要3个步骤: +#### 在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之前 + +需要3个步骤才可完成数据源的配置: * 配置文件中定义数据源信息。比如使用文件: @@ -173,7 +220,9 @@ private ReadableDataSource dataSource; [Sentinel Starter] load 3 flow rules ``` -*在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后*,要在 Spring Cloud Alibaba Sentinel 下使用动态数据源,只需要1个步骤: +#### 在版本 0.2.0.RELEASE 或 0.1.0.RELEASE 之后 + +只需要1个步骤就可完成数据源的配置: * 直接在 `application.properties` 配置文件中配置数据源信息即可 @@ -236,6 +285,16 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo ### More +下表显示当应用的 `ApplicationContext` 中存在对应的Bean的类型时,会进行的一些操作: + +:frame: topbot +[width="60%",options="header"] +|==== +^|存在Bean的类型 ^|操作 ^|作用 +|`UrlCleaner`|`WebCallbackManager.setUrlCleaner(urlCleaner)`|资源清理(资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下)) +|`UrlBlockHandler`|`WebCallbackManager.setUrlBlockHandler(urlBlockHandler)`|自定义限流处理逻辑 +|==== + 下表显示 Spring Cloud Alibaba Sentinel 的所有配置信息: :frame: topbot @@ -244,14 +303,16 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo ^|配置项 ^|含义 ^|默认值 |`spring.cloud.sentinel.enabled`|Sentinel自动化配置是否生效|true |`spring.cloud.sentinel.eager`|取消Sentinel控制台懒加载|false -|`spring.cloud.sentinel.charset`|metric文件字符集|UTF-8 |`spring.cloud.sentinel.transport.port`|应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer|8721 |`spring.cloud.sentinel.transport.dashboard`|Sentinel 控制台地址| |`spring.cloud.sentinel.transport.heartbeatIntervalMs`|应用与Sentinel控制台的心跳间隔时间| |`spring.cloud.sentinel.filter.order`|Servlet Filter的加载顺序。Starter内部会构造这个filter|Integer.MIN_VALUE |`spring.cloud.sentinel.filter.spring.url-patterns`|数据类型是数组。表示Servlet Filter的url pattern集合|/* +|`spring.cloud.sentinel.metric.charset`|metric文件字符集|UTF-8 |`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric 单个文件的大小| |`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric 总文件数量| +|`spring.cloud.sentinel.log.dir`|Sentinel 日志文件所在的目录| +|`spring.cloud.sentinel.log.switch-pid`|Sentinel 日志文件名是否需要带上pid|false |`spring.cloud.sentinel.servlet.blockPage`| 自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL | |`spring.cloud.sentinel.flow.coldFactor`| https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8[冷启动因子] |3 |====