SpringMVC(四)-请求处理


声明:本文转载自https://my.oschina.net/u/2377110/blog/1551527,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

上一章SpringMVC组件的初始化,所有这些初始化都是为了迎接外部的请求。当外部的请求到来时,SpringMVC如何区分不同请求类型,如何路由到正确的Controller方法,如何生成视图返回?本章就从源码分析SpringMVC的核心处理逻辑。

我们知道所有的请求都会由Servlet容器交由Servlet的doService方法,我们来看DispatcherServlet。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  	// Keep a snapshot of the request attributes in case of an include, 	// to be able to restore the original attributes after the include. 	Map<String, Object> attributesSnapshot = null; 	if (WebUtils.isIncludeRequest(request)) { 		attributesSnapshot = new HashMap<String, Object>(); 		Enumeration<?> attrNames = request.getAttributeNames(); 		while (attrNames.hasMoreElements()) { 			String attrName = (String) attrNames.nextElement(); 			if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { 				attributesSnapshot.put(attrName, request.getAttribute(attrName)); 			} 		} 	}  	// Make framework objects available to handlers and view objects. 	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); 	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());  	FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); 	if (inputFlashMap != null) { 		request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); 	} 	request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 	request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);  	try { 		doDispatch(request, response); 	} 	finally { 		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 			// Restore the original attribute snapshot, in case of an include. 			if (attributesSnapshot != null) { 				restoreAttributesAfterInclude(request, attributesSnapshot); 			} 		} 	} } 

DispatcherServlet的doService方法做了三个事情

  1. 将request中所有的attribute做了备份,以在执行完include后恢复原有属性
  2. 将spring上下文,本地化解析,主题解析器绑定到request请求上
  3. 处理FlashMap

而对请求的处理交给doDispatcher方法

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);  			// 决定当前请求的Handler 			mappedHandler = getHandler(processedRequest); 			if (mappedHandler == null || mappedHandler.getHandler() == null) { 				noHandlerFound(processedRequest, response); 				return; 			}  			// 决定当前请求的HandlerAdapter 			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  			// 处理last-modified请求头 			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; 			}  			// 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); 			} 		} 	} } 

先看doDispatcher方法执行的主要操作时序图

输入图片说明

1.请求路由

getHandler方法就是从HandlerMapping中查询匹配当前request的Handler。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 	for (HandlerMapping hm : this.handlerMappings) { 		HandlerExecutionChain handler = hm.getHandler(request); 		if (handler != null) { 			return handler; 		} 	} 	return null; } 

HandlerMapping的getHandler方法在抽象基类AbstractHandlerMapping

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 	// 由子类根据request获取Handler 	Object handler = getHandlerInternal(request); 	// 如果没匹配到,则获取默认Handler 	if (handler == null) { 		handler = getDefaultHandler(); 	} 	if (handler == null) { 		return null; 	} 	// 如果返回的Handler为String,则使用Spring容器实例化 	if (handler instanceof String) { 		String handlerName = (String) handler; 		handler = getApplicationContext().getBean(handlerName); 	}  	// 查询匹配的拦截器,组装Handler生成HandlerExecutionChain 	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); 	if (CorsUtils.isCorsRequest(request)) { 		CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request); 		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); 		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); 		executionChain = getCorsHandlerExecutionChain(request, executionChain, config); 	} 	return executionChain; } 

