zoukankan      html  css  js  c++  java
  • 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的其它内容也将陆续推出。

  • 相关阅读:
    js正则表达式中的问号使用技巧总结
    380. Insert Delete GetRandom O(1)
    34. Find First and Last Position of Element in Sorted Array
    162. Find Peak Element
    220. Contains Duplicate III
    269. Alien Dictionary
    18. 4Sum
    15. 3Sum
    224. Basic Calculator
    227. Basic Calculator II
  • 原文地址:https://www.cnblogs.com/dragonfei/p/6148625.html
Copyright © 2011-2022 走看看