SpringMVC源码分析(二)启动过程之RequestMappingHandlerMapping分析

news2024/11/15 11:12:05

a、http请求中的url是如何与对应Handler的即Controller对应method映射的?

在上篇中提到在SpringMVC中,初始化9大内置组件的时候其中有一个组件就是HandlerMapping,在初始化HandlerMapping的时候会加载代码中所有标注了@Controller和@RequestMapping的类到spring容器中,作为一个个bean对象。

关键类RequestMappingHandlerMapping

类图上看出RequestMappingInfoHandlerMapping继承了AbstractHandlerMethodMapping。实现了InitializingBean接口并且实现了afterPropertiesSet方法。所以在spring初始化这个RequestMappingHandlerMapping对象的时候会进入到afterPropertiesSet()中,这个里面会调用父类AbstractHandlerMethodMapping的afterPropertiesSet(),然后调用initHandlerMethods()。在其中会初始化所有的HandlerMethods。

在这里插入图片描述

1、initHandlerMethods()

在当前方法中,主要做了几件事:

  1. 扫描所有的Handler类,获取所有带有@Controller或@RequestMapping注解的类。
  2. 遍历每个Handler类,获取类中的所有方法。
  3. 对于每个方法,判断是否存在@RequestMapping注解。
  4. 如果存在@RequestMapping注解,则解析该注解,获取其中的属性值,如请求路径、请求方法、请求参数等。
  5. 根据解析到的属性值,生成一个RequestMappingInfo对象,该对象代表了一个请求路径和请求方法的映射关系。
  6. 将生成的RequestMappingInfo对象与对应的HandlerMethod对象进行关联,形成一个映射关系
  7. 将该映射关系保存到RequestMappingInfoHandlerMapping中的pathLookup和registry两个Map中。
    • pathLookup是一个Map,以请求路径作为键,将对应的RequestMappingInfo对象作为值存储起来,用于后续处理请求时的查找。
    • registry是一个Map,以RequestMappingInfo对象作为键,将对应的HandlerMethod对象作为值存储起来,用于后续执行相应的方法。
  8. 遍历完所有的Handler类和方法后,初始化完成,此时已经将请求路径、请求方法和对应的HandlerMethod对象都保存起来了。

当有实际的请求进来时,RequestMappingHandlerMapping会根据请求的路径和方法,从pathLookup中查找对应的RequestMappingInfo对象。
然后,通过RequestMappingInfo对象从registry中获取对应的HandlerMethod对象,从而执行相应的方法。HandlerMethod对象是在处理请求时动态生成的,它包含了方法的相关信息,如所属的类、方法名、参数列表等。

在这里插入图片描述

   protected void initHandlerMethods() {
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Looking for request mappings in application context: " + this.getApplicationContext());
        }
        //这里是获取应用中所有Object的bean的名字
        String[] beanNames = this.detectHandlerMethodsInAncestorContexts?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.obtainApplicationContext(), Object.class):this.obtainApplicationContext().getBeanNamesForType(Object.class);
        String[] var2 = beanNames;
        int var3 = beanNames.length;
        //遍历这个含有应用中所有beanName的字符串数组,并得到这个beanName对应的bean的类型
        for(int var4 = 0; var4 < var3; ++var4) {
            String beanName = var2[var4];
            if(!beanName.startsWith("scopedTarget.")) {
                Class beanType = null;
 
                try {
                    //根据这个beanName对应的beanType的类型
                    beanType = this.obtainApplicationContext().getType(beanName);
                } catch (Throwable var8) {
                    if(this.logger.isDebugEnabled()) {
                        this.logger.debug("Could not resolve target class for bean with name \'" + beanName + "\'", var8);
                    }
                }
                //判断这个根据这个bean的类型判断是不是一个handler
                if(beanType != null && this.isHandler(beanType)) {
                    this.detectHandlerMethods(beanName);
                }
            }
        }
 
        this.handlerMethodsInitialized(this.getHandlerMethods());
    }

1.1 isHandler()

这个Bean是否含有@Controller注解或@RequestMapping注解,如果是就表示是一个handler

 * {@inheritDoc}
	 * Expects a handler to have a type-level @{@link Controller} annotation.
	 */
	@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

