URL 和 HandlerMapping建立映射(11)

本文详细分析了如何使用@EnableWebMvc注解来替代传统的SpringMVCxml配置,包括拦截器的设置、视图解析、异常处理器等。同时,解释了DelegatingWebMvcConfiguration在处理HandlerMapping中的作用,以及ApplicationContext的两次启动过程,特别是DispatcherServlet和ContextLoaderListener之间的父子关系。文章还探讨了请求映射关系的建立流程,从扫描@Controller和@RequestMapping注解到建立URL与Method的映射关系。
摘要由CSDN通过智能技术生成

上一篇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 {

    //拦截器
    @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<HandlerExceptionResolver> 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的核心类:

 而 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注解的处理逻辑,而这个逻辑是和跨域访问相关的,后面单独分析,此处跳过。

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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值