@RequestMapping注解和Controller方法建立映射的流程

2023-11-13

当你的才华还撑不起你的野心的时候,唯有静下心来学习。

       有没有想过,为什么在controller类的方法上,添加一个@RequestMapping("/toIndex")注解的时候,从浏览器的输入localhost:8080/toIndex请求就可以到达指定的controller方法呢?

大致实现原理

       大致实现原理,可以简单来说,稍后会进行源码证明。原理就是在spring在解析到这个@RequestMapping的时候,可以拿到它里面的值,以及标记了这个注解的方法。然后,把他们存在一个map集合中,key@RequestMappingvalue(对应上面的toIndex),value为标记了这个注解的方法。当浏览器发送请求的时候,可以从request中获取到请求路径(如localhost:8080/toIndex,可以获取到toIndex),然后拿到请求路径后从之前的map集合中去查找,如查找到就反射调用他的method方法。

源码实现

       SpringMVC在和Tomcat整合的时候,会在Tomcat启动的时加载DispatcherServlet这个类。在加载DispatcherServlet这个类的时候,他有静态代码块,会在加载的时候执行:

static {
		try {
		//  加载DispatcherServlet.properties属性文件, 供之后使用
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
		}
	}

       DispatcherServlet.properties的属性文件中有这一段内容,其中RequestMappingHandlerMapping这个类就是完成@RequestMapping到方法的映射,其他内容自己查看:

org.springframework.web.servlet.HandlerMapping=
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

       DispatcherServlet本身就是一个Servlet容器。所以启动的过程中会调用他的init()方法进行Springmvc/Web环境的初始化,init方法由他的父类HttpBeanServlet进行实现,具体细节可以看源码实现,相对简单。
在这里插入图片描述
通过以上init()方法的初始化流程,最终会到initStrategies(context)这个方法,这个方法中有调用initHandlerMappings(context)方法,完成HandlerMapping的初始化:

protected void initStrategies(ApplicationContext context) {
		...
		initHandlerMappings(context);// 初始化HandlerMapping
		...
}

       额外说明,其实在加载DispatcherServlet这个类的时候,Spring的环境已经初始化完成了,通过DispatherServlet的构造方法传递一个WebApplicationContext的实现类,然后DispatcherServlet就持有了Spring上下文环境(包括工厂,以及注册的bean)。
initHandlerMappings(context)方法的实现:

private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		// detectAllHandlerMappings默认为true
		if (this.detectAllHandlerMappings) { 
		//	这个方法底层实现,是从beanFactory容器中获取HandlerMapping类型的bean
		//  第一次获取为空,还未注册
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
			}
		}
		// 获取为空,
		if (this.handlerMappings == null) {
			// 核心代码,从之前静态代码块已经加载好的DispatcherServlet.properties的
			// defaultStrategies对象从获取内容,反射创建实例注册到容器中
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		// key = org.springframework.web.servlet.HandlerMapping
		String key = strategyInterface.getName();
		// 
		/**
		* 获取到属性文件的key对应的value
		* 	org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
		*   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
		*/
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			// 根据逗号分隔
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<>(classNames.length);
			for (String className : classNames) {
				try {
				    // 反射创建实例
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					// 底层是调用createBean方法,注册到beanFactory容器中,并实例化
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Unresolvable class definition for DispatcherServlet's default strategy class [" +
							className + "] for interface [" + key + "]", err);
				}
			}
			return strategies;
		}
		else {
			return new LinkedList<>();
		}
	}

getDefaultStrategies()这个方法执行结束后,很明显RequestMappingHandlerMapping这个类已经注入到beanFactory容器中了,交给了spring来管理。又由于RequestMappingHandlerMapping这个类的父类实现了InitializingBean这个接口,而RequestMappingHandlerMapping这个类又重写了InitializingBean接口的afterPropertiesSet()方法,所有在spring在创建他的实例的时候,会回调他的afterPropertiesSet()

@Override
	public void afterPropertiesSet() {
		// 调用父类
		super.afterPropertiesSet();
	}
