URL 和 HandlerMapping建立映射(11)
上一篇https://blog.csdn.net/chen_yao_kerr/article/details/130194864
我们已经分析了Spring MVC的配置,并且说明了如何通过注解的方式去替换各种各样的xml配置文件。本篇将更深入分析:
取代 springmvc.xml 配置
之前我们说过,定义一个类使用 @EnableWebMvc 注解开启Spring MVC的。 我们用一个@EnableWebMvc 就可以完全取代 xml 配置, 其实两者完成的工作是一样的,都是为了创建必要组件的实例。等同于在Spring mvc.xml文件中如下配置:
<!--默认的HandlerMapping和HandlerAdapter配置形式--><!-- 解决springMVC响应数据乱码 text/plain就是响应的时候原样返回数据--><mvc:annotation-driven><mvc:message-converters register-defaults="true"><bean class="org.springframework.http.converter.StringHttpMessageConverter"><property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" /></bean></mvc:message-converters></mvc:annotation-driven>
自定义的类:
package com.xiangxue.jack.mvc;import com.xiangxue.jack.interceptor.UserInterceptor;
import com.xiangxue.jack.interceptor.UserInterceptor1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;import java.util.List;@Configuration
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {//拦截器@Autowiredprivate UserInterceptor userInterceptor;@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {registry.enableContentNegotiation(new MappingJackson2JsonView());registry.jsp("/jsp/", ".jsp");}@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/view/ok").setViewName("ok");registry.addViewController("/view/index").setViewName("index");}//开启默认handlerMapping@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}//钩子方法的实现,添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userInterceptor).addPathPatterns("/user/").excludePathPatterns("/user/query/");registry.addInterceptor(new UserInterceptor1()).addPathPatterns("/user/").excludePathPatterns("");}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/image/").addResourceLocations("classpath:/img/");}@Overridepublic void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {super.configureHandlerExceptionResolvers(exceptionResolvers);}/* @Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/user/").allowedOrigins("*").allowCredentials(true).allowedMethods("GET", "POST", "DELETE", "PUT","PATCH").maxAge(3600);}*/
}
而@EnableWebMvc注解会import进来一个 DelegatingWebMvcConfiguration类,它是实现HandlerMapping的核心类:
而 DelegatingWebMvcConfiguration 继承了 WebMvcConfigurationSupport,在父类中有很多的@Bean方法, 这些方法完成很多组件的实例化, 比如 HandlerMapping, HandlerAdapter 等等。 如图:
然后在实例化过程中会涉及到很多钩子方法的调用, 而这些钩子方法就是我们需要去实现
的, 比如获取拦截器的钩子方法, 获取静态资源处理的钩子方法等等。这些方法都是在我们自定义的AppConfig类中实现的。这也解释了AppConfig 一大堆实现方法的原因。
AnnotationConfigWebApplicationContext上下文的2次启动
在我们完成ContextLoaderListener 和 DispatcherServlet 实例化过程的时候,我们分别为这2个类生成了AnnotationConfigWebApplicationContext上下文类。
这两个类是有调用先后顺序的,先进行ContextLoaderListener 的初始化并且启动上下文类;然后再进行DispatcherServlet 的初始化并且调用上下文类:
ContextLoaderListener :
DispatcherServlet :
这里需要重点分析一下DispatcherServlet 启动上下文的情况。
也就是说DispatcherServlet 中的上下文是子,ContextLoaderListener 中的上下文是父,他们是父子关系。
请求之前建立映射关系
1.ContextLoaderListener 启动上下文: 如果我们在扫描的时候,Spring能够扫描到含有@Controller、@RequestMapping注解的类,我们实例化@Controller、@RequestMapping类的时候,我们会把这些类的method 和 URL生成映射。
2. DispatcherServlet 启动上下文:如果Spring扫描不到@Controller、@RequestMapping注解的类, 而Spring MVC支持的类扫描到这些类,也会完成method 和 URL生成映射。
3. 无论是Spring建立映射关系,还是Spring MVC建立映射关系,底层代码的实现逻辑都是一样的。
建立映射关系流程
在我们实例化完 RequestMappingHandlerMapping 对象以后,我们最终会进入initializeBean 方法进行映射关系的调用,具体调用如下:
而 invokeInitMethods最终会调用到 RequestMappingHandlerMapping 对象的 afterPropertiesSet方法:
也就是说,在实例化RequestMappingHandlerMapping 对象以后,我们会对所有的候选BeanDefinition进行遍历
在 processCandidateBean方法内部,我们首先判断当前实例化的Bean对象是否有@Controller注解或者@RequestMapping注解,如果有的话就建立映射关系.
建立映射关系核心代码
接下来就进入了URL与Method的映射关系的核心流程,具体方法为 detectHandlerMethods
protected void detectHandlerMethods(Object handler) {Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {Class<?> userType = ClassUtils.getUserClass(handlerType);//获取方法对象和方法上面的@RequestMapping注解属性封装对象的映射关系Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {//回调方法,具体创建RequestMappingInfo的方法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));}methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);//建立uri和方法的各种映射关系,反正一条,根据uri要能够找到method对象registerHandlerMethod(handler, invocableMethod, mapping);});}}
大体思路分为7步:
1. 根据搜集到有@Controller注解或者@RequestMapping注解的类,通过反射获取到所有的方法,逐个找到有@RequestMapping注解的方法
2. 根据方法上的 URL 和 类上方的 URL 拼接处一个完整的URL。 比如:类上的URL为:@RequestMapping("/user"), 方法上的URL为 @RequestMapping("/queryUser"), 那么完整的URL为 /user/queryUser。
@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping("/queryUser")public @ResponseBody String queryUser() {return "jack";}
}
3. 把这个完整的URL封装成RequestMappingInfo对象
4. 把方法对应的Method对象 和 RequestMappingInfo对象放入map中,Method 为key, RequestMappingInfo 为 value
5. 遍历map, 根据Method 和 当前Controller类名,封装成唯一的 HandlerMethod对象,该类型封装了method, beanName, Bean, 方法类型等信息。
6. 遍历map, 将RequestMappingInfo作为key,HandlerMethod作为value,建立映射关系
7. 遍历map,将字符串 /user/queryUser作为key,RequestMappingInfo作为value,建立映射关系
5、6、7 步骤都是在遍历同一个map中完成的,这样就可以根据 字符串URL找到RequestMappingInfo,再根据RequestMappingInfo找到HandlerMethod,而HandlerMethod可以确定具体的方法。映射关系建立完毕。
下面对以上步骤逐步进行代码确认,下面这张图涉及到了前4步操作:
以上这张图涉及到了前4步流程,只是封装RequestMappingInfo对象涉及到回调,下面看一下具体回调到了哪个方法中:
5、6、7步都是在遍历map的时候进行的操作,看一下具体的遍历流程:
进入这个方法内部,最终会调到 register方法:
在这个方法内部,还涉及到 CrossOrigin注解的处理逻辑,而这个逻辑是和跨域访问相关的,后面单独分析,此处跳过。
至此,整个映射关系就建立起来了。