zoukankan      html  css  js  c++  java
  • Springboot 2.x MVC-getHandler

    请求映射即为当我们发送某一个请求,Springboot 会调用对应的处理器来对当前请求进行处理,之前接触过 Springmvc 的应该都知道,浏览器发送请求之后会交由一个 DispatcherServlet 的 Servlet 来进行处理,而 Springboot 对于请求的处理也是由 DispatcherServlet 完成的,我们这里选取 Springboot-2.3.7.RELEASE 这个版本来探究一下

    首先找到 DispatcherServlet ,打开这个 Servlet 的继承体系结构

    作为一个 Servlet 必然有 doGet / doPost 方法,在 HttpServletBean 中没有找到,那么继续尝试去子类中找,打开 FrameworkServlet ,在这个 Servlet 中我们找到了 doGet 和 doPost 方法,

    FrameworkServlet 中的 doGet / doPost 方法最终调用的都是本类的 processRequest 方法

    点进去 processRequest 方法,里面有核心的业务方法 doService(request, response)

    打开 doService 方法,发现该方法是一个抽象方法

    打开 doService 方法的实现类,我们就来到了 DispatcherServlet 这个类中,它作为 FrameworkServlet 的子类,重写了 FrameworkServlet 的抽象方法 doService 

    打开 DispatcherServlet 的 doService 方法,里面有该 Servlet 的核心方法 doDispatch(request, response)

    打开 doDispatch 方法,里面有一个 mappedHandler = getHandler(processedRequest) 方法,该方法的作用是根据请求路径获取匹配的处理器执行链

    例如 浏览器发送请求 http://localhost:8080/user/1/games/dota ,就会匹配到 MvcController 这个控制器的 requestParams 方法

    // 该注解是 @Controller 和 @ResponseBody 的组合注解
    // @Controller:表明该类是一个 Controller ,它的底层是 @Component 注解,会将该类注入到 IOC 容器中
    // @ResponseBody:将 Controller 类中方法的返回值对象通过适当的转换器转换之后,写入到 Response 对象的 body 区(响应体中)
    // 通常用来返回 JSON 或者是 XML 数据
    @RestController
    public class MvcController {
        // 该注解等价于 @RequestMapping(value="/user/{1}/games/{dota}",method = RequestMethod.GET)
        // Rest 风格获取 1 号用户最喜欢玩的游戏
        @GetMapping(value="/user/{userId}/games/{favGame}")
        public Map<String, Object> requestParams(
                // 将路径变量中的 userId 赋值给形式参数 id
                @PathVariable("userId") Integer id,
                // 将路径变量中的 favGame 赋值给形式参数 favouriteGame
                @PathVariable("favGame") String favouriteGame) {
    
            Map map = new HashMap<String, Object>();
            map.put("id", id);
            map.put("favouriteGame", favouriteGame);
    
            return map;
        }
    }

    代码块一、getHandler(processedRequest)

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    	// 1、获取 Springboot 默认为我们注入的所有 handlerMappings ,它的作用是保存 request 和 handler 的映射规则
    	if (this.handlerMappings != null) {
    		// 2、循环遍历所有的 handlerMappings ,
    		for (HandlerMapping mapping : this.handlerMappings) {
    			// 3、获取 handler ----> 详情见代码块二
    			HandlerExecutionChain handler = mapping.getHandler(request);
    			// 4、如果能找到合适的 handler ,将该处理器返回
    			if (handler != null) {
    				return handler;
    			}
    		}
    	}
    	// 5、如果没有找到合适的处理器,返回 null
    	return null;
    }

    1、handlerMappings: Springboot 为我们默认配置的 handlerMappings 如下

    所有的 mapping 映射关系保存在 mappingRegistry (映射注册中心) 中,具体的 @RequestMapping 注解与其处理器映射关系如下

    3、获取 handler: 获取处理器 handler 并将其封装在处理器执行链中(HandlerExecutionChain 封装 handler 和 interceptors) ----> 详情见代码块八

    代码块二、getHandler

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    	// 1、获取内部的处理器 ----> 详情见代码块三
    	Object handler = getHandlerInternal(request);
    	// 2、如果没有定义处理器,则获取默认的处理器(默认的处理器为 null,难道是需要我们自己设置一个默认的处理器吗?)
    	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);
    	}
    	// 3、获取处理器执行链 ----> 代码块六
    	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    
    	if (logger.isTraceEnabled()) {
    		logger.trace("Mapped to " + handler);
    	}
    	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
    		logger.debug("Mapped to " + executionChain.getHandler());
    	}
    	// 4、与跨域访问有关
    	if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
    		CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
    		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    		config = (config != null ? config.combine(handlerConfig) : handlerConfig);
    		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    	}
    	// 5、返回处理器执行链
    	return executionChain;
    }

    1、getHandlerInternal(request):获取内部的处理器 ----> 详情见代码块三

    3、getHandlerExecutionChain:获取处理器执行链 ----> 代码块六

    代码块三、getHandlerInternal

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    	// 1、移除可生产的媒体类型属性值
    	request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    	try {
    		// 2、调用父类的 getHandlerInternal 方法 ----> 详情见代码块四
    		return super.getHandlerInternal(request);
    	}
    	finally {
    		// 3、清除媒体类型属性 
    		ProducesRequestCondition.clearMediaTypesAttribute(request);
    	}
    }

    代码块四、getHandlerInternal

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    	// 1、通过 UrlPathHelper 获取 request 请求中的请求路径(浏览器或其它客户端发送的请求路径 user/1/games/dota
    	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    	// 2、将获取到的请求路径作为属性设置到 request 中 
    	request.setAttribute(LOOKUP_PATH, lookupPath);
    	// 3、读锁
    	this.mappingRegistry.acquireReadLock();
    	try {
    		// 4、获取处理器方法 ----> 详情见代码块五
    		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    		// 5、返回最终的处理器方法(也就是哪个 Controller 中的哪个方法能处理该请求)
    		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    	}
    	finally {
    		// 6、释放锁
    		this.mappingRegistry.releaseReadLock();
    	}
    }

    2、request 设置 LOOKUP_PATH 属性

    key 的全称为:org.springframework.web.servlet.HandlerMapping.lookupPath

    4、lookupHandlerMethod(lookupPath, request) : 根据请求的路径寻找合适的处理器 ----> 详情见代码块十一

    代码块五、寻找处理器方法:lookupHandlerMethod

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    	// 1、定义一个 List 集合 matches
    	List<Match> matches = new ArrayList<>();
    	// 2、根据请求路径查询有没有能直接匹配上的,如果有,则将其存入 directPathMatches 集合中
    	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    	// 3、如果直接匹配上了,则添加到 matches 集合中
    	if (directPathMatches != null) {
    		addMatchingMappings(directPathMatches, matches, request);
    	}
    	// 4、如果没有直接匹配上,则获取所有的映射,挨个匹配
    	if (matches.isEmpty()) {
    		// No choice but to go through all mappings...
    		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    	}
    	// 5、匹配、排序、筛选出最匹配的处理方法
    	if (!matches.isEmpty()) {
    		Match bestMatch = matches.get(0);
    		if (matches.size() > 1) {
    			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
    			matches.sort(comparator);
    			bestMatch = matches.get(0);
    			if (logger.isTraceEnabled()) {
    				logger.trace(matches.size() + " matching mappings: " + matches);
    			}
    			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();
    				String uri = request.getRequestURI();
    				throw new IllegalStateException(
    						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
    			}
    		}
    		// 6、将获取的最佳处理方法设置为 request 的属性
    		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
    		// 7、处理具体的匹配
    		handleMatch(bestMatch.mapping, lookupPath, request);
    		// 8、返回最佳的匹配方法
    		return bestMatch.handlerMethod;
    	}
    	else {
    		// 9、如果所有的 mappings 都没有任何一个能匹配上,返回 null
    		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    	}
    }
    

    2、直接匹配,比如浏览器输入 http://localhost:8080/user/1/games/dota ,它会寻找有没有 @RequestMapping("/user/1/games/dota",.....) 这个内容的注解

    7、handleMatch(bestMatch.mapping, lookupPath, request) 这个方法是具体处理匹配的方法,有兴趣可以去看一下

    8、通过 /user/1/games/dota 寻找到最佳的匹配方法如下

    代码块六、getHandlerExecutionChain

    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    	// 1、判断当前 handler 是否是 HandlerExecutionChain 类型,如果不是则新建一个 HandlerExecutionChain
    	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
    			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    	// 2、获取请求路径
    	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
    	
    	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
    		// 3、如果处理器拦截器是已经映射过的拦截器
    		if (interceptor instanceof MappedInterceptor) {
    			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
    			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
    				chain.addInterceptor(mappedInterceptor.getInterceptor());
    			}
    		}
    		else {
    			// 4、处理器执行链添加拦截器
    			chain.addInterceptor(interceptor);
    		}
    	}
    	// 5、返回处理器执行链
    	return chain;
    }

    最终返回的 HandlerExecutionChain 如下

     

  • 相关阅读:
    动态获取页面参数内容
    服务器处理静态文件请求
    最简单的Web服务器
    控制台浏览器代码实战
    4.caffe资源汇总(更新中)
    3. caffe中 python Notebook
    2.caffe初解
    1.caffe初入
    有监督学习和无监督学习
    MySQL 之基础操作及增删改查等
  • 原文地址:https://www.cnblogs.com/xiaomaomao/p/14288433.html
Copyright © 2011-2022 走看看