乐趣区

关于springboot:你了解SpringBoot启动时API相关信息是用什么数据结构存储的吗上篇

纸上得来终觉浅,绝知此事要躬行

留神: 本文 SpringBoot 版本为 2.5.2; JDK 版本 为 jdk 11.

后续文章👉 从浏览器发送申请给 SpringBoot 后端时,是如何精确找到哪个接口的?(下篇)

前言:

在写文章的时候,我都会习惯性的记录下,是什么因素促使我去写的这篇文章。并竟对于感兴趣的货色,写起来也上心,也更得心应手,文章品质相应也更高。当然更多的是想和更多人分享本人的认识,与更多的人一起交换。“三人行,必有我师焉”,欢送大家留言评论交换。

写这篇文章的起因是在于昨天一个学 Go 语言的后端小伙伴,问了我一个问题。

问题大抵如下:

为什么浏览器向后端发动申请时,就晓得要找的是哪一个接口?采纳了什么样的匹配规定呢?

SpringBoot 后端是如何存储 API 接口信息的?又是拿什么数据结构存储的呢?

@ResponseBody
@GetMapping("/test")
public String test(){return "test";}

说实话,听他问完,我感觉我又不够卷了,几乎灵魂拷问,我一个答不进去。咱们一起去看看吧。

我对于 SpringBoot 的框架源码浏览教训可能就一篇👉SpringBoot 主动拆卸原理算是吧,所以在肯定水平上我集体对于 SpringBoot 框架了解的还是十分通俗的。

如果文章中有不足之处,请你肯定要及时批正!在此郑重感激。

一、注解派生概念

算是一点点前提概念吧

在 java 体系中, 类是能够被继承, 接口能够被实现。然而注解没有这些概念, 而是有一个派生的概念。举例, 注解 A。被标记了在注解 B 头上,那么咱们能够说注解 B 就是注解 A 的派生。

如:

就像 注解 @GetMapping 上就还有一个 @RequestMapping(method = RequestMethod.GET),所以咱们实质上也是应用了 @RequestMapping注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {}

还有 @Controller 和 @RestController 也是如此。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {}

废话不多说,间接肝啦。


二、启动流程

更后面的不去做探索了,咱们间接到这个入口处。

做了一个大抵的 剖析流程图 给大家做参考,也是我集体探索的路线。

2.1、AbstractHandlerMethodMapping

/** HandlerMapping 实现的形象基类,定义了申请和 HandlerMethod 之间的映射。对于每个注册的处理程序办法,一个惟一的映射由定义映射类型 <T> 细节的子类保护 
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    
    // ...
    
    /** 在初始化时检测处理程序办法。能够说是入口处啦 */
    @Override
    public void afterPropertiesSet() {initHandlerMethods();
    }
    /** 扫描 ApplicationContext 中的 bean,检测和注册处理程序办法。*/
    protected void initHandlerMethods() {//getCandidateBeanNames() : 确定应用程序上下文中候选 bean 的名称。for (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                // 确定指定候选 bean 的类型,如果标识为处理程序类型,则调用 detectHandlerMethods 
                // 这里的处理程序 就为咱们在 controller 中书写的那些接口办法
                processCandidateBean(beanName);
            }
        }
        // 这里的逻辑不做探讨啦
        handlerMethodsInitialized(getHandlerMethods());
    }
    
    // ...
}

只有当扫描到 是由 @RestController 或 @RequestMapping 注解润饰时,进入 processCandidateBean 办法,这个时候才是咱们要找的货色。其余的 bean 咱们不是咱们探讨的点,不做探讨。

咱们来接着看看 processCandidateBean的解决逻辑,它做了一些什么事件。

/** 确定指定候选 bean 的类型,如果标识为处理程序类型,则调用 detectHandlerMethods。*/
protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        // 确定注入的 bean 类型
        beanType = obtainApplicationContext().getType(beanName);
    }
    catch (Throwable ex) {
        // 无奈解析的 bean
        if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean'" + beanName + "'", ex);
        }
    }
    //isHandler 办法判断是否是 web 资源类。if (beanType != null && isHandler(beanType)) {
        // 算是这条线路上重点啦
        detectHandlerMethods(beanName);
    }
}

isHandler 办法判断是否是 web 资源类。当一个类被标记了 @Controller 或者 @RequestMapping。留神 @RestController 是 @Controller 的派生类。所以这里只用判断 @Controller 或者 @RequestMapping 就行了。

