开发20年,你可知道SpringWeb接口资源是如何保存起来的?

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 我们在浏览器上只输入了一个URL地址,怎么就能访问到这个接口的呢?于是乎就引出了今天我们要讨论的话题。Spring中的Web接口资源是如何保存起来的?

前言

在我们使用 SpringBoot 开发中,我们定义一接口是下面这样的

@RestController
public class UserController{
    
    @GetMapping( name = "/getUserName")
    public String getUserName(){
        return "Hello World";
    }
}

这时候我们思考一个问题,我们在浏览器上只输入了一个URL地址,怎么就能访问到这个接口的呢?于是乎就引出了
今天我们要讨论的话题。Spring中的Web接口资源是如何保存起来的?

一、Spring中的Web接口资源是如何保存起来的?

在我们学习之前我们可以先自己来进行思考一下。处理逻辑是什么样的?

  • Spring容器解析 @RequestMapping 注解。当然这个注解又派生了其他的注解比如。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {}

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

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.DELETE)
public @interface DeleteMapping {}
  • 我们猜测Spring源码中一定会对@RestController@Controller标记的类,里面的每个
    Method进行处理,判断是否包含了上面的注解。注解那么多Spring肯定不会这样一个一个去处理。我们可以看到
    上面的注解都使用了@AliasFor注解。其中奥妙就在这里。看下面例子代码。
  • 我们猜测Spring肯定对这些Method判断是否有@RequestMapping有注解。

1. @AliasFor使用

@RestController
public class PostController {

    @ApiOperation(value = "查询Bbs所有文章")
    @PostMapping(value = "/query/bbs/posts", produces = MediaType.APPLICATION_JSON_VALUE)
    public Result<PostAllResponse> queryBbsPostAll(@RequestBody PostAllSelectRequest postAllSelectRequest) {
        return postBiz.queryBbsPostAll(postAllSelectRequest);
    }

    public static void main(String[] args) {
        Method queryBbsPostAll = ClassUtils.getMethod(PostController.class, "queryBbsPostAll",PostAllSelectRequest.class);
        PostMapping annotation = AnnotationUtils.findAnnotation(queryBbsPostAll, PostMapping.class);
        ///query/bbs/posts
        System.out.println(StringUtils.arrayToCommaDelimitedString(annotation.value()));
        //application/json
        System.out.println(StringUtils.arrayToCommaDelimitedString(annotation.produces()));
        //是否包含RequestMapping: true
        System.out.println("是否包含RequestMapping: "+AnnotatedElementUtils.hasAnnotation(queryBbsPostAll,RequestMapping.class));

        RequestMapping mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(queryBbsPostAll, RequestMapping.class);
        ///query/bbs/posts
        System.out.println(StringUtils.arrayToCommaDelimitedString(mergedAnnotation.value()));
    }
}

可以看到只要使用下面代码就能把被@PostMapping等等的注解都涵盖了。

System.out.println("是否包含RequestMapping: "+AnnotatedElementUtils.hasAnnotation(queryBbsPostAll,RequestMapping.class));
RequestMapping mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(queryBbsPostAll, RequestMapping.class);

2. 解析请求Method

AbstractHandlerMethodMapping 实现 InitializingBean。在当前 Bean初始化时候会执行

afterPropertiesSet -> initHandlerMethods

从这里开始解析Web资源类的信息。请小伙伴们看下面的截图,截图中源码已经把类名也截上了,方便小伙伴们自己在根据截图看一遍源码。

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }
    
    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #getCandidateBeanNames()
     * @see #processCandidateBean
     * @see #handlerMethodsInitialized
     */
    protected void initHandlerMethods() {
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
}

RequestMappingHandlerMapping解析Method上的RequestMapping信息

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

detectHandlerMethods方法就是真正开始解析Method的逻辑。通过解析Method上的 @RequestMapping或者其他派生的注解。生成请求信息。
注意这个请求信息里面也是有很多逻辑的不过不是本篇讨论的重点,就不说了。稍微提一下。根据规则来匹配url逻辑就在这里面。

这里我们能看到源码里拿到了Method并拿到了执行这个Method的实例Bean。在这里封装成了HandlerMethod并注册到了MappingRegistry中。

在注册的过程中把RequestMapping中的路径信息同事也放到一个urlLookup中。key是url,value是Mapping信息。

到这里其实我们就把本篇的议题就说明清楚了。下面我们在看下SpringWeb是如何将http请求信息路由到具体的HandlerMethod的吧。

3. 最后串一下流程

看了前面的截图,我们知道Spring是如何把这些Web资源信息给保存起来的了。然后就看是DispatcherServlet的逻辑了。

首先DispatcherServlet 是一个Servlet。Servlet相信大家都都知道就不重点说原理。 我们直接看
doService -> doDispatch 方法

根据请求路径,找到从Mapping信息,然后根据Mapping信息匹配到具体的HandlerMethod。 ok本篇内容就到这里。谢谢大家。

相关文章
|
8月前
|
JSON 数据格式
【有关调用第三方接口工具】
【有关调用第三方接口工具】
68 0
|
JSON 前端开发 JavaScript
释放双手,优雅地调用后端接口💗
释放双手,优雅地调用后端接口💗
1301 0
释放双手,优雅地调用后端接口💗
|
4月前
|
IDE Java 开发工具
流操作代码开发后端逻辑
该文档指导开发者在魔笔平台上下载代码模板并进行自定义逻辑流操作开发。首先登录魔笔并下载`bundle.zip`模板,解压缩后获得一个包含`custom-action-core`模块的Java工程。核心模块已预置接口与实现类,开发者需在`execute`方法中编写业务逻辑。工程要求JDK11+及Maven3.5+环境,并提供IDEA调试建议。每个自定义操作对应一个实现类,`MobiContext`参数简化了实体与结构体操作。注意不要修改工程特定目录以外的内容以确保兼容性。
43 2
|
6月前
|
监控 Java 测试技术
开发与运维资源问题之接口A的阻塞情况比接口B更明显,尽管接口B的响应时间更长如何解决
开发与运维资源问题之接口A的阻塞情况比接口B更明显,尽管接口B的响应时间更长如何解决
47 2
开发与运维资源问题之接口A的阻塞情况比接口B更明显,尽管接口B的响应时间更长如何解决
|
5月前
|
开发工具 Android开发
Android项目架构设计问题之SDK内部减少每次回调时的冗余判断逻辑如何解决
Android项目架构设计问题之SDK内部减少每次回调时的冗余判断逻辑如何解决
50 0
|
7月前
|
消息中间件 存储 Serverless
函数计算产品使用问题之想要请求持久化该怎么操作
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
8月前
|
前端开发 JavaScript
怎样使用接口引用数据
怎样使用接口引用数据
|
Java 数据库连接 API
流程定义资源文件下载
流程定义资源文件下载
|
安全 C++ Windows
C++调用外部应用程序的方法的整理总结(常用)
一、三个SDK函数:  WinExec,ShellExecute ,CreateProcess可以实现调用其他程序的要求,其中以WinExec最为简单,ShellExecute比WinExec灵活一些,CreateProcess最为复杂。
3059 0
|
Java
CheerpJ调用的两种方式
CheerpJ调用的两种方式
258 0