
上一篇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注解的处理逻辑,而这个逻辑是和跨域访问相关的,后面单独分析,此处跳过。
至此,整个映射关系就建立起来了。







![[TIFS 2022] FLCert:可证明安全的联邦学习免受中毒攻击](https://img-blog.csdnimg.cn/ef73634b20104948a4ff91975251f2ad.png)