最终返回的Handler是由拦截器链和Handler共同组成的,而具体匹配Handler的方法是交给子类来完成的。上一章组件初始化中提到生产环境下使用的是RequestMappingHandlerMapping,getHandlerInternal方法的实现在它的基类AbstractHandlerMethodMapping。

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { 	// 从request获取匹配url 	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 	if (logger.isDebugEnabled()) { 		logger.debug("Looking up handler method for path " + lookupPath); 	} 	this.mappingRegistry.acquireReadLock(); 	try { 		// 查询匹配的HandlerMethod 		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(); 	} } 

可以看到返回的Handler的类型为HandlerMethod,它对应于Controller中的方法。上一章也提过,在AbstractHandlerMethodMapping中有一个MappingRegistry,统一管理URL和Controller方法的映射关系,lookupHandlerMethod就是对MappingRegistry的操作。

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { 	List<Match> matches = new ArrayList<Match>(); 	// 从mappingRegistry获取匹配到的RequestMappingInfo 	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); 	if (directPathMatches != null) { 		addMatchingMappings(directPathMatches, matches, request); 	} 	if (matches.isEmpty()) { 		// No choice but to go through all mappings... 		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); 	}  	// 对匹配项进行排序 	if (!matches.isEmpty()) { 		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); 		Collections.sort(matches, 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 + "}"); 			} 		} 		handleMatch(bestMatch.mapping, lookupPath, request); 		return bestMatch.handlerMethod; 	} 	else { 		// 无匹配项处理 		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); 	} } 

通过mappingRegistry匹配返回RequestMappingInfo,对应于每个有@RequestMapping注解解析后的Method。

protected RequestMappingInfo createRequestMappingInfo( 		RequestMapping requestMapping, RequestCondition<?> customCondition) {  	return RequestMappingInfo 			.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) 			.methods(requestMapping.method()) 			.params(requestMapping.params()) 			.headers(requestMapping.headers()) 			.consumes(requestMapping.consumes()) 			.produces(requestMapping.produces()) 			.mappingName(requestMapping.name()) 			.customCondition(customCondition) 			.options(this.config) 			.build(); } 

当我们拿到HandlerExecutionChain,就完成了request到Controller的路由操作。

2.适配器匹配

有了Handler后,需要合适的HandlerAdapter对其进行操作,因而就要根据Handler进行匹配。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { 	for (HandlerAdapter ha : this.handlerAdapters) { 		if (logger.isTraceEnabled()) { 			logger.trace("Testing handler adapter [" + ha + "]"); 		} 		if (ha.supports(handler)) { 			return ha; 		} 	} 	throw new ServletException("No adapter for handler [" + handler + 			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } 

HandlerAdapter接口中定义了supports方法,用于检测是否支持Handler。生产环境使用的RequestMappingHandlerAdapter在其基类AbstractHandlerMethodAdapter中实现了supports方法。

public final boolean supports(Object handler) { 	return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); } 

supportsInternal方法在RequestMappingHandlerAdapter的实现里默认返回true。因而RequestMappingHandlerAdapter就是用来支持类型为HandlerMethod的Handler的处理的。

3.拦截器处理

在SpringMVC中的拦截器接口HandlerInterceptor中定义了三个方法

public interface HandlerInterceptor {  	// 在Handler找到后,执行前拦截 	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 			throws Exception;  	// 在Handler执行后,视图渲染前拦截 	void postHandle( 			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 			throws Exception;  	// 请求处理完成,视图渲染后执行资源清理等 	void afterCompletion( 			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 			throws Exception;  } 

可以很清晰地对应到doDispatcher方法中。需要注意的有几点

  1. 前置处理preHandle,返回值为boolean。如果返回true,则执行下一个,如果返回false,则认为当前拦截器完成了请求,DispatcherServlet会直接返回,在返回前会调用所有拦截器的afterCompletion方法,完成清理工作。
  2. afterCompletion方法在遇到任何情况时都需要被执行,无论是成功返回还是抛出异常。

4.执行请求

HandlerAdapter的handle方法完成请求的真正执行。在AbstractHandlerMethodAdapter中由handleInternal执行。

protected ModelAndView handleInternal(HttpServletRequest request, 		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  	ModelAndView mav; 	checkRequest(request);   	// 执行HandlerMethod 	mav = invokeHandlerMethod(request, response, handlerMethod);  	// 处理缓存 	if (!response.containsHeader(HEADER_CACHE_CONTROL)) { 		if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { 			applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); 		} 		else { 			prepareResponse(response); 		} 	}  	return mav; } 

在invokeHandlerMethod中,HandlerMethod被封装ServletInvocableHandlerMethod,包裹上方法执行需要的信息。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, 		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  	ServletWebRequest webRequest = new ServletWebRequest(request, response); 	try { 		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); 		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);  		// 封装HandlerMethod 		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); 		invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); 		invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); 		invocableMethod.setDataBinderFactory(binderFactory); 		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);  		ModelAndViewContainer mavContainer = new ModelAndViewContainer(); 		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); 		modelFactory.initModel(webRequest, mavContainer, invocableMethod); 		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);  		// 异步请求处理 		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); 		asyncWebRequest.setTimeout(this.asyncRequestTimeout);  		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 		asyncManager.setTaskExecutor(this.taskExecutor); 		asyncManager.setAsyncWebRequest(asyncWebRequest); 		asyncManager.registerCallableInterceptors(this.callableInterceptors); 		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);  		if (asyncManager.hasConcurrentResult()) { 			Object result = asyncManager.getConcurrentResult(); 			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; 			asyncManager.clearConcurrentResult(); 			if (logger.isDebugEnabled()) { 				logger.debug("Found concurrent result value [" + result + "]"); 			} 			invocableMethod = invocableMethod.wrapConcurrentResult(result); 		}  		// 执行处理 		invocableMethod.invokeAndHandle(webRequest, mavContainer); 		if (asyncManager.isConcurrentHandlingStarted()) { 			return null; 		}  		// 封装数据和视图 		return getModelAndView(mavContainer, modelFactory, webRequest); 	} 	finally { 		webRequest.requestCompleted(); 	} } 

再到ServletInvocableHandlerMethod的invokeAndHandle方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, 		Object... providedArgs) throws Exception {  	// 执行request 	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 	setResponseStatus(webRequest);  	if (returnValue == null) { 		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { 			mavContainer.setRequestHandled(true); 			return; 		} 	} 	else if (StringUtils.hasText(getResponseStatusReason())) { 		mavContainer.setRequestHandled(true); 		return; 	}  	mavContainer.setRequestHandled(false); 	try { 		// 对返回值进行处理 		this.returnValueHandlers.handleReturnValue( 				returnValue, getReturnValueType(returnValue), mavContainer, webRequest); 	} 	catch (Exception ex) { 		if (logger.isTraceEnabled()) { 			logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); 		} 		throw ex; 	} }  public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, 		Object... providedArgs) throws Exception {  	// 执行方法参数 	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); 	if (logger.isTraceEnabled()) { 		logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + 				"' with arguments " + Arrays.toString(args)); 	} 	 	Object returnValue = doInvoke(args); 	if (logger.isTraceEnabled()) { 		logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + 				"] returned [" + returnValue + "]"); 	} 	return returnValue; }  protected Object doInvoke(Object... args) throws Exception { 	ReflectionUtils.makeAccessible(getBridgedMethod()); 	return getBridgedMethod().invoke(getBean(), args); } 

需要说明的一点是方法执行完成的返回值通过返回值处理器HandlerMethodReturnValueHandler进行处理。在RequestMappingHandlerAdapter的初始化中,内置了众多的HandlerMethodReturnValueHandler来处理多种类型的返回值。

在完成请求执行后,doDispatcher方法中做了一个默认View的设置。

applyDefaultViewName(processedRequest, mv);  private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception { 	if (mv != null && !mv.hasView()) { 		mv.setViewName(getDefaultViewName(request)); 	} } 

而这个getDefaultViewName是通过RequestToViewNameTranslator的实现类来解析的

protected String getDefaultViewName(HttpServletRequest request) throws Exception { 	return this.viewNameTranslator.getViewName(request); } 

默认实现DefaultRequestToViewNameTranslator,根据配置的一些通用url进行匹配

public String getViewName(HttpServletRequest request) { 	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); 	return (this.prefix + transformPath(lookupPath) + this.suffix); } 

5.视图渲染

当请求完成后,返回的ModelAndView需要渲染到浏览器进行显示。doDispatcher方法中processDispatchResult用来处理视图。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 		HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {  	boolean errorView = false;  	// 异常处理 	if (exception != null) { 		if (exception instanceof ModelAndViewDefiningException) { 			logger.debug("ModelAndViewDefiningException encountered", exception); 			mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 		} 		else { 			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 			mv = processHandlerException(request, response, handler, exception); 			errorView = (mv != null); 		} 	}  	// Did the handler return a view to render? 	if (mv != null && !mv.wasCleared()) { 		// 渲染执行 		render(mv, request, response); 		if (errorView) { 			WebUtils.clearErrorRequestAttributes(request); 		} 	} 	else { 		if (logger.isDebugEnabled()) { 			logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 					"': assuming HandlerAdapter completed request handling"); 		} 	}  	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 		// Concurrent handling started during a forward 		return; 	}  	// 完成后执行拦截器的afterCompletion 	if (mappedHandler != null) { 		mappedHandler.triggerAfterCompletion(request, response, null); 	} } 

render方法执行渲染,最终由View实现类执行

view.render(mv.getModelInternal(), request, response); 

抽象类AbstractView执行对数据进行组装,输出操作交由子类完成

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { 	if (logger.isTraceEnabled()) { 		logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + 			" and static attributes " + this.staticAttributes); 	}  	// 组装数据 	Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); 	prepareResponse(request, response); 	// 渲染输出 	renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } 

以通用的InternalResourceView举例

protected void renderMergedOutputModel( 		Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  	// Expose the model object as request attributes. 	exposeModelAsRequestAttributes(model, request);  	// Expose helpers as request attributes, if any. 	exposeHelpers(request);  	// Determine the path for the request dispatcher. 	String dispatcherPath = prepareForRendering(request, response);  	// Obtain a RequestDispatcher for the target resource (typically a JSP). 	RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); 	if (rd == null) { 		throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + 				"]: Check that the corresponding file exists within your web application archive!"); 	}  	// If already included or response already committed, perform include, else forward. 	if (useInclude(request, response)) { 		response.setContentType(getContentType()); 		if (logger.isDebugEnabled()) { 			logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); 		} 		rd.include(request, response); 	}  	else { 		// Note: The forwarded resource is supposed to determine the content type itself. 		if (logger.isDebugEnabled()) { 			logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); 		} 		rd.forward(request, response); 	} } 

最终由java Servlet的RequestDispatcher完成输出。

本章中以请求的正向主流程解析了DispatcherServlet及相关类完成此过程的源码,其主要过程则是HandlerExecutionChain,HandlerMapping,HandlerAdapter,View等组件的交互过程,贴两张网上的核心原理图,希望对大家理解SpringMVC的原理有帮助。

输入图片说明

输入图片说明

本文发表于2017年10月16日 20:34
(c)注:本文转载自https://my.oschina.net/u/2377110/blog/1551527,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2046 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1