1.2 detectHandlerMethods()

获取这个handler中所有requestMappinng的方法,然后循环去注册该方法与对应requestMapping信息到一个名为registry的一个HashMap中去

    protected void detectHandlerMethods(Object handler) {
        Class handlerType = handler instanceof String?this.obtainApplicationContext().getType((String)handler):handler.getClass();
        if(handlerType != null) {
            Class userType = ClassUtils.getUserClass(handlerType);
            //获取这个handler中有requestMapping的方法
            //这个methods的Map结构为key是一个Method对象,value是一个RequestMappingInfo对象
            Map methods = MethodIntrospector.selectMethods(userType, (method) -> {
                try {
                    return this.getMappingForMethod(method, userType);
                } catch (Throwable var4) {
                    throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
                }
            });
            if(this.logger.isDebugEnabled()) {
                this.logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
            }
            //循环去注册Method与RequestMappingInfo的关系
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                this.registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
 
    }
1.2.1 第一步selectMethods()

1、若这个targetType不是一个代理类,就获得它本身的类以及它的接口放入handlerTypes这么一个Set中去。
2、遍历这个handlerTypes,找到用户自己定义的方法并过滤出有requestMapping的方法,并将之塞入一个methodMap中

   public static <T> Map<Method, T> selectMethods(Class<?> targetType, MethodIntrospector.MetadataLookup<T> metadataLookup) {
        LinkedHashMap methodMap = new LinkedHashMap();
        LinkedHashSet handlerTypes = new LinkedHashSet();
        Class specificHandlerType = null;
        //若这个targetType不是一个代理类,就获得它本身的类以及它的接口
        if(!Proxy.isProxyClass(targetType)) {
            specificHandlerType = ClassUtils.getUserClass(targetType);
            handlerTypes.add(specificHandlerType);
        }
 
        handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
        Iterator var5 = handlerTypes.iterator();
        //遍历
        while(var5.hasNext()) {
            Class currentHandlerType = (Class)var5.next();
            Class targetClass = specificHandlerType != null?specificHandlerType:currentHandlerType;
            //找到用户自己定义的方法并过滤出有requestMapping的方法,并将之塞入一个methodMap中
            ReflectionUtils.doWithMethods(currentHandlerType, (method) -> {
                Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                Object result = metadataLookup.inspect(specificMethod);
                if(result != null) {
                    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                    if(bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                        methodMap.put(specificMethod, result);
                    }
                }
 
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
 
        return methodMap;
    }
1 ReflectUtilsl.doWithMethods(Class<?> clazz, ReflectionUtils.MethodCallback, ReflectionUtils.MethodFilter)

入参1: Class<?> targetType
入参2: MethodCallback 一个方法回调
入参3: MethodFilter方法过滤器

在当前方法中主要做了3件事:
1、首先获取这个Class中所有定义的方法并且将之存入一个methods的Method数组中
2、遍历这个methods数组中的method如果这个mf方法拦截器为空或者这个method与方法拦截器mf的匹配规则对应,就回调mc.doWith方法。这个mc.doWith()就会调用回到去执行doWithMethods()的第二个lamda表达式。在这个表达式中又会继续回掉执行另一个方法。
3、后面我们还发现对这个类的父类和接口都有一个递归调用

其中这个mf方法拦截器就是这个RelectionUtils.USER_DECLARED_METHODS;顾名思义就是用户自己定义的方法,而非继承与Object类的方法什么的。

/**
	 * 执行给定回调操作在给定类和父类(或者给定的接口或父接口)的所有匹配方法
	 */
	public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {
		// Keep backing up the inheritance hierarchy.
		// 从缓存中获取clazz的所有声明的方法,包括它的所有接口中所有默认方法;没有时就从{@code clazz}中获取,再添加到缓存中,
		Method[] methods = getDeclaredMethods(clazz, false);
		// 遍历所有方法
		for (Method method : methods) {
			// 如果mf不为null 且 method不满足mf的匹配要求
			if (mf != null && !mf.matches(method)) {
				// 跳过该method
				continue;
			}
			try {
				// 对method执行回调操作
				mc.doWith(method);
			}
			catch (IllegalAccessException ex) {
				throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
			}
		}
		// 如果clazz的父类不为null且(mf不是与未在{@code java.lang.Object}上声明的所有非桥接非合成方法匹配的预购建方法过滤器或者clazz的父类不为Object
		if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {
			// 递归方法
			// 执行给定回调操作在clazz的父类的所有匹配方法, 子类和父类发生的相同命名方法将出现两次,
			// 子类和父类发生的相同命名方法将出现两次,除非被mf排查
			doWithMethods(clazz.getSuperclass(), mc, mf);
		}
		// 如果clazz是接口
		else if (clazz.isInterface()) {
			// 遍历clazz的所有接口
			for (Class<?> superIfc : clazz.getInterfaces()) {
				// 递归方法
				// 执行给定回调操作在superIfc的所有匹配方法, 子类和父类发生的相同命名方法将出现两次,
				// 子类和父类发生的相同命名方法将出现两次,除非被mf排查
				doWithMethods(superIfc, mc, mf);
			}
		}
	}
2 mc.doWith(method)–> 回调3

当执行到这个方法时会回掉执行doWithMethods()中的第二个入参即lamda表达式

method -> {
				Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
				T result = metadataLookup.inspect(specificMethod);
				if (result != null) {
					Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
					if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
						methodMap.put(specificMethod, result);
					}
				}
			}
3 metadataLookup.inspect(specificMethod)–> 回调1.2.1

执行到inspect()方法的时候又会继续调用MethodIntrospector.selectMethods()方法中的第二个入参数去执行第二个lamda表达式。

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);
						}
					});
