Spring MVC原理(源码解析)

 

 

Spring是如何找到URL请求对应的Controller的。

重要概念:

1.RequestMappingInfo 这个类是对请求映射的一个抽象,它包含了请求路径,请求方法,请求头等信息。其实可以看做是@RequestMapping的一个对应类。

2. HandlerMethod这个类封装了处理器实例(Controller Bean)和 处理方法实例(Method)以及方法参数数组(MethodParameter[])

3. MethodParameter  这个类从2.0就有了,它封装了方法某个参数的相关信息及行为,如该参数的索引,该参数所属方法实例或构造器实例,该参数的类型等。

4. HandlerMapping 该接口的实现类用来定义请求和处理器之前的映射关系,其中只定义了一个方法getHandler。

5. AbstractHandlerMethodMapping 这是HandlerMapping的一个基本实现类,该类定义了请求与HandlerMethod实例的映射关系。

6. RequestMappingInfoHandlerMapping这个是AbstractHandlerMethodMapping的实现类,他维护了一个RequestMappingInfo和HandlerMethod的Map属性。

7. RequestMappingHandlerMapping这个是RequestMappingInfoHandlerMapping的子类,它将@RequestMapping注解转化为RequestMappingInfo实例,并为父类使用。也就是我们处理@RequestMapping的终点。

8. InitializingBean这个接口定义了其实现Bean在容器完成属性设置后可以执行自定义初始化操作,我们的AbstractHandlerMethodMapping便实现了这个接口,并且定义了一组自定义操作,就是用来检测处理我们的@RequestMapping注解。

9. HandlerExecutionChain处理器执行链,由处理器对象和拦截器组成

入口DispatcherServlet.doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;//处理器执行器链
   boolean multipartRequestParsed = false;
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   try {
      ModelAndView mv = null;
      Exception dispatchException = null;
      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);
         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);//重点
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (logger.isDebugEnabled()) {
               logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
            }
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }
         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }
         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

我们重点看一下getHandler(processedRequest);

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping hm : this.handlerMappings) {
         if (logger.isTraceEnabled()) {
            logger.trace(
                  "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
         }
         HandlerExecutionChain handler = hm.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

这个方法的主要所用是循环遍历内存中的所有HandlerMapping返回一个HandlerExecutionChain

hm.getHandler(request)在HandlerMapping中,具体实现在AbstractHandlerMaping

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   Object handler = getHandlerInternal(request);
   if (handler == null) {
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }
   //获取handler的执行链,主要是把拦截器的执行也构建在了这个方法里面
   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;
}

getHandlerInternal(request) 具体实现在AbstractHandlerMethodMapping

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   //获取请求url
   String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
   if (logger.isDebugEnabled()) {
      logger.debug("Looking up handler method for path " + lookupPath);
   }
   //上锁(此类中的map线程不安全)
   this.mappingRegistry.acquireReadLock();
   try {
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
      if (logger.isDebugEnabled()) {
         if (handlerMethod != null) {
            logger.debug("Returning handler method [" + handlerMethod + "]");
         }
         else {
            logger.debug("Did not find handler method for [" + lookupPath + "]");
         }
      }
      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
   }
   finally {
      this.mappingRegistry.releaseReadLock();
   }
}

此方法根据request获取lookupPath在去调用lookupHandlerMethod(lookupPath, request)获取具体的HandlerMethod

我们再看看lookupHandlerMethod(lookupPath, request)

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   // 通过URL,从对应的映射map里面获取requestMappingInfo
   List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   if (directPathMatches != null) {
      // 对requestMappingInfo中的各种条件做匹配,如果匹配对了,则放入matches
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      //没有匹配到,则遍历所有的requestMappingInfo
      addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
   }
   if (!matches.isEmpty()) {
      // 比较找到的matches , 通过requestMappingInfo中的compareTo进行排序
      Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
      matches.sort(comparator);
      if (logger.isTraceEnabled()) {
         logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
      }
      // 获取匹配度最高的
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
         if (CorsUtils.isPreFlightRequest(request)) {
            return PREFLIGHT_AMBIGUOUS_MATCH;
         }
         Match secondBestMatch = matches.get(1);
         if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            Method m1 = bestMatch.handlerMethod.getMethod();
            Method m2 = secondBestMatch.handlerMethod.getMethod();
            throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                  request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
         }
      }
      request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
   }
   else {
      return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
   }
}