@Override
	public void afterPropertiesSet() {
		initHandlerMethods();
	}

	protected void initHandlerMethods() {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for request mappings in application context: " + getApplicationContext());
		}
		// 从spring容器中获取Object类型的对象的名称,也就是获取容器中的所有beanName
		String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
				obtainApplicationContext().getBeanNamesForType(Object.class));

		for (String beanName : beanNames) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				Class<?> beanType = null;
				try {
					// 根据beanName获取对应的实例信息
					beanType = obtainApplicationContext().getType(beanName);
				}
				catch (Throwable ex) {
					// An unresolvable bean type, probably from a lazy bean - let's ignore it.
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
					}
				}
				// isHandler方法就是检测这个实例的类上是否有
				// @Contoller注解或@RequestMapping注解
				if (beanType != null && isHandler(beanType)) {
					// 发现handler方法
					detectHandlerMethods(beanName);
				}
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}
@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

detectHandlerMethods(beanName);就是用来处理我们的类里面加了@ReuqestMapping注解的方法:

protected void detectHandlerMethods(final Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			final Class<?> userType = ClassUtils.getUserClass(handlerType);
			// 这个方法是发现 所有加了@RequestMapping注解的方法,放入map集合中
			// key为方法,T为我们的url路径字符串,可以自行调试
			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.isDebugEnabled()) {
				logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
			}
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// 把查询处理的方法放入到map集合中,供请求时使用
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		this.mappingRegistry.register(mapping, handler, method);
}
	public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				assertUniqueMethodMapping(handlerMethod, mapping);

				if (logger.isInfoEnabled()) {
					logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
				}
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					// TODO 在这一步放入到map集合中(spring自己定义的集合,和map结构差不多)
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

       到这里整个@RequestMapping到方法的映射,并存入map集合中的初始化工作已经结束了,可以认为在浏览器输入请求的时候,直接获取url,然后到urlLookup的map集合中去获取方法就可以了,然后执行method.invoke方法完成方法的调用。这样就完成了@RequestMapping到方法的映射。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@RequestMapping注解和Controller方法建立映射的流程 的相关文章

