Spring Webflux —— 源码阅读之 handler 包


摘要: 原创出处 https://www.jianshu.com/p/23ee4b77c160 「一颗懒能」欢迎转载,保留摘要,谢谢!

  • AbstractHandlerMapping
  • 接口

提供包括抽象基类在内的HandlerMapping实现。

先扔一张整体的diagram类图:

img

simoleUrlHandlerMapping.jpg

AbstractHandlerMapping

HandlerMapping实现的抽象基类。

接口

  • java.lang.Object
  • org.springframework.context.support.ApplicationObjectSupport
    • org.springframework.web.reactive.handler.AbstractHandlerMapping

实现了HandlerMapping, Ordered

    public abstract class AbstractHandlerMapping extends
    ApplicationObjectSupport implements HandlerMapping, Ordered {



private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty();


private int order = Integer.MAX_VALUE;  // default: same as non-Ordered

private final PathPatternParser patternParser;

private final UrlBasedCorsConfigurationSource globalCorsConfigSource;

private CorsProcessor corsProcessor = new DefaultCorsProcessor();


public AbstractHandlerMapping() {
      this.patternParser = new PathPatternParser();
      this.globalCorsConfigSource = new UrlBasedCorsConfigurationSource(this.patternParser);
}

查找给定请求的handler,如果找不到特定的请求,则返回一个空的Mono。这个方法被getHandler(org.springframework.web.server.ServerWebExchange)调用。

在CORS 预先请求中,该方法应该返回一个匹配,而不是预先请求的请求,而是基于URL路径的预期实际请求,从“Access-Control-Request-Method”头,以及“Access-Control-Request-Headers”头的HTTP方法,通过 getcorsconfiguration获得CORS配置来允许通过,

如果匹配到一个handler,就返回Mono

protected abstract Mono<?> getHandlerInternal(ServerWebExchange exchange);

检索给定handle的CORS配置。

@Nullable
protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
    if (handler instanceof CorsConfigurationSource) {
        return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange);
    }
    return null;
}

抽象类实现的主要的具体方法,来获得具体的Handle,实现了HandlerMapping中的getHandler,Mono<Object> getHandler(ServerWebExchange exchange);

@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
    return getHandlerInternal(exchange).map(handler -> {
        if (CorsUtils.isCorsRequest(exchange.getRequest())) {
            CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
            CorsConfiguration configB = getCorsConfiguration(handler, exchange);
            CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
            if (!getCorsProcessor().process(config, exchange) ||
                    CorsUtils.isPreFlightRequest(exchange.getRequest())) {
                return REQUEST_HANDLED_HANDLER;
            }
        }
        return handler;
    });
}

AbstractUrlHandlerMapping

基于URL映射的HandlerMapping实现的抽象基类。

接口

  • java.lang.Object
  • org.springframework.context.support.ApplicationObjectSupport
    • org.springframework.web.reactive.handler.AbstractHandlerMapping
    • org.springframework.web.reactive.handler.AbstractUrlHandlerMapping

支持直接匹配,例如注册的“/ test”匹配“/ test”,以及各种ant样式匹配,例如, “/ test *”匹配“/ test”和“/ team”,“/ test / *”匹配“/ test”下的所有路径, 。有关详细信息,请参阅PathPattern javadoc。

将搜索所有路径模式以查找当前请求路径的最具体匹配。最具体的模式定义为使用最少捕获变量和通配符的最长路径模式。

private final Map<PathPattern, Object> handlerMap = new LinkedHashMap<>();

返回注册路径模式和handle的只读视图,这些注册路径模式和handle可能是一个实际的handle实例或延迟初始化handle的bean名称。

public final Map<PathPattern, Object> getHandlerMap() {
    return Collections.unmodifiableMap(this.handlerMap);
}

我们可以再看到下面这两个方法实现了handle的注册,会把所有的路径映射,和handle实例放在handlerMap中

protected void registerHandler(String[] urlPaths, String beanName)
        throws BeansException, IllegalStateException {

    Assert.notNull(urlPaths, "URL path array must not be null");
    for (String urlPath : urlPaths) {
        registerHandler(urlPath, beanName);
    }
}


