• HandlerMapping 详解


    HandlerMapping 详解

    1. 导言

    万丈高楼平地起,SpringMVC的辉煌离不开每个组件的相互协作,上一章详细阐述了SpringMVC整个体系结构及实现原理,知道HandlerMapping在这个SpringMVC体系结构中有着举足轻重的地位,充当着url和Controller之间映射关系配置的角色。主要有三部分组成:HandlerMapping映射注册、根据url获取对应的处理器、拦截器注册。本文将立足于RequestMappingHandlerMapping详细阐述HandlerMapping的整个体系。其结构如图所示。
    HandlerMapping体系结构
    笔者可以以不同颜色表示三大主要过程,下面笔者将逐步分析RequestMappingHandlerMapping的整个体系。

    2. 检测方法,构造RequestHandlerInfo映射集合

    • AbstractHandlerMethodMapping一个并不陌生的方法,afterPropertiesSet()
      注意AbstractHandlerMethodMapping继承自InitializingBean,会在Bean初始化完成后调用afterPropertiesSet()方法
    @Override
    public void afterPropertiesSet() {        
    	initHandlerMethods();  
    }
    

    initHandlerMethods的实现如下图所示:
    initHandlerMethods实现
    判断beanType是否是满足要求的handler和检测并生存handlerMethod是最为关键的两个过程。其中判断是否满足要求的handler,实现如下:

    protected abstract boolean isHandler(Class<?> beanType);
    
    @Override
    protected boolean isHandler(Class<?> beanType) {
      return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
          AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
    

    注意到,isHandler方法是一个抽象方法,在父类不能确定如何实现,这边将具体的实现交子类来进行,在 RequestMappingHandlerMapping中的实现为只要有@Controller注解或者@RequestMapping注解的均为满足要求的handler。
    检测HandlerMethods是在detectHandlerMethods方法中实现的,其几个关键的类、接口及方法实现如图所示:
    selectMethods关键类
    detectHandlerMethods的实现序列图如图所示
    detectHandlerMethods时序图

    • what?selectMethods感觉好凌乱,这么复杂,是否有跟笔者一样的想法?
      selectMethods其实是两个命令模式的变体的叠加。笔者看来每个设计模式都有多种变体,重要的是理解每个设计模式解决的问题。命令模式的主要目的是为了将触发和命令的具体实现解耦,以实现触发命令操作和具体的命令的实现相互隔离。当命令触发时,命令对象就会执行操作,这是java事件的处理方式。java中典型的命令模式,就是多线程的start方法和Runnable的run方法,相信读者并不会陌生。
    Thread thread = new Thread(new Runnable(){
        @Override
        public void run(){
          log.info("简单的测试");
        }
    });
    ...
    thread.start();
    

    首先传入一个命令对象,这个命令(run方法)并不会立马执行,会在事件触发后才会调用命令(start方法),但在什么时候触发事件,在传入命令对象的时候,我们并不关心,也没办法知道如何触发事件。
    简单解释了命令模式,解决的问题,现在回到主题,selectMethods是怎么实现的?
    第一个命令模式:

    public interface MetadataLookup<T> {
       	/**
       	 * Perform a lookup on the given method and return associated metadata, if any.
       	 * @param method the method to inspect
       	 * @return non-null metadata to be associated with a method if there is a match,
       	 * or {@code null} for no match
       	 */
       	T inspect(Method method);
       }
    
    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
       			new MethodIntrospector.MetadataLookup<T>() {
       				@Override
       				public T inspect(Method method) {
       					try {
       						return getMappingForMethod(method, userType);
       					}
       					catch (Throwable ex) {
       						throw new IllegalStateException("Invalid mapping on handler class [" +
       								userType.getName() + "]: " + method, ex);
       					}
       				}
       			});
    

    传入一个命令,MetadataLookup的实现,在selectMethods方法内部会调用对象的inspect方法。(实际上是在第二命令中调用的这个命令)。
    第二个命令模式:

    public interface MethodCallback {
      /**
       * Perform an operation using the given method.
       * @param method the method to operate on
       */
      void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
    }
    
    for (Class<?> currentHandlerType : handlerTypes) {
       		final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
       		ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
       			@Override
       			public void doWith(Method method) {
       				Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
       				T result = metadataLookup.inspect(specificMethod);
       				if (result != null) {
       					Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
       					if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
       						methodMap.put(specificMethod, result);
       					}
       				}
       			}
       		}, ReflectionUtils.USER_DECLARED_METHODS);
       	}
    

    传入一个命令,MethodCallback的实现,在doWithMethods方法内部会调用对象的dowith方法方法。

    • 再谈selectMethods实现
      第一个命令模式,即selectMethods方法中,
      (1)首先选择所有HandlerType的所有继承体系的所有class:
      final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
    		Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
    		Class<?> specificHandlerType = null;
    		if (!Proxy.isProxyClass(targetType)) {
    			handlerTypes.add(targetType);
    			specificHandlerType = targetType;
    		}
    		handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));
    

    (2)遍历每一个handlerType
    (3)选择每一个满足要求的方法,执行dowith方法

    public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
      // Keep backing up the inheritance hierarchy.
      Method[] methods = getDeclaredMethods(clazz);
      for (Method method : methods) {
        if (mf != null && !mf.matches(method)) {
          continue;
        }
        try {
          mc.doWith(method);
        }
        catch (IllegalAccessException ex) {
          throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
        }
      }
      if (clazz.getSuperclass() != null) {
        doWithMethods(clazz.getSuperclass(), mc, mf);
      }
      else if (clazz.isInterface()) {
        for (Class<?> superIfc : clazz.getInterfaces()) {
          doWithMethods(superIfc, mc, mf);
        }
      }
    }
    
    public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() {
      //只选择用户定义的方法,Object方法和代理方法不满则需求
      @Override
      public boolean matches(Method method) {
        return (!method.isBridge() && method.getDeclaringClass() != Object.class);
      }
    };
    

    (4)针对每一个method调用metadataLookup的dowith方法,以{method,result}的形式缓存:

    public void doWith(Method method) {
    		Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
        T result = metadataLookup.inspect(specificMethod);
    		if (result != null) {
    			Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
    			if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
    				methodMap.put(specificMethod, result);
    			}
    		}
    }
    

    (5)重头戏,inspect方法

    public T inspect(Method method) {
    	try {
      		return getMappingForMethod(method, userType);
    			}
    			catch (Throwable ex) {
    				throw new IllegalStateException("Invalid mapping on handler class [" +
    									userType.getName() + "]: " + method, ex);
    			}
    }
    

    其关键之关键为getMappingForMethod,首先会读取方法上的@RequestMapping注解,然今读取类上面的注解,最后进行联合操作。

    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
      RequestMappingInfo info = createRequestMappingInfo(method);
      if (info != null) {
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
          info = typeInfo.combine(info);
        }
      }
      return info;
    }
    

    (6)注册RequestMappingInfo

    Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
    T mapping = entry.getValue();
    registerHandlerMethod(handler, invocableMethod, mapping);
    

    registerHandlerMethod会调用MappingRegistry的registry方法,其实现流程如图所示
    registerHandlerMethod实现流程
    这个过程主要针对HandlerMethod做了一些缓存,方便查询,根据url,name,mapping均做了相应缓存,主要是为了优化查询handlerMethod的性能。

    3. getHandler方法,获取执行器链。

    • 获取执行器链入口:
    mappedHandler = getHandler(processedRequest);
    
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		for (HandlerMapping hm : this.handlerMappings) {
    			if (logger.isTraceEnabled()) {
    				logger.trace("Testing handler map [" + hm + "] DispatcherServlet with name '" + getServletName() + "'");
    			}
    			HandlerExecutionChain handler = hm.getHandler(request);
    			if (handler != null) {
    				return handler;
    			}
    		}
    		return null;
    	}
    

    遍历配置的handlerMappings,依次调用getHandler方法,只要找到满足要求的handlerMapping,立马返回。

    • HandlerMapping的getHandler方法:
      getHandler时序图
      查找到匹配项后,handlerMethod做一些处理,RequestHandlerMethodMapping是会将相关内容缓存在request域中,当然,使用的时候也可以定制一些内容。笔者猜想,这些都是为了性能提升而努力的,毕竟性能提升在每一小步。
      构造执行器链,执行器链中包含HandlerMethod和相关拦截器,同时包含有跨域的解决方案。
    	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
    				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
    			if (interceptor instanceof MappedInterceptor) {
    				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
    				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
    					chain.addInterceptor(mappedInterceptor.getInterceptor());
    				}
    			}
    			else {
    				chain.addInterceptor(interceptor);
    			}
    		}
    		return chain;
    	}
    

    4. 再谈拦截器

    从上一节的代码可以看出,拦截器至少包含两种,实现MappedInterceptor和实现普通HandlerInterceptor接口的类。
    interceptor接口
    普通handler接口,会直接加入到拦截器链中,而MappedInterceptor则只会加入matches方法返回true的拦截器。
    至此HandlerMapping已分析完毕,SpringMVC的其它内容也将陆续推出。

  • 相关阅读:
    别用言语考验底线
    QQ登陆接口
    cookie设置今日不提醒功能
    Sublime Text 3常用插件
    浏览器的标准模式与怪异模式的设置与区分方法
    JS中offsetTop、clientTop、scrollTop、offsetTop各属性介绍
    javascript中闭包的工作原理
    javascript判断图片是否加载完成方法整理
    社会十二效应法则
    小故事
  • 原文地址:https://www.cnblogs.com/dragonfei/p/6148625.html
Copyright © 2020-2023  润新知