随机推荐

  • 【Python爬虫与数据分析】爬虫Json数据解析

    目录 一 Json文件数据解析 二 Json数据包解析获取图片资源 三 Json数据包解析获取视频资源 一 Json文件数据解析 json字符串 通常类似python数据类型中的列表和字典的结合 也可能是单独的列表或者字典格式 通常可以通过
  • Win10:修改电脑桌面路径

    Win10 修改电脑桌面路径 1 win R进入运行 输入 regedit 2 进入 注册表编辑器 3 依次打开 HKEY CURRENT USER Software Miscrosoft Windows Explorer Uesr she
  • 腾讯2020校招第一次笔试第1题

    小Q想要给他的朋友发送一个神秘字符串 但是他发现字符串的过于长了 于是小Q 发明了一种压缩算法对字符串中重复的部分进行了压缩 对于字符串中连续的m个相同字 符串S将会压缩为 m S m为一个整数且1 lt m lt 100 例如字符串ABC
  • 如何创建Vue项目

    一 Vue项目构建 1 安装node 简介 Node js并不是我们平常使用的JavaScript库 它是一个JavaScript的运行环境 基于这个环境 我们可以不需要浏览器直接执行js代码 类似于Java的运行环境jdk 安装 官网下载
  • 环境搭建之jetBrains-IDEA安装激活

    1 下载安装程序 2 运行安装程序 3 装完后运行 激活或者破解参考 https blog csdn net justszh article details 81484802 ThisCrackLicenseId licenseId Thi
  • 炒了8年的概念,到底该如何理解DevOps这个词?

    如何实施DevOps成为众多企业迫切面临的问题 本文作者刘相 有10多年的从业经验 他结合自身企业实施DevOps的经验 梳理出DevOps在企业的组织 技术 流程等方面的最佳实践与价值 以及如何搭建DevOps平台来支撑DevOps的落地
  • Windows环境同时安装多个版本的Python解释器(python2和python3)(超详细)

    1 背景描述 众所周知 python当前有两大主流版本 分别是Python2和Python3系列 其中Python3因为对Python2做了较大的优化 使得Python3不会向下兼容 但是工作和学习中 有很多项目需要Python2的环境 有
  • Unity使用UnityWebRequest请求服务器json数据,webgl端服务器请求

    根据unity官方说的 WebGL 网络无法直接访问套接字 由于存在安全隐患 JavaScript 代码无法直接访问 IP 套接字来实现网络连接 因此 NET 网络类 即 System Net 命名空间中的所有内容 具体而言就是 Syste
  • error_80051,source size 2780KB exceed max limit 2MB [时间][appid]

    error 80051 source size 2780KB exceed max limit 2MB 时间 appid 微信小程序预览报错 message Error 系统错误 错误码 80051 source size 2780KB e
  • Docker之容器退出(实战)

    docker实战 一 centos7 yum安装docker docker实战 二 基础命令篇 docker实战 三 docker网络模式 超详细 docker实战 四 docker架构原理 docker实战 五 docker镜像及仓库配置
  • 好用的资源推荐

    前端 扁平化前端css框架 materializecss 很喜欢里面的组件和javascript的调用方式 简单易用多组件好看的css框架 semantic ui 简单好看的基于jquery的消息提示框 CodeSeven toastr 好
  • Java小技巧:避免缓存,Java动态加载配置文件

    Java动态加载配置文件 关键 每次读取都要重新生成流 今天无意间在项目的代码中看到如下这样一段简单加载配置文件的代码 Properties prop new Properties InputStream in PropertiesTest
  • 瀑布流&下拉加载

    瀑布流结合列表下拉加载动画过渡展示 前言 需求默认瀑布流展示列表 下拉加载的时候看到列表顺序过渡加载 1 效果样式 如下 下拉瀑布流加载图片列表 2 引入文件 用到macyjs瀑布流与下拉加载插件mescroll mescroll官网 ht
  • 数据结构学习系列之顺序表的两种插入方式

    方式1 在顺序表末端插入数据元素 代码如下 示例代码 int insert seq list 1 list t seq list int data if NULL seq list printf 入参为NULL n return 1 if
  • 【学习之路】axios之axios.CancelToken

    写法 接受一个回调函数作为参数 new axios CancelToken cancel gt cancel是取消本次请的方法 if xxx cancel else TODO 一般用法 用于发出多个相同请求时 取消重复请求 使用场景 使用数
  • 虚拟机:[Errno 256] No more mirrors to try

    使用linux系统的yum命令去安装一些软件时总是会出现各种问题 下面总结一下 首先确认yum搭建成功 使用mount挂载 每次开虚拟机 都记得要mount挂载 因为这个mount是暂时的 关机后下次就必须重新挂载 不然yum无法正常进行
  • 微信小程序隐藏滚动条的方法,以及禁止上下滑动的方法

    webkit scrollbar display none width 0 height 0 color transparent ms scrollbar display none width 0 height 0 color transp
  • android 手机网络接入点名称及WAP、NET模式的区别

    移动 电信 联通 APN cmwap cmnet ctwap ctnet 3gwap uniwap 3gnet uninet设置 APN Access Point Name 即 接入点名称 用来标识GPRS的业务种类 目前分为两大类 CMW
  • PACS系统源码 PACS源码 基于VC + MSSQL开发

    基于VC MSSQL开发的一套大型医院医学影像PACS系统源码 有演示 文末获取联系 PACS系统可以覆盖医院现有放射 CT MR 核医学 超声 内镜 病理 心电等绝大部分DICOM和非DICOM检查设备 支持从科室级 全院级 集团医院级乃
  • @RequestMapping注解和Controller方法建立映射的流程

    当你的才华还撑不起你的野心的时候 唯有静下心来学习 有没有想过 为什么在controller类的方法上 添加一个 RequestMapping toIndex 注解的时候 从浏览器的输入localhost 8080 toIndex请求就可以