4 getMappingForMethod()

最终执行到getMappingForMethod(),找到这个方法上的RequestMapping,如果这个方法上的requestMapping信息不为空的话就去照这个handler类上面的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()

RequestMappingInfo 是请求映射信息的封装对象,用来确定请求的URL、请求方法、请求参数等信息

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		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);
	}
protected RequestMappingInfo createRequestMappingInfo(
			RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

		RequestMappingInfo.Builder builder = RequestMappingInfo
				.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
				.methods(requestMapping.method())
				.params(requestMapping.params())
				.headers(requestMapping.headers())
				.consumes(requestMapping.consumes())
				.produces(requestMapping.produces())
				.mappingName(requestMapping.name());
		if (customCondition != null) {
			builder.customCondition(customCondition);
		}
		return builder.options(this.config).build();
	}
1.2.2 第二步 registerHandlerMethod()

遍历methods注册handlerMethod

。。。。。省略selectMethods()中的代码
//循环去注册Method与RequestMappingInfo的关系
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                this.registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
1 registerHandlerMethod()
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		this.mappingRegistry.register(mapping, handler, method);
	}
2 register()

通过handler与method创建HandlerMethod对象;确保requestMapping唯一映射一个method, 最后注册requestMappingInfo与对应handlerMethod的关系。

       public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
 
            try {
    //创建HandlerMethod对象,这个对象包含了handler与method的信息
                HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
    //确保同一个requestMapping唯一映射一个method
    // 例如:url路径 /aaa/bbb 只能对应methodA 不能对应对应methodB
                this.assertUniqueMethodMapping(handlerMethod, mapping);
                if(AbstractHandlerMethodMapping.this.logger.isInfoEnabled()) {
                //SpringBoot项目或者SpringMVC项目启动的时候控制台上输出的就是这个
                    AbstractHandlerMethodMapping.this.logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
                }
                //注册requestMapping与HandlerMethodInfo的关系
                this.mappingLookup.put(mapping, handlerMethod);
                List directUrls = this.getDirectUrls(mapping);
                Iterator name = directUrls.iterator();
 
                while(name.hasNext()) {
                    String corsConfig = (String)name.next();
                    this.urlLookup.add(corsConfig, mapping);
                }
 
                String name1 = null;
                if(AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
                    name1 = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
                    this.addMappingName(name1, handlerMethod);
                }
 
                CorsConfiguration corsConfig1 = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);
                if(corsConfig1 != null) {
                    this.corsLookup.put(handlerMethod, corsConfig1);
                }
 
                this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directUrls, name1));
            } finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1081001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

