URL 和 HandlerMapping建立映射(11)

URL 和 HandlerMapping建立映射(11)_第1张图片

上一篇https://blog.csdn.net/chen_yao_kerr/article/details/130194864

我们已经分析了Spring MVC的配置,并且说明了如何通过注解的方式去替换各种各样的xml配置文件。本篇将更深入分析:

取代 springmvc.xml 配置

之前我们说过,定义一个类使用 @EnableWebMvc 注解开启Spring MVC的。 我们用一个@EnableWebMvc 就可以完全取代 xml 配置, 其实两者完成的工作是一样的,都是为了创建必要组件的实例。等同于在Spring mvc.xml文件中如下配置:


    
    
        
            
                
            
        
    

 自定义的类:

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 {

    //拦截器
    @Autowired
    private UserInterceptor userInterceptor;

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp("/jsp/", ".jsp");
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/view/ok").setViewName("ok");
        registry.addViewController("/view/index").setViewName("index");
    }

    //开启默认handlerMapping
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //钩子方法的实现,添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor).addPathPatterns("/user/**").excludePathPatterns("/user/query/**");
        registry.addInterceptor(new UserInterceptor1()).addPathPatterns("/user/**").excludePathPatterns("");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/image/**")
                .addResourceLocations("classpath:/img/");
    }

    @Override
    public void configureHandlerExceptionResolvers(List exceptionResolvers) {
        super.configureHandlerExceptionResolvers(exceptionResolvers);
    }


/*    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/user/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT","PATCH")
                .maxAge(3600);
    }*/
}

而@EnableWebMvc注解会import进来一个 DelegatingWebMvcConfiguration类,它是实现HandlerMapping的核心类:

URL 和 HandlerMapping建立映射(11)_第2张图片

 而 DelegatingWebMvcConfiguration 继承了 WebMvcConfigurationSupport,在父类中有很多的@Bean方法, 这些方法完成很多组件的实例化, 比如 HandlerMapping, HandlerAdapter 等等。 如图:
URL 和 HandlerMapping建立映射(11)_第3张图片

URL 和 HandlerMapping建立映射(11)_第4张图片

然后在实例化过程中会涉及到很多钩子方法的调用, 而这些钩子方法就是我们需要去实现
的, 比如获取拦截器的钩子方法, 获取静态资源处理的钩子方法等等。这些方法都是在我们自定义的AppConfig类中实现的。这也解释了AppConfig 一大堆实现方法的原因。

AnnotationConfigWebApplicationContext上下文的2次启动

在我们完成ContextLoaderListener 和 DispatcherServlet 实例化过程的时候,我们分别为这2个类生成了AnnotationConfigWebApplicationContext上下文类。

这两个类是有调用先后顺序的,先进行ContextLoaderListener 的初始化并且启动上下文类;然后再进行DispatcherServlet 的初始化并且调用上下文类:

ContextLoaderListener :

URL 和 HandlerMapping建立映射(11)_第5张图片

URL 和 HandlerMapping建立映射(11)_第6张图片

 DispatcherServlet 

URL 和 HandlerMapping建立映射(11)_第7张图片

 URL 和 HandlerMapping建立映射(11)_第8张图片

URL 和 HandlerMapping建立映射(11)_第9张图片

这里需要重点分析一下DispatcherServlet 启动上下文的情况。

URL 和 HandlerMapping建立映射(11)_第10张图片

 也就是说DispatcherServlet 中的上下文是子,ContextLoaderListener 中的上下文是父,他们是父子关系。

 请求之前建立映射关系

1.ContextLoaderListener 启动上下文: 如果我们在扫描的时候,Spring能够扫描到含有@Controller、@RequestMapping注解的类,我们实例化@Controller、@RequestMapping类的时候,我们会把这些类的method 和 URL生成映射。

URL 和 HandlerMapping建立映射(11)_第11张图片

2. DispatcherServlet 启动上下文:如果Spring扫描不到@Controller、@RequestMapping注解的类, 而Spring MVC支持的类扫描到这些类,也会完成method 和 URL生成映射。

URL 和 HandlerMapping建立映射(11)_第12张图片

3. 无论是Spring建立映射关系,还是Spring MVC建立映射关系,底层代码的实现逻辑都是一样的。

建立映射关系流程

在我们实例化完 RequestMappingHandlerMapping 对象以后,我们最终会进入initializeBean 方法进行映射关系的调用,具体调用如下:

URL 和 HandlerMapping建立映射(11)_第13张图片

 而 invokeInitMethods最终会调用到 RequestMappingHandlerMapping 对象的 afterPropertiesSet方法:

URL 和 HandlerMapping建立映射(11)_第14张图片

也就是说,在实例化RequestMappingHandlerMapping 对象以后,我们会对所有的候选BeanDefinition进行遍历

URL 和 HandlerMapping建立映射(11)_第15张图片 

在 processCandidateBean方法内部,我们首先判断当前实例化的Bean对象是否有@Controller注解或者@RequestMapping注解,如果有的话就建立映射关系.

URL 和 HandlerMapping建立映射(11)_第16张图片

 建立映射关系核心代码

接下来就进入了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 methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup) 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步操作:

URL 和 HandlerMapping建立映射(11)_第17张图片

 

以上这张图涉及到了前4步流程,只是封装RequestMappingInfo对象涉及到回调,下面看一下具体回调到了哪个方法中:

URL 和 HandlerMapping建立映射(11)_第18张图片

 URL 和 HandlerMapping建立映射(11)_第19张图片

 

5、6、7步都是在遍历map的时候进行的操作,看一下具体的遍历流程:

URL 和 HandlerMapping建立映射(11)_第20张图片

进入这个方法内部,最终会调到 register方法:

URL 和 HandlerMapping建立映射(11)_第21张图片

 

在这个方法内部,还涉及到 CrossOrigin注解的处理逻辑,而这个逻辑是和跨域访问相关的,后面单独分析,此处跳过。

至此,整个映射关系就建立起来了。

 

 

你可能感兴趣的:(Spring源码,spring,mvc,java)