protected void registerHandler(String urlPath, Object handler)
        throws BeansException, IllegalStateException {

    Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    // Parse path pattern
    urlPath = prependLeadingSlash(urlPath);
    PathPattern pattern = getPathPatternParser().parse(urlPath);
    if (this.handlerMap.containsKey(pattern)) {
        Object existingHandler = this.handlerMap.get(pattern);
        if (existingHandler != null) {
            if (existingHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to [" + urlPath + "]: " +
                        "there is already " + getHandlerDescription(existingHandler) + " mapped.");
            }
        }
    }

    // Eagerly resolve handler if referencing singleton via name.
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (obtainApplicationContext().isSingleton(handlerName)) {
            resolvedHandler = obtainApplicationContext().getBean(handlerName);
        }
    }

    // Register resolved handler
    this.handlerMap.put(pattern, resolvedHandler);
    if (logger.isInfoEnabled()) {
        logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
    }
}

预处理映射的路径,如果不以/开头就加上/

private static String prependLeadingSlash(String pattern) {
    if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
        return "/" + pattern;
    }
    else {
        return pattern;
    }
}

在这一步的时候开始获取内部的handle,查找给定请求的handle,如果找不到特定的请求,则返回一个空的Mono。

    @Override
public Mono<Object> getHandlerInternal(ServerWebExchange exchange) {
    PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication();
    Object handler;
    try {
        handler = lookupHandler(lookupPath, exchange);
    }
    catch (Exception ex) {
        return Mono.error(ex);
    }

    if (handler != null && logger.isDebugEnabled()) {
        logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
        logger.trace("No handler mapping found for [" + lookupPath + "]");
    }

    return Mono.justOrEmpty(handler);
}

获取到handle的类,先获取请求的url地址,调用lookupHandler(lookupPath, exchange)去找这个handle。

@Nullable
protected Object lookupHandler(PathContainer lookupPath, ServerWebExchange exchange)
        throws Exception {

    return this.handlerMap.entrySet().stream()
            .filter(entry -> entry.getKey().matches(lookupPath))
            .sorted((entry1, entry2) ->
                    PathPattern.SPECIFICITY_COMPARATOR.compare(entry1.getKey(), entry2.getKey()))
            .findFirst()
            .map(entry -> {
                PathPattern pattern = entry.getKey();
                if (logger.isDebugEnabled()) {
                    logger.debug("Matching pattern for request [" + lookupPath + "] is " + pattern);
                }
                PathContainer pathWithinMapping = pattern.extractPathWithinPattern(lookupPath);
                return handleMatch(entry.getValue(), pattern, pathWithinMapping, exchange);
            })
            .orElse(null);
}

在这里又调用了handleMatch(entry.getValue(), pattern, pathWithinMapping, exchange)来匹配handle,验证过后,然后设置到ServerWebExchange中最后返回。

private Object handleMatch(Object handler, PathPattern bestMatch, PathContainer pathWithinMapping,
        ServerWebExchange exchange) {

    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    validateHandler(handler, exchange);

    exchange.getAttributes().put(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
    exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatch);
    exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);

    return handler;
}

SimpleUrlHandlerMapping

HandlerMapping的实现,来把url请求映射到对应的request handler的bean

支持映射到bean实例和映射到bean名称;非单例的handler需要映射到bean名称。

“urlMap”属性适合用bean实例填充处理程序映射。可以通过java.util.Properties类接受的形式,通过“映射”属性设置映射到bean名称,如下所示:

/welcome.html=ticketController /show.html=ticketController

语法是PATH = HANDLER_BEAN_NAME。如果路径不是以斜杠开始的,则给它自动补充一个斜杠。

支持直接匹配,例如注册的“/ test”匹配“/ test”,以及各种ant样式匹配,例如, “/ test *”匹配“/ test”和“/ team”,“/ test / *”匹配“/ test”下的所有路径, 。有关详细信息,请参阅PathPattern javadoc。

