天天看點

spring cloud alibaba整合sentinel之webmvc攔截器方式源碼簡析

spring cloud alibaba

提供的

Sentinel

支援方式如下:

  1. 使用webmvc方式,即使用

    SentinelWebInterceptor

    攔截器
  2. 對Feign的支援,需開啟

    feign.sentinel.enabled

    參數
  3. 對RestTemplate的支援,使用

    @SentinelRestTemplate

    注解

本文主要說明使用webmvc方式的源碼簡析,看本文前需要對

Sentinel

元件有一定了解。項目依賴的spring cloud的版本資訊如下:

Spring Cloud Alibaba Version Sentinel Version Nacos Version
2.2.3.RELEASE 1.8.0 1.3.3

涉及到的關鍵jar:

spring-cloud-starter-alibaba-sentinel-2.2.3.RELEASE.jar

sentinel-spring-webmvc-adapter-1.8.0.jar

1.程式加載入口SentinelWebAutoConfiguration

按照套路,分析的入口肯定starter的某一個AutoConfiguration裡。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@ConditionalOnClass(SentinelWebInterceptor.class)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelWebAutoConfiguration implements WebMvcConfigurer {

	private static final Logger log = LoggerFactory
			.getLogger(SentinelWebAutoConfiguration.class);
	@Autowired
	private SentinelProperties properties;
	@Autowired
	private Optional<UrlCleaner> urlCleanerOptional;
	@Autowired
	private Optional<BlockExceptionHandler> blockExceptionHandlerOptional;
	@Autowired
	private Optional<RequestOriginParser> requestOriginParserOptional;
	@Autowired
	private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;
    //将攔截器添加到spring mvc的配置中
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		if (!sentinelWebInterceptorOptional.isPresent()) {
			return;
		}
		SentinelProperties.Filter filterConfig = properties.getFilter();
		registry.addInterceptor(sentinelWebInterceptorOptional.get())
				.order(filterConfig.getOrder())
				.addPathPatterns(filterConfig.getUrlPatterns());
		log.info(
				"[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.",
				filterConfig.getUrlPatterns());
	}
    //建構攔截器對象,SentinelWebMvcConfig對象的建立在後面的代碼裡分析
	@Bean
	@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
			matchIfMissing = true)
	public SentinelWebInterceptor sentinelWebInterceptor(
			SentinelWebMvcConfig sentinelWebMvcConfig) {
		return new SentinelWebInterceptor(sentinelWebMvcConfig);
	}
    //建立SentinelWebMvcConfig對象,為建立SentinelWebInterceptor提供參數配置
	@Bean
	@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
			matchIfMissing = true)
	public SentinelWebMvcConfig sentinelWebMvcConfig() {
		SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
		sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
		sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());

		if (blockExceptionHandlerOptional.isPresent()) {
			blockExceptionHandlerOptional
					.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
		}
		else {
			if (StringUtils.hasText(properties.getBlockPage())) {
				sentinelWebMvcConfig.setBlockExceptionHandler(((request, response,
						e) -> response.sendRedirect(properties.getBlockPage())));
			}
			else {
				sentinelWebMvcConfig
						.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
			}
		}

		urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
		requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
		return sentinelWebMvcConfig;
	}

}
           

同過

sentinelWebInterceptor()

sentinelWebMvcConfig()

方法,我們可以得到以下有用的資訊:

  1. 通過

    sentinelWebInterceptor()

    方法上的注解參數

    spring.cloud.sentinel.filter.enabled

    可以知道引入

    sentinel-spring-webmvc-adapter-1.8.0.jar

    後預設就開啟了使用

    SentinelWebInterceptor

    來處理
  2. UrlCleaner

    為自動注入,如果想自定義

    UrlCleaner

    ,隻需自己寫一個

    UrlCleaner

    接口實作類,并添加到Spring IOC 容器中即可
  3. BlockExceptionHandler

    為自動注入,如果不想使用預設的

    DefaultBlockExceptionHandler

    ,隻需自己寫一個

    BlockExceptionHandler

    接口實作類,并添加到Spring IOC 容器中即可
  4. 可以采用和3類型的方式自定義

    RequestOriginParser

2.重點配置類SentinelProperties的配置

同過

addInterceptors()

方法可以看到,隻針對filter提供如下配置:

1、通過spring.cloud.sentinel.filter的配置
spring:
  cloud:
    sentinel:
      filter:
        #配置需要攔截的URL(預設"/**")
        url-patterns:
          - '/userconfig/**'
          - '/nacosconfig/**'
        #攔截器的order(預設最高優先級-2147483648)
        order:-123   

2、SentinelProperties還提供很多常用的配置,跟SentinelWebMvcConfig相關的還有:
 spring:
  cloud:
    sentinel:
       #如果針對同一URL資源需要區分是Post還是Get請求可以将參數設定為true
       http-method-specify:true
       webContextUnify:true
       
           

3.攔截器SentinelWebInterceptor源碼簡析

public class SentinelWebInterceptor extends AbstractSentinelInterceptor {
    private final SentinelWebMvcConfig config;
    public SentinelWebInterceptor() {
        this(new SentinelWebMvcConfig());
    }
    public SentinelWebInterceptor(SentinelWebMvcConfig config) {
        super(config);
        if (config == null) {
            // Use the default config by default.
            this.config = new SentinelWebMvcConfig();
        } else {
            this.config = config;
        }
    }
    @Override
    protected String getResourceName(HttpServletRequest request) {
        // Resolve the Spring Web URL pattern from the request attribute.
        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() + ":" + resourceName;
        }
        return resourceName;
    }
    @Override
    protected String getContextName(HttpServletRequest request) {
        if (config.isWebContextUnify()) {
            return super.getContextName(request);
        }
        return getResourceName(request);
    }
}
//AbstractSentinelInterceptor類的中的方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    try {
        String resourceName = getResourceName(request);

        if (StringUtil.isEmpty(resourceName)) {
            return true;
        }
        
        if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
            return true;
        }
        
        // Parse the request origin using registered origin parser.
        String origin = parseOrigin(request);
        String contextName = getContextName(request);
        ContextUtil.enter(contextName, origin);
        Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
        request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
        return true;
    } catch (BlockException e) {
        try {
            handleBlockException(request, response, e);
        } finally {
            ContextUtil.exit();
        }
        return false;
    }
}
           

該類業務邏輯很簡單。主要業務如下:

  1. 解析請求資源路徑,支援通過我們自定義的

    UrlCleaner

    來對URL資源進行清洗,一般用于變量在url路徑的restful形式的url,如:/getuser/123
  2. 在攔截器的preHandle方法中對URL使用

    SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN)

    進行保護

繼續閱讀