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.jarsentinel-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)进行保护

猜你喜欢

转载自blog.csdn.net/mapleleafforest/article/details/111033992