接口

  • java.lang.Object
  • org.springframework.context.support.ApplicationObjectSupport
    • org.springframework.web.reactive.handler.SimpleUrlHandlerMapping
    • org.springframework.web.reactive.handler.AbstractHandlerMapping
    • org.springframework.web.reactive.handler.AbstractUrlHandlerMapping
private final Map<String, Object> urlMap = new LinkedHashMap<>();


程序启动遍历的时候把加载到的所有映射路径,和handle设置到urlMap
public void setUrlMap(Map<String, ?> urlMap) {
    this.urlMap.putAll(urlMap);
}

获得所有的urlMap,允许urlMap访问URL路径映射,可以添加或覆盖特定条目。
public Map<String, ?> getUrlMap() {
    return this.urlMap;
}


初始化程序上下文,除了父类的初始化,还调用了registerHandler
@Override
public void initApplicationContext() throws BeansException {
    super.initApplicationContext();
    registerHandlers(this.urlMap);
}

开始注册handler,注册urlMap中为相应路径指定的所有的handler。 如果handler不能注册,抛出 BeansException 如果有注册的handler有冲突,比如两个相同的,抛出java.lang.IllegalStateException

protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
    if (urlMap.isEmpty()) {
        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
    }
    else {
        for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
            String url = entry.getKey();
            Object handler = entry.getValue();
            // Prepend with slash if not already present.
            if (!url.startsWith("/")) {
                url = "/" + url;
            }
            // Remove whitespace from handler bean name.
            if (handler instanceof String) {
                handler = ((String) handler).trim();
            }
            registerHandler(url, handler);
        }
    }
}

这里调用的registerHandler(url, handler)就是刚刚抽象类AbstractUrlHandlerMapping中的registerHandler方法

原文发布于微信公众号 - 芋道源码(YunaiV)

原文发表时间:2018-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOS开发

iOS开发之 Method Swizzling 深入浅出

如果产品经理突然说:"在所有页面添加统计功能,也就是用户进入这个页面就统计一次"。我们会想到下面的一些方法:

4767
来自专栏大内老A

如何解决EnterLib异常处理框架最大的局限——基于异常"类型"的异常处理策略

个人觉得EnterLib的EHAB(Exception Handling Application Block)是一个不错的异常处理框架,借助于EHAB,我们可以...

2115
来自专栏androidBlog

java 解决文件名重复问题的两种方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

3131
来自专栏编程坑太多

java:工作中常用到的工具类

1553
来自专栏程序员的SOD蜜

.net访问PostgreSQL数据库发生“找不到函数名”的问题追踪

    PostgreSQL是一个使用广泛的免费开源的数据库,与MySQL比较,它更适合复杂的企业计算任务,而MySQL在互联网领域应用更为广泛,究其原因,可能...

3517
来自专栏Spark生态圈

[Spark SQL] 源码解析之Analyzer

Analyzer模块将Unresolved LogicalPlan结合元数据catalog进行绑定,最终转化为Resolved LogicalPlan。跟着代码...

1252
来自专栏JMCui

Netty 系列六(编解码器).

    网络传输的单位是字节,如何将应用程序的数据转换为字节,以及将字节转换为应用程序的数据,就要说到到我们该篇介绍的编码器和解码器。

1001
来自专栏林德熙的博客

WPF 开发

如果使用NamedPipeServerStream、Mutex做单实例,需要传入字符串,这时如果传入一个固定的字符串,会在多用户的时候无法使用。

2031
来自专栏林德熙的博客

Windows 10 16251 添加的 api 工具

本文主要讲微软最新的sdk添加的功能,暂时还不能下载,到 7月29 ,现在可以下载是 16232 ,支持Neon效果。 实际上设置软件最低版本为 16232 就...

1471
来自专栏Android 研究

APK安装流程详解5——Installer、InstallerConnection和Installd守护进程

因为Installer继承自SystemService,所以我们看下Installer的onStart方法 代码在Installer.java 396行

1311

扫码关注云+社区

领取腾讯云代金券