它定义了一个matchs的列表,然后内存变量mappingRegistry根据lookupPath获取所有的匹配对象。如果存在这个匹配对象,那么回调用addMatchingMappings方法将匹配的对象映射到matches上。如果未匹配到这个lookupPath,那么则会循环mappingRegistry中每个Mapping来匹配该路径(因为RequestMapping中定义的路径可以有通配符)。

getMappingsByUrl(String urlPath)

public List<T> getMappingsByUrl(String urlPath) {
   return this.urlLookup.get(urlPath);
}

我们看一下 addMatchingMappings方法

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
   for (T mapping : mappings) {
      T match = getMatchingMapping(mapping, request);
      if (match != null) {
         matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
      }
   }
}
RequestMappingInfoHandlerMapping这个class继承了这个抽象类,泛型类型为:RequestMappingInfo。
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
   return info.getMatchingCondition(request);
}

判断method params header consumes等是否相同
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
   RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
   ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
   HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
   ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
   ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
   if (methods == null || params == null || headers == null || consumes == null || produces == null) {
      return null;
   }
   PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
   if (patterns == null) {
      return null;
   }
   RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
   if (custom == null) {
      return null;
   }
   return new RequestMappingInfo(this.name, patterns,
         methods, params, headers, consumes, produces, custom.getCondition());
}

Q1:这个mappingRegistry变量是何物,什么时候将Mapping结果保存的?

class MappingRegistry {

   private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
   // requestMappingInfo 和HandlerMethod的对应关系
   private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
   // url 和requestMappingInfo的对应关系
   private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
   // name和HandlerMethod的关系
   private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
   // 方法和跨域信息的映射
   private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
   private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

 MappingRegistry是AbstractHandlerMethodMapping的一个内部类

当ioc容器初始化的时候会调用initHandlerMethods

public void afterPropertiesSet() {
   initHandlerMethods();
}

我们再看看initHandlerMethods();

protected void initHandlerMethods() {
   if (logger.isDebugEnabled()) {
      logger.debug("Looking for request mappings in application context: " + getApplicationContext());
   }
   //获取spring容器里的所有beanName
   String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
         BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
         obtainApplicationContext().getBeanNamesForType(Object.class));
   for (String beanName : beanNames) {
      //判断是否以约定头开始; 如果是scopedTarget.这个开头的bean, 则不做处理。
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         Class<?> beanType = null;
         try {
            beanType = obtainApplicationContext().getType(beanName);//获取bean
         }
         catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
               logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
            }
         }
         //判断这些bean是否需要处理
         if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
         }
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}

这个方法的作用就是得到IoC容器里面所有的BeanName然后去判断这个bean是否需要处理判断的标准是isHandler(),点进去可以看到判断的标准是(是否有@RequestMapping注解或者有@Controller注解)

detectHandlerMethods(beanName);处理bean

protected void detectHandlerMethods(Object handler) {
   //获得类 (判断handler是字符串还是对象,如果是字符串则从容器拿)
   Class<?> handlerType = (handler instanceof String ?
         obtainApplicationContext().getType((String) handler) : handler.getClass());
   if (handlerType != null) {
      //返回给定类的用户定义类:通常返回给定类,但如果是动态代理机制的情况下返回原始类(父类)。
      Class<?> userType = ClassUtils.getUserClass(handlerType);
      //进行方法筛选
      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);
      }
      //对检测到的Method进行缓存注册
      //mapping是指RequestMappingInfo
      //method是url对应的方法包括入参返回值等
      methods.forEach((method, mapping) -> {
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
         registerHandlerMethod(handler, invocableMethod, mapping);
      });
   }
}

