spring mvc 是怎么实现的? 为什么我们只需要在方法上写一个注解, 就可以通过 http 访问这个接口? 下面我们分 3 部分来解答这两个问题
注意: 本文是基于 spring4.3.2 的
spring mvc 整体流程
- HandlerMapping
- HandlerAdapter
spring mvc 整体流程
我们通过看一下 spring 处理一个 http 请求的过程来大概了解下
Spring mvc 的入口就是 DispatcherServlet, 请求交给这个 servlet 之后, 通过调用 doDispatch 来分发这个请求, 主要做了一下几件事情
处理 multipart 请求, 如果有文件上传等请求, 先封装为 DefaultMultipartHttpServletRequest
从 HandlerMapping 中获取处理该请求的 handler, 并构造 HandlerExecutionChain, 将入所有的 interceptor
根据 handler 从所有的 HandlerAdapter 中找到可以处理这个 handler 的 adapter
执行所有 interceptor.prehandle 方法
调用写有 RequestMapping 注解的方法, 返回 ModelAndView
执行所有 interceptor.postHandle 方法
解析 view
render, 将 model 转化为 response 返回
执行所有 interceptor.afterCompletion
cleanupMultipart
从上面的流程中可以看出, 在处理过程中主要是一些关键组件完成了对应的逻辑, 下面我们看下其中的组件.
HandlerMapping
作为 DispatcherServlet 组件之一, 就是其中一个 field
org.springframework.web.servlet.DispatcherServlet#handlerMappings
主要作用是维护从 url 到 Controller 的映射, 根据 url 找到对应的 Controller.
有哪些 HandlerMapping
spring 是怎么查找所有的 HandlerMapping 的呢? 是在 DispatcherServlet 初始化的时候查找并初始化这个字段的
- // org.springframework.Web.servlet.DispatcherServlet#initHandlerMappings
- private void initHandlerMappings(ApplicationContext context) {
- this.handlerMappings = null;
- if (this.detectAllHandlerMappings) {
- // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
- // 从容器中查找所有 HandlerMapping 类型的 bean
- Map<String, HandlerMapping> matchingBeans =
- BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
- if (!matchingBeans.isEmpty()) {
- this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
- // We keep HandlerMappings in sorted order.
- AnnotationAwareOrderComparator.sort(this.handlerMappings);
- }
- }
- else {
- 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.
- }
- }
- // Ensure we have at least one HandlerMapping, by registering
- // a default HandlerMapping if no other mappings are found.
- if (this.handlerMappings == null) {
- this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
- if (logger.isDebugEnabled()) {
- logger.debug("No HandlerMappings found in servlet'" + getServletName() + "': using default");
- }
- }
- }
那么容器中有哪些 HandlerMapping 类型的 bean 呢? 如果我们使用 RequestMapping 注解的话需要在 xml 中进行以下配置
<mvc:annotation-driven />
spring 解析这个标签使用的是
org.springframework.Web.servlet.config.AnnotationDrivenBeanDefinitionParser
这个类解析 xml 标签的时候会向容器中注册 RequestMappingHandlerMapping, 这个类继承自 HandlerMapping
如果我们进行了一下配置
<mvc:default-servlet-handler />
spring 解析这个标签的使用的是
org.springframework.Web.servlet.config.DefaultServletHandlerBeanDefinitionParser
解析的时候 spring 会向容器注入 DefaultServletHttpRequestHandler 和 SimpleUrlHandlerMapping
那么
- BeanNameHandlerMapping
- DefaultAnnotationHandlerMapping
所以这种情况下会有这三个 HandlerMapping 类型的 bean,spring 遍历 handlerMappings 来根据 request path 查找对应的 handler.
HandlerMapping 怎么管理 url 到 handler 的映射关系
HandlerMapping 抽象类 AbstractHandlerMethodMapping 中有一个 field
- // org.springframework.Web.servlet.handler.AbstractHandlerMethodMapping
- private final MappingRegistry mappingRegistry = new MappingRegistry();
这个类就是用来维护 url 到 handler 的映射, 看下这个类有哪些数据结构
- // org.springframework.Web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry
- // 所有的 mapping 都会加入这个 map 中
- private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
- // 只有具体的 (没有通配符 org.springframework.util.AntPathMatcher#isPattern) 的才会加入这个 map 中
- private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
- // 根据 name 找 handler
- private final Map<String, List<HandlerMethod>> nameLookup =
- new ConcurrentHashMap<String, List<HandlerMethod>>();
- // cors mapping
- private final Map<HandlerMethod, CorsConfiguration> corsLookup =
注册 mapping 的过程就是讲找到的 mapping 添加到上面对应的数据结构中, 以 RequestMappingHandlerMapping 为例, 具体注册的时机是在 RequestMappingHandlerMapping bean 初始化的时候, spring 容器初始化完成以后会将容器中 eagere init 的 bean 进行初始化, 构造 bean 的时候会调用 afterPropertiesSet, 最后会调用 AbstractHandlerMethodMapping#initHandlerMethods 来查找容器中所有的 bean
找到容器中所有的 bean
针对每个 bean 查找里面有没有 mapping
在调用到 initHandlerMethods 方法的时候会拿出容器中所有的 bean, 用 isHandler 判断是否是 handler, 其实就是判断是否有 Controller 或 RequestMapping 的注解
- protected boolean isHandler(Class<?> beanType) {
- return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
- AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
- }
找到类后, 再找里面的方法, 方法的查找逻辑就是看看方法上有没有 RequestMapping,
- private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
- // 查找方法上的 RequestMapping 注解
- RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
- RequestCondition<?> condition = (element instanceof Class<?> ?
- getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
- return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
- }
找到对应的方法之后构造成 mapping 注册到 MappingRegistry
- public void register(T mapping, Object handler, Method method) {
- this.readWriteLock.writeLock().lock();
- try {
- HandlerMethod handlerMethod = createHandlerMethod(handler, method);
- assertUniqueMethodMapping(handlerMethod, mapping);
- if (logger.isInfoEnabled()) {
- logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
- }
- // 所有的 mapping 都会加入 mappingLookup
- this.mappingLookup.put(mapping, handlerMethod);
- List<String> directUrls = getDirectUrls(mapping);
- // 找到所有具体的 (直接的, 就是不包含通配符) 的 url 添加到 urlLookup
- for (String url : directUrls) {
- this.urlLookup.add(url, mapping);
- }
- String name = null;
- if (getNamingStrategy() != null) {
- name = getNamingStrategy().getName(handlerMethod, mapping);
- addMappingName(name, handlerMethod);
- }
- CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
- if (corsConfig != null) {
- this.corsLookup.put(handlerMethod, corsConfig);
- }
- this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
- }
- finally {
- this.readWriteLock.writeLock().unlock();
- }
- }
在根据 url 查找 handler 的时候优先查找 urlLookup, 看看没有通配符的能不能匹配, 如果没有直接匹配的则再从 mappingLookup 里面找
HandlerAdapter
HandlerAdapter 是为了将 invoke 过程的细节对于 DispatcherServlet 屏蔽, 比如参数解析.
在 DispatcherServlet 初始化的时候调用 inithandlerAdapters, 在里面找容器中所有的 handlerAdapter 实现类, 容器里面的 handlerAdapter 是在解析 mvc 标签的时候加入容器的
总结
这篇文章通过 spring 处理一次请求的过程了解了 springmvc 怎么工作, 以及其中两个关键组件, HandlerMapping 和 HandlerAdapter.
来源: https://www.cnblogs.com/sunshine-2015/p/10555748.html