diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 7639ee0bf..ed51123ab 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -16,7 +16,7 @@ Spring Cloud Alibaba Dependencies - 0.1.0 + 0.1.1 @@ -41,6 +41,16 @@ sentinel-transport-simple-http ${sentinel.version} + + com.alibaba.csp + sentinel-annotation-aspectj + ${sentinel.version} + + + com.alibaba.csp + sentinel-dubbo-adapter + ${sentinel.version} + diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml b/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml index 25c0ef217..7b10ee91d 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml +++ b/spring-cloud-alibaba-sentinel-autoconfigure/pom.xml @@ -25,6 +25,16 @@ sentinel-transport-simple-http + + com.alibaba.csp + sentinel-annotation-aspectj + + + + com.alibaba.csp + sentinel-dubbo-adapter + + diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java index 73795218a..0f4fb4fee 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java @@ -16,11 +16,12 @@ package org.springframework.cloud.alibaba.sentinel; -import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import java.util.ArrayList; import java.util.List; -import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import javax.annotation.PostConstruct; +import javax.servlet.Filter; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -33,14 +34,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; -import javax.annotation.PostConstruct; -import javax.servlet.Filter; +import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; /** * @author xiaojing */ @Configuration -@ConditionalOnWebApplication @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelWebAutoConfiguration { diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelCustomAspectAutoConfiguration.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java similarity index 61% rename from spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelCustomAspectAutoConfiguration.java rename to spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java index 77cde741c..6586c8d31 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelCustomAspectAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/annotation/SentinelProtect.java @@ -14,17 +14,24 @@ * limitations under the License. */ -package org.springframework.cloud.alibaba.sentinel.custom; +package org.springframework.cloud.alibaba.sentinel.annotation; -import org.springframework.context.annotation.Bean; +import java.lang.annotation.*; /** - * @author xiaojing + * @author fangjian */ -public class SentinelCustomAspectAutoConfiguration { +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SentinelProtect { + + String blockHandler() default ""; + + Class blockHandlerClass() default void.class; + + String fallback() default ""; + + Class fallbackClass() default void.class; - @Bean - public SentinelAspect sentinelAspect() { - return new SentinelAspect(); - } } diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/BlockClassRegistry.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/BlockClassRegistry.java new file mode 100644 index 000000000..42eeca3cf --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/BlockClassRegistry.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.alibaba.sentinel.custom; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + * @author fangjian + */ +final class BlockClassRegistry { + + private static final Map FALLBACK_MAP = new ConcurrentHashMap<>(); + private static final Map BLOCK_HANDLER_MAP = new ConcurrentHashMap<>(); + + static Method lookupFallback(Class clazz, String name) { + return FALLBACK_MAP.get(getKey(clazz, name)); + } + + static Method lookupBlockHandler(Class clazz, String name) { + return BLOCK_HANDLER_MAP.get(getKey(clazz, name)); + } + + static void updateFallbackFor(Class clazz, String name, Method method) { + if (clazz == null || StringUtil.isBlank(name)) { + throw new IllegalArgumentException("Bad argument"); + } + FALLBACK_MAP.put(getKey(clazz, name), method); + } + + static void updateBlockHandlerFor(Class clazz, String name, Method method) { + if (clazz == null || StringUtil.isBlank(name)) { + throw new IllegalArgumentException("Bad argument"); + } + BLOCK_HANDLER_MAP.put(getKey(clazz, name), method); + } + + private static String getKey(Class clazz, String name) { + return String.format("%s:%s", clazz.getCanonicalName(), name); + } + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/EnableSentinel.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/EnableSentinel.java deleted file mode 100644 index c85bb274c..000000000 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/EnableSentinel.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.alibaba.sentinel.custom; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to add Sentinel to custom method - * @author xiaojing - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface EnableSentinel { - - /** - * @return sentinel resource value - */ - String value(); - - /** - * @return Sentinel BlockException Handler name - */ - String handler() default ""; -} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/HandlerUtil.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/HandlerUtil.java deleted file mode 100644 index 167af1917..000000000 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/HandlerUtil.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.alibaba.sentinel.custom; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author xiaojing - */ -public class HandlerUtil { - - private static final ConcurrentHashMap map = new ConcurrentHashMap<>( - 16); - - /** - * you should add your custom handler before use it - * @param name see {@link EnableSentinel#handler()} - * @param handler you custom handler - */ - public static void addHandler(String name, SentinelBlockHandler handler) { - map.put(name, handler); - } - - public static SentinelBlockHandler getHandler(String name) { - SentinelBlockHandler handler = map.get(name); - if (null == handler) { - throw new RuntimeException("cannot find handler name=<" + name - + ",> did you forgot to invoke HandlerUtil.addHandler(name, handler) ?"); - } - return handler; - } -} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAspect.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAspect.java deleted file mode 100644 index fb923c5d5..000000000 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAspect.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.alibaba.sentinel.custom; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.SphU; -import com.alibaba.csp.sentinel.context.ContextUtil; -import com.alibaba.csp.sentinel.slots.block.BlockException; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author xiaojing - */ -@Aspect -public class SentinelAspect { - - private static final Logger LOGGER = LoggerFactory.getLogger(SentinelAspect.class); - - @Around("@annotation(org.springframework.cloud.alibaba.sentinel.custom.EnableSentinel)") - public Object customBlock(ProceedingJoinPoint pjp) throws Throwable { - SentinelEntry sentinelEntry = new SentinelEntry(); - try { - beforeProceed(sentinelEntry, pjp); - return pjp.proceed(); - } - catch (BlockException e) { - LOGGER.error(e.getMessage(), e); - if (null != sentinelEntry.getHandler() - && sentinelEntry.getHandler().length() > 0) { - return HandlerUtil.getHandler(sentinelEntry.getHandler()).handler(e); - } - else { - throw e; - } - } - finally { - releaseContextResources(sentinelEntry); - } - } - - private void beforeProceed(SentinelEntry sentinelEntry, ProceedingJoinPoint pjp) - throws BlockException { - Method method = getMethod(pjp); - - int modifiers = method.getModifiers(); - - if (!Modifier.isPublic(modifiers) || "toString".equals(method.getName()) - || "hashCode".equals(method.getName()) - || "equals".equals(method.getName()) - || "finalize".equals(method.getName())) { - return; - } - - Annotation[] annotations = method.getDeclaredAnnotations(); - for (Annotation annotation : annotations) { - if (annotation instanceof EnableSentinel) { - sentinelEntry.setKey(((EnableSentinel) annotation).value()); - sentinelEntry.setHandler(((EnableSentinel) annotation).handler()); - } - } - if (null == sentinelEntry.getKey() || sentinelEntry.getKey().length() == 0) { - return; - } - - ContextUtil.enter(sentinelEntry.getKey()); - sentinelEntry.setEntry(SphU.entry(sentinelEntry.getKey())); - } - - private void releaseContextResources(SentinelEntry sentinelEntry) { - - if (null == sentinelEntry.getEntry()) { - return; - } - - Entry entry = sentinelEntry.getEntry(); - entry.exit(); - ContextUtil.exit(); - } - - private Method getMethod(ProceedingJoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - return signature.getMethod(); - } - -} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java new file mode 100644 index 000000000..7d2a2e10a --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.alibaba.sentinel.custom; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; + +/** + * @author xiaojing + */ +@Configuration +public class SentinelAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public SentinelResourceAspect sentinelResourceAspect() { + return new SentinelResourceAspect(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(value = RestTemplate.class) + public SentinelBeanPostProcessor sentinelBeanPostProcessor() { + return new SentinelBeanPostProcessor(); + } + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java new file mode 100644 index 000000000..2d95c06c4 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.alibaba.sentinel.custom; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +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.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 + * + * @author fangjian + * @see SentinelProtect + * @see SentinelProtectInterceptor + */ +public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { + + @Autowired + private ApplicationContext applicationContext; + + 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); + } + } + + private boolean checkSentinelProtect(RootBeanDefinition beanDefinition, + Class beanType) { + return beanType == RestTemplate.class + && beanDefinition.getSource() instanceof StandardMethodMetadata + && ((StandardMethodMetadata) beanDefinition.getSource()) + .isAnnotated(SentinelProtect.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; + } + + 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); + } + +} \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBlockHandler.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBlockHandler.java deleted file mode 100644 index 3b7201fed..000000000 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBlockHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.alibaba.sentinel.custom; - -import com.alibaba.csp.sentinel.slots.block.BlockException; - -/** - * Sentinel Block Handler - * @author xiaojing - */ -public interface SentinelBlockHandler { - - /** - * custom method to process BlockException - * @param e block exception when blocked by sentinel - * @return Object result processed by the handler - */ - Object handler(BlockException e); - -} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelEntry.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelEntry.java deleted file mode 100644 index fdc4f3bf6..000000000 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelEntry.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.alibaba.sentinel.custom; - -import com.alibaba.csp.sentinel.Entry; - -/** - * @author xiaojing - */ -public class SentinelEntry { - - private String key; - private String handler; - - private Entry entry; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getHandler() { - return handler; - } - - public void setHandler(String handler) { - this.handler = handler; - } - - public Entry getEntry() { - return entry; - } - - public void setEntry(Entry entry) { - this.entry = entry; - } -} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java new file mode 100644 index 000000000..2d2c9ba45 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.alibaba.sentinel.custom; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.ClassUtils; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + * Interceptor using by SentinelProtect and SentinelProtectInterceptor + * + * @author fangjian + */ +public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { + + private static final Logger LOGGER = LoggerFactory + .getLogger(SentinelProtectInterceptor.class); + + private SentinelProtect sentinelProtect; + + public SentinelProtectInterceptor(SentinelProtect sentinelProtect) { + this.sentinelProtect = sentinelProtect; + } + + @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 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 boolean isDegradeFailure(BlockException ex) { + return ex instanceof DegradeException; + } + +} diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/spring.factories index f9b510b64..ae146be44 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,4 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\ -org.springframework.cloud.alibaba.sentinel.custom.SentinelCustomAspectAutoConfiguration +org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration diff --git a/spring-cloud-alibaba-sentinel-autoconfigure/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java b/spring-cloud-alibaba-sentinel-autoconfigure/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java index 10608c535..29fbc8ee2 100644 --- a/spring-cloud-alibaba-sentinel-autoconfigure/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java +++ b/spring-cloud-alibaba-sentinel-autoconfigure/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java @@ -16,51 +16,98 @@ package org.springframework.cloud.alibaba.sentinel; +import static org.assertj.core.api.Assertions.assertThat; + 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.custom.SentinelAspect; -import org.springframework.cloud.alibaba.sentinel.custom.SentinelCustomAspectAutoConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelProtect; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; +import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author fangjian */ public class SentinelAutoConfigurationTests { - private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of(SentinelCustomAspectAutoConfiguration.class, SentinelWebAutoConfiguration.class)) - .withPropertyValues("spring.cloud.sentinel.port=8888") - .withPropertyValues("spring.cloud.sentinel.filter.order=123") - .withPropertyValues("spring.cloud.sentinel.filter.urlPatterns=/*,/test"); - - @Test - public void testSentinelAspect() { - this.contextRunner.run(context -> assertThat(context).hasSingleBean(SentinelAspect.class)); - } - - @Test - public void testFilter() { - this.contextRunner.run(context -> { - assertThat(context.getBean( - "servletRequestListener").getClass() == FilterRegistrationBean.class).isTrue(); - }); - } - - @Test - public void testProperties() { - this.contextRunner.run(context -> { - SentinelProperties sentinelProperties = context.getBean(SentinelProperties.class); - assertThat(sentinelProperties.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"); - }); - } + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SentinelAutoConfiguration.class, + SentinelWebAutoConfiguration.class, SentinelTestConfiguration.class)) + .withPropertyValues("spring.cloud.sentinel.port=8888") + .withPropertyValues("spring.cloud.sentinel.filter.order=123") + .withPropertyValues("spring.cloud.sentinel.filter.urlPatterns=/*,/test"); + + @Test + public void testFilter() { + this.contextRunner.run(context -> { + assertThat(context.getBean("servletRequestListener") + .getClass() == FilterRegistrationBean.class).isTrue(); + }); + } + + @Test + public void testBeanPostProcessor() { + this.contextRunner.run(context -> { + assertThat(context.getBean("sentinelBeanPostProcessor") + .getClass() == SentinelBeanPostProcessor.class).isTrue(); + }); + } + + @Test + public void testProperties() { + this.contextRunner.run(context -> { + SentinelProperties sentinelProperties = context + .getBean(SentinelProperties.class); + assertThat(sentinelProperties.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() { + this.contextRunner.run(context -> { + 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 { + + @Bean + @SentinelProtect + RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + @SentinelProtect(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()); + } + } }