另外 isHandler 定义在 AbstractHandlerMethodMapping< T >,实现在 RequestMappingHandlerMapping

/**
给定类型是否是具备处理程序办法的处理程序。处理程序就是咱们写的 Controller 类中的接口办法
冀望处理程序具备类型级别的 Controller 正文或类型级别的 RequestMapping 正文。*/
@Override
protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

持续往下:

2.2、detectHandlerMethods() 办法

这个办法 detectHandlerMethods(beanName); 它是做什么的呢?

它的办法正文为:在指定的处理程序 bean 中查找处理程序办法。

其实 detectHandlerMethods 办法就是真正开始解析 Method 的逻辑。通过解析 Method 上的 @RequestMapping 或者其余派生的注解。生成申请信息。

/** 在指定的处理程序 bean 中查找处理程序办法。*/
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            // 返回给定类的用户定义类:通常只是给定的类,但如果是 CGLIB 生成的子类,则返回原始类。Class<?> userType = ClassUtils.getUserClass(handlerType);
            
            //selectMethods:
            // 依据相干元数据的查找,抉择给定指标类型的办法。// 调用者通过 MethodIntrospector.MetadataLookup 参数定义感兴趣的办法,容许将关联的元数据收集到后果映射中
            // 简略了解:解析 RequestMapping 信息
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                                // 为处理程序办法提供映射。不能为其提供映射的办法不是处理程序办法
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]:" + method, ex);
                        }
                    });
            if (logger.isTraceEnabled()) {logger.trace(formatMappings(userType, methods));
            }
            else if (mappingsLogger.isDebugEnabled()) {mappingsLogger.debug(formatMappings(userType, methods));
            }
            // 这里将解析的信息, 循环进行注册
            methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

2.3、getMappingForMethod

getMappingForMethod定义在 AbstractHandlerMethodMapping< T >,实现在 RequestMappingHandlerMapping 类下

这里简略说就是 将类档次的 RequestMapping 和办法级别的 RequestMapping 联合 (createRequestMappingInfo)

/** 应用办法和类型级别的 RequestMapping 注解来创立 RequestMappingInfo。*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {info = typeInfo.combine(info);
        }
        // 获取类上 
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
        }
    }
    return info;
}

createRequestMappingInfo:

/**
委托 createRequestMappingInfo(RequestMapping, RequestCondition),依据提供的 annotatedElement 是类还是办法提供适当的自定义 RequestCondition。*/
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    // 次要是 解析 Method 上的 @RequestMapping 信息
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    RequestCondition<?> condition = (element instanceof Class ?
                                     getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

2.4、MethodIntrospector.selectMethods()办法

依据相干元数据的查找,抉择给定指标类型的办法

很多杂七杂八的货色在外面,很难说分明,这里只简略说了一下。

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {final Map<Method, T> methodMap = new LinkedHashMap<>();
    Set<Class<?>> handlerTypes = new LinkedHashSet<>();
    Class<?> specificHandlerType = null;

    if (!Proxy.isProxyClass(targetType)) {specificHandlerType = ClassUtils.getUserClass(targetType);
        handlerTypes.add(specificHandlerType);
    }
    handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));

    for (Class<?> currentHandlerType : handlerTypes) {final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
        // 对给定类和超类(或给定接口和超接口)的所有匹配办法执行给定的回调操作。ReflectionUtils.doWithMethods(currentHandlerType, method -> {Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
            T result = metadataLookup.inspect(specificMethod);
            if (result != null) {
                // BridgeMethodResolver : 给定一个合成 bridge Method 返回被桥接的 Method。// 当扩大其办法具备参数化参数的参数化类型时,编译器可能会创立桥接办法。在运行时调用期间,能够通过反射调用和 / 或应用桥接 Method 
                //findBridgedMethod : 找到提供的 bridge Method 的原始办法。Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {methodMap.put(specificMethod, result);
                }
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
    }
    return methodMap;
}

办法上的 doc 正文:

依据相干元数据的查找,抉择给定指标类型的办法。
调用者通过 MethodIntrospector.MetadataLookup 参数定义感兴趣的办法,容许将关联的元数据收集到后果映射中

一眼两言说不清楚,间接贴一张 debug 的图片给大家看一下。

2.5、registerHandlerMethod 办法