视频目标分割数据集分享

MOSE: A New Dataset for Video Object Segmentation in Complex Scenes MOSE 是一个新的视频目标分割数据集&#xff0c;旨在解决复杂环境下的目标跟踪和分割。MOSE 包含 2,149 个视频片段和来自 36 个类别的 5,200 个物体&#xff0c;以及 431,725 个高质量物体分割掩码。MOS…

ubuntu安装依赖包时显示需要先安装其所需要的各种安装包)apt-get源有问题

最近在崭新的ubuntu上安装g以及一些其他的依赖与工具时遇到以下报错情况&#xff1a; 依赖环环相扣&#xff0c;手动无法解决。 总结&#xff1a; 出现需要很多依赖项的情况是因为软件源中没有可用的依赖项或者依赖项版本不正确。 其实在Ubuntu系统中&#xff0c;使用sudo…

Java程序设计实验4 | 面向对象(下)

*本文是博主对Java各种实验的再整理与详解&#xff0c;除了代码部分和解析部分&#xff0c;一些题目还增加了拓展部分&#xff08;⭐&#xff09;。拓展部分不是实验报告中原有的内容&#xff0c;而是博主本人自己的补充&#xff0c;以方便大家额外学习、参考。 &#xff08;没…

AI如何帮助Salesforce从业者找工作?

在当今竞争激烈的就业市场中&#xff0c;找到满意的工作是一项艰巨的任务。成千上万的候选人竞争一个岗位&#xff0c;你需要利用一切优势从求职大军中脱颖而出。 这就是AI的用武之地&#xff0c;特别是像ChatGPT这样的人工智能工具&#xff0c;可以成为你的秘密武器。本篇文章…

2.1、如何在FlinkSQL中读取写出到Kafka

目录 1、环境设置 方式1&#xff1a;在Maven工程中添加pom依赖 方式2&#xff1a;在 sql-client.sh 中添加 jar包依赖 2、读取Kafka 2.1 创建 kafka表 2.2 读取 kafka消息体&#xff08;Value&#xff09; 使用 format json 解析json格式的消息 使用 format csv 解析…

力扣第98题 验证二叉搜索树 c++ 与上一篇文章相似

题目 98. 验证二叉搜索树 中等 相关标签 树 深度优先搜索 二叉搜索树 二叉树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当…

淘宝天猫商品历史价格API接口

获取淘宝商品历史价格接口的步骤如下&#xff1a; 注册淘宝开放平台&#xff1a;首先在淘宝开放平台上注册一个账号&#xff0c;并进行登录。创建应用&#xff1a;在淘宝开放平台上创建一个应用&#xff0c;并获取该应用的App Key和App Secret&#xff0c;用于后续的接口调用。…

Python“梦寻”淘宝天猫店铺所有数据接口,淘宝店铺所有商品数据API接口,淘宝API接口申请指南(含代码示例)

获取淘宝店铺所有商品数据的接口可以通过淘宝开放平台获取。 具体操作步骤如下&#xff1a; 在淘宝开放平台注册成为开发者&#xff0c;并创建一个应用&#xff0c;获取到所需的 App Key 和 App Secret 等信息。使用获取到的 App Key 和 App Secret&#xff0c;进行签名和认证…

Android组件通信——Intent(二十三)

1. 认识Intent 1.1 知识点 &#xff08;1&#xff09;了解Intent的主要作用&#xff1b; &#xff08;2&#xff09;掌握Activity程序对Intent操作的支持&#xff1b; &#xff08;3&#xff09;可以使用Intent完成Activity程序间的跳转&#xff0c;也可以通过Intent接收返…

QT基础 QChart绘制折线

目录 1.简单折线 2.数学折线 3.可滑动折线 1.简单折线 //![1] //! 折现段坐标QLineSeries *series new QLineSeries(); //![1]//![2] //! 添加点series->append(0, 6);series->append(2, 4);series->append(3, 8);series->append(7, 4);series->append(10, 5)…