返回给用户类时有两种方法一种是jvm动态

  1. 如果目标对象实现了接口,默认采用JDK的动态代理实现AOP,当然也可以强制使用CGLIB实现
  2. 如果对象没有实现接口,必须采用CGLIB实现

在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

  1. 使用JDK动态代理,因为有接口,所以是系统更加松耦合,缺点为每一个目标类创建接口

使用CGLIB进行动态代理,因为代理类与目标类是继承关系,所以不需要接口的存在,但因为没有使用接口,所以系统的耦合性较差。

进行方法筛选getMappingForMethod(method, userType);在RequestMappingHandlerMapping中

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
   //创建RequestMappingInfo(方法)
   RequestMappingInfo info = createRequestMappingInfo(method);
   if (info != null) {
      //创建RequestMappingInfo(类)
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if (typeInfo != null) {
         //将类级请求映射和方法级请求映射进行组合
         info = typeInfo.combine(info);
      }
   }
   return info;
}

继续看将信息注册达到缓存中

registerHandlerMethod(handler, invocableMethod, mapping);

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
   this.mappingRegistry.register(mapping, handler, method);
}

public void register(T mapping, Object handler, Method method) {
   this.readWriteLock.writeLock().lock();
   try {
      HandlerMethod handlerMethod = createHandlerMethod(handler, method);//构建HandlerMethod
      assertUniqueMethodMapping(handlerMethod, mapping);
      // 将requestMappingInfo为键,handlerMethod为value,放入linkedHashMap
      this.mappingLookup.put(mapping, handlerMethod);
      if (logger.isInfoEnabled()) {
         logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
      }
      List<String> directUrls = getDirectUrls(mapping);// 获取URL地址
      // 获得url映射路径,将映射路径和匹配条件对象RequestMappingInfo存起来
      for (String url : directUrls) {
         //urlLookup是Map<K, List<V>> 一个url会对应多个RequestMappingInfo
         this.urlLookup.add(url, mapping);
         //为什么说一个url会对应多个requestMappingInfo
         //同一个方法可能是url可能是post请求 也可能是别的请求(例如GET,DELETE)
         //一个参数的改变就是导致RequestMappingInfo的不同所以要存储成一对多的形式
         /**
          * add()将给定的单个值添加到给定键的当前值列表中
          * urlLookup是一个LinkedMultiValueMap  private final Map<K, List<V>> targetMap;
          * add()方法采用的是jdk1.8 以后map新增方法computeIfAbsent()
          * 如果当前map中可以找到这个key对应的List则将这个值添加到已有的List中
          * 如果当前map中找不到这个key对应的List则新new一个List并将这个值添加到List中
          * /add/user/         requestMappingInfo
          */


      }

      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<>(mapping, handlerMethod, directUrls, name));
   }
   finally {
      this.readWriteLock.writeLock().unlock();
   }
}

注意:当我们在Controller里面两个方法的RequestMapping里面设置的属性完全一样时,当项目启动时会报

Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'employeeController' method这个错误

源码是怎么处理的?

在注册缓存时会将requestMappingInfo为键,handlerMethod为value,放入mappingLookup(linkedHashMap)在存放的时候会通过assertUniqueMethodMapping()方法进行判断

private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
   HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
   if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
      throw new IllegalStateException(
            "Ambiguous mapping. Cannot map '" +    newHandlerMethod.getBean() + "' method \n" +
            newHandlerMethod + "\nto " + mapping + ": There is already '" +
            handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
   }
}

会先通过RequestMapping去查询是否存在对应的HandlerMethod,如果存在会与要存的HandlerMethod进行比较,如果相同会报错

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值