目录
SpringMVC上下文如何初始化HandlerMapping
DispatcherServlet中如何获取HandlerMapping对象
前言
相信能看到此篇帖子的小伙伴们对SpringMVC的大致源码还是有了解,可能是来仔细阅读SpringMVC中每个组件的功能和底层源码实现。对于SpringMVC来说对URL地址中地址处理和地址对应的controller层面的类与接口的映射关系都是HandlerMapping来做处理。所以本篇帖子来解读SpringMVC启动时HandlerMapping组件对上下文的作用。
正文
很明确的说如今已经不是ssm框架的天下了,基本全是基于Spring boot微服务的天下了,但是呢SpringMVC对与Spring的整合和Spring boot的整合是有区别的,所以本帖对于两个整合都做以说明
基于Spring的HandlerMapping
SpringMVC上下文如何初始化HandlerMapping
对于SpringMVC来说,核心类就是DispatcherServlet类,任何请求都通过tomcat然后映射到此类,然后走doDispatch()方法,然后就是通过HandlerMapping找请求映射的类和类中的方法。所以说在整个Spring容器启动的时候,我们的controller层的类和方法都会被储存到HandlerMapping中,当请求过来的时候从HandlerMapping中找到映射的方法,然后包装成HandlerExecutionChain继续往下执行。所以我们目前要关心的是在spring容器启动的时候,我们controller层的类如何解析并添加到HandlerMapping中?
但是在这之前我们是不是要考虑HandlerMapping是在什么时候被创建的,我们知道整合Spring那就肯定是要注入到IoC容器中,注入到IoC容器中无非就是@Bean、或者注解或者是XML配置文件,而我们ssm整合是写了几个xml配置文件的,所以看到springMVC的XML配置文件中。
但是并没有看到HandlerMapping的身影。但是说
<mvc:annotation-driven />
会解析这个配置,这里会把RequestMappingHandlerMapping——HandlerMapping的实现类给解析给BeanDefinition,相信大家对BeanDefinition不陌生他就是一个bean对象的抽象实例。
我们再看到RequestMappingHandlerMapping的继承图
我们知道实现了InitializingBean接口是在Bean创建完实例并且执行完赋值操作才开始执行InitializingBean接口的实现方法
说了这么多,那我们具体看看afterPropertiesSet()方法做了一些什么事情。
由于RequestMappingHandlerMapping最后也是super.afterPropertiesSet(),所以我们看到父类
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
// 获取到全部的bean对象。
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
// 增强for循环遍历bean对象
for (String beanName : beanNames) {
// 查看Bean对象是否有特殊的代理,如果有特殊的代理会在bean对象的名字上加上前缀
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 获取到bean对象
Class<?> beanType = null;
try {
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);
}
}
// 判断bean对象是否实现了@Controller注解或者是@RequestMapping注解
// 很好理解这里就是要把业务中controller层的类加载到HandlerMapping中
if (beanType != null && isHandler(beanType)) {
// 加载逻辑,下面会具体讲解
detectHandlerMethods(beanName);
}
}
}
// 方法实现为null,方法的注释说是加载完所有的类和方法的一个回调方法
// 作用应该是给子类去实现重写,做一个加载完毕的回调把。
handlerMethodsInitialized(getHandlerMethods());
}
![](https://img-blog.csdnimg.cn/8ab09af1f6cd43eba31f39930ab19051.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5p2O5ZOI,size_20,color_FFFFFF,t_70,g_se,x_16)
这里也就是通过上下文容器获取到所有要加载进IoC容器中的bean实例,然后判断是否实现了@Controller注解或者是@RequestMapping注解,实现了就做下一步的操作。
detectHandlerMethods()方法的具体实现
protected void detectHandlerMethods(final Object handler) {
// 获取到bean对象的class实例
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 获取到类中的方法和RequestMappingInfo(具体的方法映射信息,url地址、请求方式等等...)
// 然后使用LinkedHashMap存储
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);
}
// 遍历方法
for (Map.Entry<Method, T> entry : methods.entrySet()) {
// 通过工具类对method方法做一个筛选,如果是private或者static修饰的方法就会直接抛出异常
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
// 获取到RequestMappingInfo对象
T mapping = entry.getValue();
// 添加到缓存中的具体逻辑了
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}
这里是给添加到缓存中的一个铺垫操作,解析类和方法,并且做一个包装,为添加到缓存做准备
registerHandlerMethod()方法具体实现
写这么多缓存,其实也就是证实了如今硬件设备强,大家开发都是空间换时间。
添加缓存中的逻辑就不细讲了,内部维护了一把读写锁来控制并发。
大家要注意一个问题,我们现在是一个方法栈,也就是说,前面的方法还在当前方法的下面,而当前方法都是在前面方法的for循环中。如图所示
![](https://img-blog.csdnimg.cn/41f32dbbd8fe4eb3b282a87a09c66067.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5p2O5ZOI,size_20,color_FFFFFF,t_70,g_se,x_16)
先是遍历类的for循环,再是类中方法的循环,是一个嵌套的循环!
DispatcherServlet中如何获取HandlerMapping对象
上面的步骤也就是把RequestMappingHandlerMapping给注入到IoC容器中,但是我们处理请求还是在DispatcherServlet中处理,那么肯定在DispatcherServlet中要获取到具体的HandlerMapping对象,所以接下来追寻一下怎么获取到RequestMappingHandlerMapping对象。
我们知道我们的请求都是先经过tomcat服务器,而对于servlet来说是懒加载机制,当第一个请求通过tomcat达到servlet控制器的后,servlet才开始进行一些初始化操作。
在DispatchServlet中定义了一个内部类来实现了Spring中的事件监听事件,还是容器上下文刷新完毕回调事件,所以这个事件是在哪里触发的呢?
没错就是在AbstractApplicationContext中的refresh()方法中的finishRefresh()方法中。
我们继续回到FrameworkServlet中
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 获取到IoC容器中所有HandlerMapping子类对象,其中就包括了RequestMappingHandlerMapping
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
// 将获取到的HandlerMapping子类对象赋值给DispatcherServlet中维护的全handlerMappings变量,是一个List集合。
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
// 这里就代表IoC中没有HandlerMapping子类对象,将手动创建一个放入IoC容器中。
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// 这里代表上面操作都没获取到HandlerMapping对象,这里将手动创建一个
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
看着onRefresh()方法是不是以为是IoC中refresh()方法中的OnRefresh()方法?很显然他欺骗了你。
通过上面的流程就在DispatcherServlet中获取到HandlerMapping对象的集合了,当请求到DispatcherServlet中运行doDispatch中会获取到RequestMappingHandlerMapping对象来做请求地址和缓存的一个查询匹对。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 解析HttpServletRequest中的路径,并且命中缓存
// 因为缓存中的HandlerMethod对象为了性能存的是String类型的,所以再根据getBean()取出实例再封装一个新的HandlerMethod对象。
Object handler = getHandlerInternal(request);
// 判空操作,如果没有命中缓存,就代表你的请求地址有问题,或者是tomcat启动的第一次默认请求
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 根据HandlerMethod对象new一个HandlerExecutionChain对象,并且还会获取到当前请求的拦截器
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
// 一些跨域的处理
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
这里就是解析请求,并且尝试获取到缓存中的值,最后获取到的值和拦截器对象一起封装成一个HandlerMethod对象返回为后面处理请求做铺垫。
基于Spring boot的HandlerMapping
我们知道对于Spring boot来说为开发者减少了很多配置项,当然肯定也减少了SpringMVC的配置,对于Spring boot来说自动配置遵循一个规定,那就是在jar包中的META-INF中定义一个spring.factory文件下,里面写好需要自动配置的类路径,让Spring boot来扫描并且添加到Spring容器中完成一个自动配置。
所以在Spring boot中会通过这种方式注入到IoC容器中,并且RequestMappingHandlerMapping他的父类是实现了InitializingBean接口。
这些流程跟Spring整合SpringMVC一模一样这里就不过多解释,也就是加载Bean对象赋值后的一个回调事件,然后项目中的标有@Controller注解和@RequestMapping注解的类和方法做一个简单的解析放入到MappingRegisty中的多个缓存中。来请求就从缓存中查询是否存在。
我们知道在Spring整合SpringMVC中,是需要开发者搭建一个tomcat服务器,而Spring的上下文容器需要通过tomcat服务器对Servlet初始化过程中才能进行refresh的(懒加载,需要用户发第一个请求时加载),而RequestMappingHandlerMapping等等SpringMVC的组件都是在Spring上下文刷新的时候注入到IoC容器中,在DispatcherServlet类中维护组件的初始化(就是从IoC容器获取)都是在Spring上下文刷新完毕的监听事件中执行的。而对于Spring boot来说内置了一个tomcat服务器,是Spring boot实现的Spring上下文刷新的时候对其内置的Tomcat做一个初始化。两者是相反的过程。所以RequestMappingHandlerMapping等等SpringMVC的组件都是在上下文刷新的过程中就加入到IoC容器中。但是对于DispatcherServlet中维护的组件初始化还是需要内置的tomcat来进行初始化,所以也是一个懒加载,需要用户发第一个请求时对其进行一个初始化工作,其实不过是Spring整合还是Spring boot整合都是需要通过tomcat来进行一定的初始化工作。
在看initWebApplicationContext()方法之前,我们看一定下FrameworkServlet的继承和实现关系,他实现了ApplicationContextAware接口,所以可以得到了ApplicationContext容器上下文。
最后就回到了DispatcherServlet的onRefresh()方法来对DispatcherServlet中维护的SpringMVC的组件进行初始化操作(也就是从IoC容器中获取)。当请求过来时走doDispatch()方法就可以获取到SpringMVC的各种组件进行工作了。
总结
总体难度不大,就是要区分Spring和Spring boot两者的初始化过程的区别。
最后如果本帖对您有帮助希望点赞+关注,一直在更新各种框架的源码帖,您的支持就是给我最大的支持!~