YOLOv7改进: CFP:即插即用的多尺度融合模块,EVC助力小目标检测| 顶刊TIP 2023

💡💡💡本文独家改进:即插即用的多尺度融合模块,EVC助力小目标检测 EVC | 亲测在多个数据集实现暴力涨点,强烈推荐,独家首发; 收录: YOLOv7高阶自研专栏介绍:http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOLOv7自研创新结合,轻松搞…

MS31804四通道低边驱动器可pin对pin兼容DRV8804

MS31804TE 是一个具有过流保护功能的四通道低边驱动器。MS31804TE 内置钳位二极管&#xff0c;用来钳制由电感负载续流产生的电压。MS31804TE 可以驱动单极步进电机、直流电机、继电器、螺线管或者其它负载。 散热良好的情况下&#xff0c;MS31804TE 可以提供每个通道最高 2A 的…

整理笔记——二极管

一、什么是二极管 二极管是一种由半导体材料制成的一种具有单向导电性能的电子元器件&#xff0c;二极管的核心是PN结。 加在二极管两端的电压和通关的电流被成为&#xff0c;二极管的伏安特性曲线 ​​​ 二极管的正向特性&#xff1a;起初正向电压较小时&#xff0c;正向电流…

[ACTF2020 新生赛]Exec1

拿到题目&#xff0c;不知道是sql注入还是命令执行漏洞 先ping一下主机 有回显&#xff0c;说明是命令执行漏洞 我们尝试去查看目录 127.0.0.1|ls&#xff0c;发现有回显&#xff0c;目录下面有个index.php的文件 我们之间访问index.php 输入127.0.0.1;cat index.php 发现又…

基于Springboot实现汽车租赁平台管理系统项目【项目源码】

基于Springboot实现汽车租赁平台管理系统演示 JAVA简介 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterprise JavaBeans&#xff09;的全面支持&#xff0c;java servlet API&#xff0c;JSP&#xff08;java ser…

选择适合自身业务的HTTP代理有哪些因素决定?

相信对很多爬虫工作者和数据采集的企业来说&#xff0c;如何选购适合自己业务的HTTP代理是一个特别特别困扰的选题&#xff0c;市面上那么多HTTP代理厂商&#xff0c;好像这家有这些缺点&#xff0c;转头又看到另外一家的缺点&#xff0c;要找一家心仪的仿佛大海捞针。今天我们…

Table.Group系列_第4参数为全局的情况下,利用第五参数进行分组汇总

原始数据: 部门与职位存在于同一列中 实现功能: 根据筛选条件,可对部门或职位进行统计汇总第一列列名根据筛选自动变更,显示当前统计的维度 实现方式: 1. 构建筛选器内容 在任意空白单元格内输入需要筛选的内容 2. 插入"组合框"控件,并进行相应设置 从开发工具…

flex布局在多层嵌套时,内层设置了justify-content: space-between;不生效问题

内层的地址和时间这一行&#xff0c;设置了justify-content: space-between;但并不生效&#xff0c;原因是要在上一层.center 设置 flex:1;&#xff08; 重点&#xff09; 经常忘记&#xff0c;特在此记录一下&#xff0c;以下是代码 <view class"index-card" c…

深度学习基础知识 BatchNorm、LayerNorm、GroupNorm的用法解析

深度学习基础知识 BatchNorm、LayerNorm、GroupNorm的用法解析 1、BatchNorm2、LayerNorm3、GroupNorm用法&#xff1a; BatchNorm、LayerNorm 和 GroupNorm 都是深度学习中常用的归一化方式。 它们通过将输入归一化到均值为 0 和方差为 1 的分布中&#xff0c;来防止梯度消失和…

学生用的台灯哪种比较好?分享专家推荐的学生台灯

对于学生来说&#xff0c;台灯是必不可少的一盏学习照明灯具&#xff0c;它能提供室内不足的光线、亮度&#xff0c;基本每个学生在宿舍、家里都会备着一台。不过台灯也并不是随便挑选一台使用就可以的&#xff0c;很多学生就是因为使用了一些价格低廉、质量安全没有保障的台灯…