diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml
index 02b5268c..0abdf666 100755
--- a/sentinel-adapter/pom.xml
+++ b/sentinel-adapter/pom.xml
@@ -24,6 +24,7 @@
sentinel-spring-webflux-adapter
sentinel-api-gateway-adapter-common
sentinel-spring-cloud-gateway-adapter
+ sentinel-spring-webmvc-adapter
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md b/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md
new file mode 100755
index 00000000..1693c281
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md
@@ -0,0 +1,115 @@
+# Sentinel Spring MVC Interceptor
+
+Sentinel provides Spring MVC Interceptor integration to enable flow control for web requests, And support url like '/foo/{id}'
+
+Add the following dependency in `pom.xml` (if you are using Maven):
+
+```xml
+
+ com.alibaba.csp
+ sentinel-spring-webmvc-adapter
+ x.y.z
+
+```
+
+Configure interceptor
+
+```java
+@Configuration
+public class InterceptorConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ //Add sentinel interceptor
+ addSpringMvcInterceptor(registry);
+ //If you want to sentinel the total flow, you can add total interceptor
+ addSpringMvcTotalInterceptor(registry);
+ }
+
+ private void addSpringMvcInterceptor(InterceptorRegistry registry) {
+ //Configure
+ SentinelWebMvcConfig config = new SentinelWebMvcConfig();
+ //Custom configuration if necessary
+ config.setHttpMethodSpecify(true);
+ config.setOriginParser(request -> request.getHeader("S-user"));
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelInterceptor(config)).addPathPatterns("/**");
+ }
+
+ private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) {
+ //Configure
+ SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();
+ //Custom configuration if necessary
+ config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container");
+ config.setTotalResourceName("my-spring-mvc-total-url-request");
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelTotalInterceptor(config)).addPathPatterns("/**");
+ }
+}
+```
+
+Configure 'BlockException' handler, there are three options:
+1. Global exception handling in spring MVC.
+```java
+@ControllerAdvice
+@Order(0)
+public class SentinelSpringMvcBlockHandlerConfig {
+ private Logger logger = LoggerFactory.getLogger(this.getClass());
+ @ExceptionHandler(BlockException.class)
+ @ResponseBody
+ public String sentinelBlockHandler(BlockException e) {
+ AbstractRule rule = e.getRule();
+ logger.info("Blocked by sentinel, {}", rule.toString());
+ return "Blocked by Sentinel";
+ }
+}
+
+```
+2. Use `DefaultBlockExceptionHandler`
+```java
+//SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();
+SentinelWebMvcConfig config = new SentinelWebMvcConfig();
+config.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
+```
+3. `implements BlockExceptionHandler`
+```java
+//SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();
+SentinelWebMvcConfig config = new SentinelWebMvcConfig();
+config.setBlockExceptionHandler((request, response, e) -> {
+ String resourceName = e.getRule().getResource();
+ //Depending on your situation, you can choose to process or throw
+ if ("/hello".equals(resourceName)) {
+ //Do something ......
+ //Write string or error page;
+ response.getWriter().write("Blocked by sentinel");
+ } else {
+ //Handle it in global exception handling
+ throw e;
+ }
+});
+```
+
+Configuration
+- Common configuration in `SentinelWebMvcConfig` and `SentinelWebMvcTotalConfig`
+
+| name | description | type | default value |
+|------|------------|------|-------|
+| blockExceptionHandler| The handler when blocked by sentinel, there are three options:
1. The default value is null, you can hanlde `BlockException` in spring MVC;
2.Use `DefaultBlockExceptionHandler`;
3. `implements BlockExceptionHandler` | `BlockExceptionHandler` | `null` |
+| originParser | `RequestOriginParser` interface is useful for extracting request origin (e.g. IP or appName from HTTP Header) from HTTP request | `RequestOriginParser` | `null` |
+
+- `SentinelWebMvcConfig` configuration
+
+| name | description | type | default value |
+|------|------------|------|-------|
+| urlCleaner | The `UrlCleaner` interface is designed for clean and unify the URL resource. For REST APIs, you can to clean the URL resource (e.g. `/api/user/getById` and `/api/user/getByName` -> `/api/user/getBy*`), avoid the amount of context and will exceed the threshold | `UrlCleaner` | `null` |
+| requestAttributeName | Attribute name in request used by sentinel, please check record log, if it is already used, please set | `String` | sentinel_spring_mvc_entry_container |
+| httpMethodSpecify | Specify http method, for example: GET:/hello | `boolean` | `false` |
+
+
+`SentinelWebMvcTotalConfig` configuration
+
+| name | description | type | default value |
+|------|------------|------|-------|
+| totalResourceName | The resource name in `SentinelTotalInterceptor` | `String` | spring-mvc-total-url-request |
+| requestAttributeName | Attribute name in request used by sentinel, please check record log, if it is already used, please set | `String` | sentinel_spring_mvc_total_entry_container |
+
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml
new file mode 100644
index 00000000..012480d1
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ sentinel-adapter
+ com.alibaba.csp
+ 1.7.0-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-spring-webmvc-adapter
+
+
+ 5.1.8.RELEASE
+ 2.1.3.RELEASE
+ 3.1.0
+
+
+
+
+ com.alibaba.csp
+ sentinel-core
+
+
+ javax.servlet
+ javax.servlet-api
+ ${servlet.api.version}
+ provided
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.boot.version}
+ test
+
+
+ com.alibaba
+ fastjson
+ test
+
+
+
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java
new file mode 100644
index 00000000..4c4d2757
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.adapter.spring.webmvc.config.BaseWebMvcConfig;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * @author kaizi2009
+ */
+public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
+
+ public static final String SPRING_MVC_CONTEXT_NAME = "spring_mvc_context";
+ private static final String EMPTY_ORIGIN = "";
+ protected static final String COLON = ":";
+ private BaseWebMvcConfig baseWebMvcConfig;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws Exception {
+
+ try {
+ String resourceName = getResourceName(request);
+
+ if (StringUtil.isNotEmpty(resourceName)) {
+ // Parse the request origin using registered origin parser.
+ String origin = parseOrigin(request);
+ ContextUtil.enter(SPRING_MVC_CONTEXT_NAME, origin);
+ Entry entry = SphU.entry(resourceName, EntryType.IN);
+
+ setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry);
+ }
+ return true;
+ } catch (BlockException e) {
+ handleBlockException(request, response, e);
+ return false;
+ }
+ }
+
+ /**
+ * Get sentinel resource name.
+ * @param request
+ * @return
+ */
+ protected abstract String getResourceName(HttpServletRequest request);
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+ Object handler, Exception ex) throws Exception {
+ Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
+ if (entry != null) {
+ traceExceptionAndExit(entry, ex);
+ removeEntryInRequest(request);
+ }
+ ContextUtil.exit();
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
+ ModelAndView modelAndView) throws Exception {
+ }
+
+ protected void setEntryInRequest(HttpServletRequest request, String name, Entry entry) {
+ Object attrVal = request.getAttribute(name);
+ if (attrVal != null) {
+ RecordLog.warn(String.format("Already exist attribute name '%s' in request, please set `requestAttributeName`", name));
+ } else {
+ request.setAttribute(name, entry);
+ }
+ }
+
+ protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) {
+ Object entryObject = request.getAttribute(attrKey);
+ return entryObject == null ? null : (Entry) entryObject;
+ }
+
+ protected void removeEntryInRequest(HttpServletRequest request) {
+ request.removeAttribute(baseWebMvcConfig.getRequestAttributeName());
+ }
+
+ protected void traceExceptionAndExit(Entry entry, Exception ex) {
+ if (entry != null) {
+ if (ex != null) {
+ Tracer.traceEntry(ex, entry);
+ }
+ entry.exit();
+ }
+ }
+
+ protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
+ if (baseWebMvcConfig.getBlockExceptionHandler() != null) {
+ baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e);
+ } else {
+ //Throw BlockException, handle it in spring mvc
+ throw e;
+ }
+ }
+
+ protected String parseOrigin(HttpServletRequest request) {
+ String origin = EMPTY_ORIGIN;
+ if (baseWebMvcConfig.getOriginParser() != null) {
+ origin = baseWebMvcConfig.getOriginParser().parseOrigin(request);
+ if (StringUtil.isEmpty(origin)) {
+ return EMPTY_ORIGIN;
+ }
+ }
+ return origin;
+ }
+
+ protected void setBaseWebMvcConfig(BaseWebMvcConfig config) {
+ this.baseWebMvcConfig = config;
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelInterceptor.java
new file mode 100644
index 00000000..a9c5c34e
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelInterceptor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.alibaba.csp.sentinel.util.StringUtil;
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Spring mvc interceptor that integrates with sentinel.
+ *
+ * @author kaizi2009
+ */
+public class SentinelInterceptor extends AbstractSentinelInterceptor {
+ private SentinelWebMvcConfig config;
+
+ public SentinelInterceptor(SentinelWebMvcConfig config) {
+ super();
+ setConfig(config);
+ super.setBaseWebMvcConfig(config);
+ }
+
+ public SentinelInterceptor() {
+ this(new SentinelWebMvcConfig());
+ }
+
+ public SentinelInterceptor setConfig(SentinelWebMvcConfig config) {
+ if (config == null) {
+ this.config = new SentinelWebMvcConfig();
+ RecordLog.info("Config is null, use default config");
+ } else {
+ this.config = config;
+ }
+ RecordLog.info(String.format("SentinelInterceptor config: requestAttributeName=%s, originParser=%s, httpMethodSpecify=%s, blockExceptionHandler=%s, urlCleaner=%s", config.getRequestAttributeName(), config.getOriginParser(), config.isHttpMethodSpecify(), config.getBlockExceptionHandler(), config.getUrlCleaner()));
+ return this;
+ }
+
+ /**
+ * Get target in HttpServletRequest
+ *
+ * @param request
+ * @return
+ */
+ @Override
+ protected String getResourceName(HttpServletRequest request) {
+ Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
+ if (resourceNameObject == null || !(resourceNameObject instanceof String)) {
+ return null;
+ }
+ String resourceName = (String) resourceNameObject;
+ UrlCleaner urlCleaner = config.getUrlCleaner();
+ if (urlCleaner != null) {
+ resourceName = urlCleaner.clean(resourceName);
+ }
+ // Add method specification if necessary
+ if (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) {
+ resourceName = request.getMethod().toUpperCase() + COLON + resourceName;
+ }
+ return resourceName;
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelTotalInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelTotalInterceptor.java
new file mode 100644
index 00000000..5cda2aff
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelTotalInterceptor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Spring mvc interceptor for all requests.
+ *
+ * @author kaizi2009
+ */
+public class SentinelTotalInterceptor extends AbstractSentinelInterceptor {
+ private SentinelWebMvcTotalConfig config;
+
+ public SentinelTotalInterceptor(SentinelWebMvcTotalConfig config) {
+ super();
+ setConfig(config);
+ setBaseWebMvcConfig(config);
+ }
+
+ public SentinelTotalInterceptor() {
+ this(new SentinelWebMvcTotalConfig());
+ }
+
+ public SentinelTotalInterceptor setConfig(SentinelWebMvcTotalConfig config) {
+ if (config == null) {
+ this.config = new SentinelWebMvcTotalConfig();
+ RecordLog.info("Config is null, use default config");
+ } else {
+ this.config = config;
+ }
+ RecordLog.info(String.format("SentinelInterceptor config: requestAttributeName=%s, originParser=%s, blockExceptionHandler=%s, totalResourceName=%s", config.getRequestAttributeName(), config.getOriginParser(), config.getBlockExceptionHandler(), config.getTotalResourceName()));
+ return this;
+ }
+
+ @Override
+ protected String getResourceName(HttpServletRequest request) {
+ return config.getTotalResourceName();
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java
new file mode 100644
index 00000000..fcf61809
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.callback;
+
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handle BlockException
+ *
+ * @author kaizi2009
+ */
+public interface BlockExceptionHandler {
+
+ /**
+ * Handle BlockException
+ *
+ * @param request
+ * @param response
+ * @param e Depending on your situation, you can choose to process or throw BlockException
+ * @throws Exception
+ */
+ void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
+
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java
new file mode 100644
index 00000000..b1df0067
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.callback;
+
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+
+/**
+ * Default `BlockException` handler
+ *
+ * @author kaizi2009
+ */
+public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
+ StringBuffer url = request.getRequestURL();
+
+ if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) {
+ url.append("?").append(request.getQueryString());
+ }
+
+ PrintWriter out = response.getWriter();
+ out.print("Blocked by Sentinel (flow limiting)");
+ out.flush();
+ out.close();
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/RequestOriginParser.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/RequestOriginParser.java
new file mode 100644
index 00000000..647e0183
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/RequestOriginParser.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.adapter.spring.webmvc.callback;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request.
+ *
+ * @author kaizi2009
+ */
+public interface RequestOriginParser {
+
+ /**
+ * Parse the origin from given HTTP request.
+ *
+ * @param request HTTP request
+ * @return parsed origin
+ */
+ String parseOrigin(HttpServletRequest request);
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java
new file mode 100644
index 00000000..9b7f7f89
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.callback;
+
+/**
+ * Clean sentinel target
+ *
+ * @author kaizi2009
+ */
+public interface UrlCleaner {
+
+ /**
+ * Clean sentinel target
+ *
+ * @param originUrl
+ * @return
+ */
+ String clean(String originUrl);
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java
new file mode 100644
index 00000000..ef1da39d
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
+
+/**
+ * Common config
+ *
+ * @author kaizi2009
+ */
+public abstract class BaseWebMvcConfig {
+ protected String requestAttributeName;
+ protected BlockExceptionHandler blockExceptionHandler;
+ protected RequestOriginParser originParser;
+
+ public String getRequestAttributeName() {
+ return requestAttributeName;
+ }
+
+ public void setRequestAttributeName(String requestAttributeName) {
+ this.requestAttributeName = requestAttributeName;
+ }
+
+ public BlockExceptionHandler getBlockExceptionHandler() {
+ return blockExceptionHandler;
+ }
+
+ public void setBlockExceptionHandler(BlockExceptionHandler blockExceptionHandler) {
+ this.blockExceptionHandler = blockExceptionHandler;
+ }
+
+ public RequestOriginParser getOriginParser() {
+ return originParser;
+ }
+
+ public void setOriginParser(RequestOriginParser originParser) {
+ this.originParser = originParser;
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java
new file mode 100755
index 00000000..35f75533
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
+
+/**
+ * @author kaizi2009
+ */
+public class SentinelWebMvcConfig extends BaseWebMvcConfig {
+
+ public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "sentinel_spring_mvc_entry_container";
+ private UrlCleaner urlCleaner;
+ protected boolean httpMethodSpecify;
+
+ public SentinelWebMvcConfig() {
+ super();
+ setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME);
+ }
+
+ public UrlCleaner getUrlCleaner() {
+ return urlCleaner;
+ }
+
+ public void setUrlCleaner(UrlCleaner urlCleaner) {
+ this.urlCleaner = urlCleaner;
+ }
+
+ public boolean isHttpMethodSpecify() {
+ return httpMethodSpecify;
+ }
+
+ public void setHttpMethodSpecify(boolean httpMethodSpecify) {
+ this.httpMethodSpecify = httpMethodSpecify;
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java
new file mode 100644
index 00000000..bf90b2f8
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
+
+
+/**
+ * @author kaizi2009
+ */
+public class SentinelWebMvcTotalConfig extends BaseWebMvcConfig {
+ public static final String DEFAULT_TOTAL_RESOURCE_NAME = "spring-mvc-total-url-request";
+ public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "sentinel_spring_mvc_total_entry_container";
+
+ private String totalResourceName = DEFAULT_TOTAL_RESOURCE_NAME;
+
+ public SentinelWebMvcTotalConfig() {
+ super();
+ setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME);
+ }
+
+ public String getTotalResourceName() {
+ return totalResourceName;
+ }
+
+ /**
+ * Config total resource name
+ *
+ * @param totalResourceName
+ * @return
+ */
+ public void setTotalResourceName(String totalResourceName) {
+ this.totalResourceName = totalResourceName;
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.java
new file mode 100644
index 00000000..66121fcd
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * @author kaizi2009
+ */
+public class ResultWrapper {
+
+ private Integer code;
+ private String message;
+
+ public ResultWrapper(Integer code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public void setCode(Integer code) {
+ this.code = code;
+ }
+
+ public static ResultWrapper error() {
+
+ return new ResultWrapper(-1, "System error");
+ }
+
+ public static ResultWrapper blocked() {
+ return new ResultWrapper(-2, "Blocked by Sentinel");
+ }
+
+ public String toJsonString() {
+ return JSONObject.toJSONString(this);
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java
new file mode 100644
index 00000000..da9b6bba
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+/**
+ * @author kaizi2009
+ */
+@SpringBootApplication
+public class TestApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(TestApplication.class);
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestInterceptor.java
new file mode 100644
index 00000000..c7304da8
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestInterceptor.java
@@ -0,0 +1,155 @@
+package com.alibaba.csp.sentinel.adapter.spring.webmvc;
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.alibaba.csp.sentinel.node.ClusterNode;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.util.Collections;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+/**
+ * @author kaizi2009
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = TestApplication.class)
+@AutoConfigureMockMvc
+public class TestInterceptor {
+
+ private static final String HELLO_STR = "Hello!";
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ public void testBase() throws Exception {
+ String url = "/hello";
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(HELLO_STR));
+
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(1, cn.passQps(), 0.01);
+ }
+
+ @Test
+ public void testOriginParser() throws Exception {
+ String springMvcPathVariableUrl = "/foo/{id}";
+ String limitOrigin = "userA";
+ final String headerName = "S-User";
+ configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin);
+
+ this.mvc.perform(get("/foo/1").accept(MediaType.TEXT_PLAIN).header(headerName, "userB"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("foo 1"));
+
+ // This will be blocked and reponse json.
+ this.mvc.perform(
+ get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin))
+ .andExpect(status().isOk())
+ .andExpect(content().json(ResultWrapper.blocked().toJsonString()));
+ this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().string(ResultWrapper.blocked().toJsonString()));
+
+ FlowRuleManager.loadRules(null);
+ }
+
+ @Test
+ public void testTotalInterceptor() throws Exception {
+ String url = "/hello";
+ String totalTarget = "my_spring_mvc_total_url_request";
+ for (int i = 0; i < 3; i++) {
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(HELLO_STR));
+ }
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(totalTarget);
+ assertNotNull(cn);
+ assertEquals(3, cn.passQps(), 0.01);
+ }
+
+ @Test
+ public void testRuntimeException() throws Exception {
+ String url = "/runtimeException";
+ configureExceptionRulesFor(url, 3, null);
+ int repeat = 3;
+ for (int i = 0; i < repeat; i++) {
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(ResultWrapper.error().toJsonString()));
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(i + 1, cn.passQps(), 0.01);
+ }
+
+ // This will be blocked and reponse json.
+ this.mvc.perform(get(url))
+ .andExpect(status().isOk())
+ .andExpect(content().string(ResultWrapper.blocked().toJsonString()));
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
+ assertNotNull(cn);
+ assertEquals(repeat, cn.passQps(), 0.01);
+ assertEquals(1, cn.blockRequest(), 1);
+ }
+
+ private void configureRulesFor(String resource, int count, String limitApp) {
+ FlowRule rule = new FlowRule()
+ .setCount(count)
+ .setGrade(RuleConstant.FLOW_GRADE_QPS);
+ rule.setResource(resource);
+ if (StringUtil.isNotBlank(limitApp)) {
+ rule.setLimitApp(limitApp);
+ }
+ FlowRuleManager.loadRules(Collections.singletonList(rule));
+ }
+
+ private void configureExceptionRulesFor(String resource, int count, String limitApp) {
+ FlowRule rule = new FlowRule()
+ .setCount(count)
+ .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
+ rule.setResource(resource);
+ if (StringUtil.isNotBlank(limitApp)) {
+ rule.setLimitApp(limitApp);
+ }
+ FlowRuleManager.loadRules(Collections.singletonList(rule));
+ }
+
+ @After
+ public void cleanUp() {
+ FlowRuleManager.loadRules(null);
+ ClusterBuilderSlot.resetClusterNodes();
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java
new file mode 100644
index 00000000..d6135ead
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelTotalInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Config sentinel interceptor
+ *
+ * @author kaizi2009
+ */
+@Configuration
+public class InterceptorConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ //Add sentinel interceptor
+ addSpringMvcInterceptor(registry);
+
+ //If you want to sentinel the total flow, you can add total interceptor
+ addSpringMvcTotalInterceptor(registry);
+ }
+
+ private void addSpringMvcInterceptor(InterceptorRegistry registry) {
+ //Config
+ SentinelWebMvcConfig config = new SentinelWebMvcConfig();
+
+ config.setBlockExceptionHandler(new BlockExceptionHandler() {
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
+ String resourceName = e.getRule().getResource();
+ //Depending on your situation, you can choose to process or throw
+ if ("/hello".equals(resourceName)) {
+ //Do something ......
+ //Write string or json string;
+ response.getWriter().write("/Blocked by sentinel");
+ } else {
+ //Handle in global exception handling
+ throw e;
+ }
+ }
+ });
+
+ //Custom configuration if necessary
+ config.setHttpMethodSpecify(false);
+ config.setOriginParser(new RequestOriginParser() {
+ @Override
+ public String parseOrigin(HttpServletRequest request) {
+ return request.getHeader("S-user");
+ }
+ });
+
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelInterceptor(config)).addPathPatterns("/**");
+ }
+
+ private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) {
+ //Configure
+ SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();
+
+ //Custom configuration if necessary
+ config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container");
+ config.setTotalResourceName("my_spring_mvc_total_url_request");
+
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelTotalInterceptor(config)).addPathPatterns("/**");
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java
new file mode 100644
index 00000000..b8e46603
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.ResultWrapper;
+import com.alibaba.csp.sentinel.slots.block.AbstractRule;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * Config 'BlockException' handler, handler it in spring veb 'ExceptionHandler'
+ *
+ * @author kaizi2009
+ */
+@ControllerAdvice
+@Order(0)
+public class SentinelSpringMvcBlockHandlerConfig {
+ private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @ExceptionHandler(BlockException.class)
+ @ResponseBody
+ public ResultWrapper sentinelBlockHandler(BlockException e) {
+ AbstractRule rule = e.getRule();
+ //Log
+ logger.info("Blocked by sentinel, {}", rule.toString());
+ //Return object
+ return ResultWrapper.blocked();
+ }
+
+ @ExceptionHandler(Exception.class)
+ @ResponseBody
+ public ResultWrapper exceptionHandler(Exception e) {
+ logger.error("System error", e.getMessage());
+ return new ResultWrapper(-1, "System error");
+ }
+}
diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java
new file mode 100644
index 00000000..d8a42ab8
--- /dev/null
+++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.adapter.spring.webmvc.controller;
+
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author kaizi2009
+ */
+@RestController
+public class TestController {
+
+ @GetMapping("/hello")
+ public String apiHello() {
+ return "Hello!";
+ }
+
+ @GetMapping("/err")
+ public String apiError() {
+ return "Oops...";
+ }
+
+ @GetMapping("/foo/{id}")
+ public String apiFoo(@PathVariable("id") Long id) {
+ return "foo " + id;
+ }
+
+ @GetMapping("/runtimeException")
+ public String runtimeException() {
+ int i = 1 / 0;
+ return "runtimeException";
+ }
+
+ @GetMapping("/exclude/{id}")
+ public String apiExclude(@PathVariable("id") Long id) {
+ return "Exclude " + id;
+ }
+
+}
diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml
index 24670928..5b0423d5 100755
--- a/sentinel-demo/pom.xml
+++ b/sentinel-demo/pom.xml
@@ -35,6 +35,7 @@
sentinel-demo-spring-cloud-gateway
sentinel-demo-zuul-gateway
sentinel-demo-etcd-datasource
+ sentinel-demo-spring-webmvc
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml b/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml
new file mode 100644
index 00000000..054649d5
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/pom.xml
@@ -0,0 +1,44 @@
+
+
+
+ sentinel-demo
+ com.alibaba.csp
+ 1.7.0-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-demo-spring-webmvc
+
+
+ 2.1.3.RELEASE
+
+
+
+
+ com.alibaba.csp
+ sentinel-core
+
+
+ com.alibaba.csp
+ sentinel-transport-simple-http
+
+
+ com.alibaba.csp
+ sentinel-spring-webmvc-adapter
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.boot.version}
+
+
+
+
\ No newline at end of file
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java
new file mode 100644
index 00000000..4004e562
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.demo.spring.webmvc;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author kaizi2009
+ *
+ * -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc
+ *
+ */
+@SpringBootApplication
+public class WebMvcDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(WebMvcDemoApplication.class);
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java
new file mode 100644
index 00000000..308e28fc
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.demo.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelTotalInterceptor;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Config sentinel interceptor
+ *
+ * @author kaizi2009
+ */
+@Configuration
+public class InterceptorConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ //Add sentinel interceptor
+ addSpringMvcInterceptor(registry);
+
+ //If you want to sentinel the total flow, you can add total interceptor
+ addSpringMvcTotalInterceptor(registry);
+ }
+
+ private void addSpringMvcInterceptor(InterceptorRegistry registry) {
+ //Config
+ SentinelWebMvcConfig config = new SentinelWebMvcConfig();
+
+ config.setBlockExceptionHandler((request, response, e) -> {
+ //Depending on your situation, you can choose to process or throw
+ boolean needThrow = true;
+ if (needThrow) {
+ throw e;
+ } else {
+ //Write string or json string;
+ response.getWriter().write("Blocked by sentinel");
+ }
+ });
+
+ //Custom configuration if necessary
+ config.setHttpMethodSpecify(true);
+ config.setOriginParser(request -> request.getHeader("S-user"));
+
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelInterceptor(config)).addPathPatterns("/**");
+ }
+
+ private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) {
+ //Config
+ SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();
+
+ //Custom configuration if necessary
+ config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container");
+ config.setTotalResourceName("my-spring-mvc-total-url-request");
+
+ //Add sentinel interceptor
+ registry.addInterceptor(new SentinelTotalInterceptor(config)).addPathPatterns("/**");
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java
new file mode 100644
index 00000000..8dc3d258
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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
+ *
+ * https://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 com.alibaba.csp.sentinel.demo.spring.webmvc.config;
+
+import com.alibaba.csp.sentinel.demo.spring.webmvc.vo.ResultWrapper;
+import com.alibaba.csp.sentinel.slots.block.AbstractRule;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * Config blocked handler
+ * @author kaizi2009
+ */
+@ControllerAdvice
+@Order(0)
+public class SentinelSpringMvcBlockHandlerConfig {
+ private Logger logger = LoggerFactory.getLogger(this.getClass());
+ @ExceptionHandler(BlockException.class)
+ @ResponseBody
+ public ResultWrapper sentinelBlockHandler(BlockException e) {
+ AbstractRule rule = e.getRule();
+ //Log
+ logger.info("Blocked by sentinel, {}", rule.toString());
+ //Return object
+ return ResultWrapper.blocked();
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/controller/WebMvcTestController.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/controller/WebMvcTestController.java
new file mode 100644
index 00000000..c70342c8
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/controller/WebMvcTestController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.demo.spring.webmvc.controller;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Test controller
+ * @author kaizi2009
+ */
+@RestController
+public class WebMvcTestController {
+
+ @GetMapping("/hello")
+ public String apiHello() {
+ doBusiness();
+ return "Hello!";
+ }
+
+ @GetMapping("/err")
+ public String apiError() {
+ doBusiness();
+ return "Oops...";
+ }
+
+ @GetMapping("/foo/{id}")
+ public String apiFoo(@PathVariable("id") Long id) {
+ doBusiness();
+ return "Hello " + id;
+ }
+
+ @GetMapping("/exclude/{id}")
+ public String apiExclude(@PathVariable("id") Long id) {
+ doBusiness();
+ return "Exclude " + id;
+ }
+
+ private void doBusiness() {
+ Random random = new Random(1);
+ try {
+ TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/vo/ResultWrapper.java b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/vo/ResultWrapper.java
new file mode 100644
index 00000000..c4c45abc
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/vo/ResultWrapper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.demo.spring.webmvc.vo;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * @author kaizi2009
+ */
+public class ResultWrapper {
+
+ private Integer code;
+ private String message;
+
+ public ResultWrapper(Integer code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public void setCode(Integer code) {
+ this.code = code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public static ResultWrapper blocked() {
+ return new ResultWrapper(-1, "Blocked by Sentinel");
+ }
+
+ public String toJsonString() {
+ return JSONObject.toJSONString(this);
+ }
+}
diff --git a/sentinel-demo/sentinel-demo-spring-webmvc/src/main/resources/application.properties b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/resources/application.properties
new file mode 100644
index 00000000..397dea55
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-spring-webmvc/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port=10000
\ No newline at end of file