这一段代码其本质就是 这里将解析进去的信息, 循环进行注册

methods.forEach((method, mapping) -> {
    // 抉择指标类型上的可调用办法:如果理论公开在指标类型上,则给定办法自身,或者指标类型的接口之一或指标类型自身上的相应办法。// 简略了解返回个办法吧
    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
    registerHandlerMethod(handler, invocableMethod, mapping);
});
protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);
}

这里的 this.mappingRegistry AbstractHandlerMethodMapping<T> 的一个外部类。

MappingRegistry:doc 正文: 一个注册表,它保护到处理程序办法的所有映射,公开执行查找的办法并提供并发拜访。

对于它的构造,在这里不做探讨啦。感兴趣,能够点进去持续看看。

咱们持续探索咱们 register 办法做了什么

public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();
    try {
        // 创立 HandlerMethod 实例。HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        // 验证办法映射
        validateMethodMapping(handlerMethod, mapping);

        // 这里就是间接获取门路  mapping 的值是 GET[/login]
        // 获取进去后 就是 /login
        Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
        for (String path : directPaths) {
            //this.pathLookup 它的定义如下://     private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
              //   其实 new 就是一个 new LinkedHashMap<>();
            // 这里就是将 path 作为 key,mapping 作为 value 存起来
            this.pathLookup.add(path, mapping);
        }

        String name = null;
        // 这里的意思能够演绎为:if (getNamingStrategy() != null) {
            /// 确定给定 HandlerMethod 和映射的名称。name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

        // 上面几行是解决跨域问题的,不是咱们本章探讨的。大家感兴趣能够去看看。CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {corsConfig.validateAllowCredentials();
            this.corsLookup.put(handlerMethod, corsConfig);
        }
       
        this.registry.put(mapping,
                          new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
    }
    finally {this.readWriteLock.writeLock().unlock();}
}
this.registry.put(mapping,
                          new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));

这里的 this.registry 的定义如下:private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

不同的办法走到这,其实差异不是很大

其实看完这个启动流程,对于咱们刚开始的三个问题,咱们大概率能够找到其中两个答案了。

2.6、小结

你们 SpringBoot 后端框架是如何存储 API 接口的信息的?是拿什么数据结构存储的呢?

第一个答案: 大抵就是和MappingRegistry 这个注册表类相干.

第二个答案: 咱们之前看到存储信息时, 都是 HashMap 相干的类来存储的, 那么咱们能够晓得它底层的数据结构就是 数组 + 链表 + 红黑树

留神: 本文 SpringBoot 版本为 2.5.2;JDK 版本 为 jdk 11.

并未针对多个版本进行比拟, 然而揣测下来, 多半都是如此.

那么咱们的下一步就是去查看 SpringBoot 申请时,是如何找到 对应的 接口的。哪里才又是咱们的一个重点。

三、小结流程

  1. 扫描所有注册的 Bean
  2. 遍历这些 Bean,顺次判断是否是处理器,并检测其 HandlerMethod
  3. 遍历 Handler 中的所有办法,找出其中被 @RequestMapping 注解标记的办法。
  4. 获取办法 method 上的 @RequestMapping 实例。
  5. 查看办法所属的类有没有 @RequestMapping 注解
  6. 将类档次的 RequestMapping 和办法级别的 RequestMapping 联合 (createRequestMappingInfo)
  7. 循环注册进去,申请的时候会再用到

四、后续

后续文章👉从浏览器发送申请给 SpringBoot 后端时,是如何精确找到哪个接口的?(下篇)

集体所谈

浏览源码的过程中,其实真的是充斥乏味和干燥的。

读懂了一些要害货色,就开心的不得了;而像“又遗记 debug 到哪了,思路又凉了 ”,就会开始满心埋怨(我经常想骂上一两句)。而后就持续苦逼的去看。

大家好,我是博主 宁在春:主页

一名喜爱文艺却踏上编程这条路线的小青年。

心愿:咱们,待别日相见时,都已有所成

另外就只能说是在此提供一份个人见解。因文字功底有余、常识不足,写不出非常术语化的文章。望见谅

如果感觉本文让你有所播种,心愿可能点个赞,给予一份激励。

也心愿大家可能踊跃交换。如有不足之处,请大家及时批正,在此感激大家。

知乎 | 宁在春

简书 | 宁在春

CSDN | 宁在春

掘金 | 宁在春

博客园 | 宁在春

退出移动版