当前位置: 首页 > news >正文

wordpress可以做电影网站吗龙华网站建设价格

wordpress可以做电影网站吗,龙华网站建设价格,网站内容怎么做,响应式网站SpringMVC 执行流程解析 注#xff1a;SpringMVC 版本 5.2.15 上面这张图许多人都看过#xff0c;本文试图从源码的角度带大家分析一下该过程。 1. ContextLoaderListener 首先我们从 ContextLoaderListener 讲起#xff0c;它继承自 ServletContextListener#xff0c;用…SpringMVC 执行流程解析 注SpringMVC 版本 5.2.15 上面这张图许多人都看过本文试图从源码的角度带大家分析一下该过程。 1. ContextLoaderListener 首先我们从 ContextLoaderListener 讲起它继承自 ServletContextListener用于监听 Web 应用程序的启动与停止。ContextLoaderListener 中的 contextInitialized() 方法用于初始化 Web 应用程序上下文。 ContextLoaderListener # contextInitialized public class ContextLoaderListener extends ContextLoader implements ServletContextListener {.../*** Initialize the root web application context.*/Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}... }其中又调用了 initWebApplicationContext() 方法初始化 Web 应用程序上下文 ContextLoader # initWebApplicationContext public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) ! null) {throw new IllegalStateException(Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!);}servletContext.log(Initializing Spring root WebApplicationContext);Log logger LogFactory.getLog(ContextLoader.class);if (logger.isInfoEnabled()) {logger.info(Root WebApplicationContext: initialization started);}long startTime System.currentTimeMillis();try {if (this.context null) {// 创建并保存应用程序上下文到属性中this.context createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {if (cwac.getParent() null) {ApplicationContext parent loadParentContext(servletContext);cwac.setParent(parent);}// 配置并刷新当前 Web 应用程序上下文configureAndRefreshWebApplicationContext(cwac, servletContext);}}servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl Thread.currentThread().getContextClassLoader();if (ccl ContextLoader.class.getClassLoader()) {currentContext this.context;}else if (ccl ! null) {currentContextPerThread.put(ccl, this.context);}if (logger.isInfoEnabled()) {long elapsedTime System.currentTimeMillis() - startTime;logger.info(Root WebApplicationContext initialized in elapsedTime ms);}return this.context;}catch (RuntimeException | Error ex) {logger.error(Context initialization failed, ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;} }该方法中调用了 createWebApplicationContext() 方法创建了 Web 应用程序上下文并调用 configureAndRefreshWebApplicationContext() 方法配置并刷新当前 Web 应用程序上下文。 ContextLoader # configureAndRefreshWebApplicationContext protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {// 为当前 Web 应用程序上下文设置一个 idif (ObjectUtils.identityToString(wac).equals(wac.getId())) {String idParam sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam ! null) {wac.setId(idParam);}else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX ObjectUtils.getDisplayString(sc.getContextPath()));}}// 设置 ServletContextwac.setServletContext(sc);String configLocationParam sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam ! null) {wac.setConfigLocation(configLocationParam);}ConfigurableEnvironment env wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);// 刷新当前应用程序上下文wac.refresh(); }该方法的主要作用是刷新当前应用程序上下文实际上就是调用了 AbstractApplicationContext # refresh 方法也就是我们常讲的启动 IOC 容器这个方法里的具体内容这里就不讲了。 总结 ContextLoaderListener 的作用是准备好 Web 应用程序上下文启动 IOC 容器。 2. DispatcherServlet 初始化逻辑 先看一张 DispatcherServlet 的继承关系图 其中 HttpServletBean 有一个 init() 方法该方法是一个初始化方法 HttpServletBean # init Override public final void init() throws ServletException {// 获取 web.xml 文件中的 init-param 中的参数保存到 PropertyValues 中PropertyValues pvs new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {// 将 HttpServletBean 对象转换为 BeanWrapper 对象BeanWrapper bw PropertyAccessorFactory.forBeanPropertyAccess(this);// 创建一个 ResourceLoader 对象ResourceLoader resourceLoader new ServletContextResourceLoader(getServletContext());// 注册属性编辑器bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));// 初始化 BeanWrapper 对象initBeanWrapper(bw);// 将属性值保存到 BeanWrapper 中bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error(Failed to set bean properties on servlet getServletName() , ex);}throw ex;}}// Let subclasses do whatever initialization they like.initServletBean(); }该方法中首先尝试获取 web.xml 文件中的 init-param 值如果获取到的话则将它保存到 BeanWrapper 中。最后调用了 initServletBean() 方法 FrameworkServlet # initServletBean Override protected final void initServletBean() throws ServletException {getServletContext().log(Initializing Spring getClass().getSimpleName() getServletName() );if (logger.isInfoEnabled()) {logger.info(Initializing Servlet getServletName() );}long startTime System.currentTimeMillis();try {// 初始化 WebApplicationContextthis.webApplicationContext initWebApplicationContext();// 空方法可由子类去具体实现initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error(Context initialization failed, ex);throw ex;}if (logger.isDebugEnabled()) {String value this.enableLoggingRequestDetails ?shown which may lead to unsafe logging of potentially sensitive data :masked to prevent unsafe logging of potentially sensitive data;logger.debug(enableLoggingRequestDetails this.enableLoggingRequestDetails : request parameters and headers will be value);}if (logger.isInfoEnabled()) {logger.info(Completed initialization in (System.currentTimeMillis() - startTime) ms);} }该方法中主要调用了 initWebApplicationContext() 去初始化 web 应用程序上下文 FrameworkServlet # initWebApplicationContext protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac null;if (this.webApplicationContext ! null) {wac this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() null) {cwac.setParent(rootContext);}// 配置并刷新 web 应用程序上下文configureAndRefreshWebApplicationContext(cwac);}}}if (wac null) {// 去获取 web 应用程序上下文wac findWebApplicationContext();}if (wac null) {// 创建 web 应用程序上下文wac createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized (this.onRefreshMonitor) {// 刷新 web 应用程序上下文onRefresh(wac);}}if (this.publishContext) {String attrName getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac; }该方法主要是去创建一个新的 web 应用程序上下文然后调用 onRefresh() 方法 FrameworkServlet # onRefresh Override protected void onRefresh(ApplicationContext context) {initStrategies(context); }DispatcherServlet # initStrategies protected void initStrategies(ApplicationContext context) {// 初始化 文件上传解析器initMultipartResolver(context);// 初始化 本地化解析器initLocaleResolver(context);// 初始化 主题解析器initThemeResolver(context);// 初始化 处理器映射器initHandlerMappings(context);// 初始化 处理器映射适配器initHandlerAdapters(context);// 初始化 异常解析器initHandlerExceptionResolvers(context);// 初始化 请求获取视图名转换器initRequestToViewNameTranslator(context);// 初始化 视图解析器initViewResolvers(context);// 初始化 FlashMap 管理器initFlashMapManager(context); }该方法中初始化了 SpringMVC 的九大组件。我以 initHandlerMappings() 方法为例讲解一下其他组件的获取方式和它基本相同。 DispatcherServlet # initHandlerMappings /*** Initialize the HandlerMappings used by this class.* pIf no HandlerMapping beans are defined in the BeanFactory for this namespace,* we default to BeanNameUrlHandlerMapping.*/ private void initHandlerMappings(ApplicationContext context) {this.handlerMappings null;// 从 ApplicationContext 中去获取 HandlerMapping// 获取不到的话去获取对象名为 handlerMapping 的 HandlerMapping 对象// 再获取不到的话则注册默认的 HandlerMappingif (this.detectAllHandlerMappings) {// 从 ApplicationContext 中获取MapString, HandlerMapping matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {try {// 获取名为 handlerMapping 的 HandlerMapping 对象HandlerMapping hm context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {}}if (this.handlerMappings null) {// 注册默认的 HandlerMappingthis.handlerMappings getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace(No HandlerMappings declared for servlet getServletName() : using default strategies from DispatcherServlet.properties);}} }该方法首先从 ApplicationContext 中去获取 HandlerMapping获取不到的话去获取对象名为 handlerMapping 的 HandlerMapping 对象再获取不到的话则注册默认的 HandlerMapping。我们看一下这个默认的 HandlerMapping 是怎么获取的。 DispatcherServlet # getDefaultStrategies protected T ListT getDefaultStrategies(ApplicationContext context, ClassT strategyInterface) {String key strategyInterface.getName();// 获取逻辑在这String value defaultStrategies.getProperty(key);if (value ! null) {// 获取类名String[] classNames StringUtils.commaDelimitedListToStringArray(value);ListT strategies new ArrayList(classNames.length);for (String className : classNames) {try {// 创建 Class 对象Class? clazz ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());Object strategy createDefaultStrategy(context, clazz);strategies.add((T) strategy);}catch (ClassNotFoundException ex) {throw new BeanInitializationException(Could not find DispatcherServlets default strategy class [ className ] for interface [ key ], ex);}catch (LinkageError err) {throw new BeanInitializationException(Unresolvable class definition for DispatcherServlets default strategy class [ className ] for interface [ key ], err);}}return strategies;}else {return new LinkedList();} }可以看到会尝试从 defaultStrategies 中获取值。然后将获取到的值通过 Class.forName() 方法去创建对象。那么我们看一下这个 defaultStrategies 是什么。 private static final Properties defaultStrategies;static {try {ClassPathResource resource new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);defaultStrategies PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException(Could not load DEFAULT_STRATEGIES_PATH : ex.getMessage());} }private static final String DEFAULT_STRATEGIES_PATH DispatcherServlet.properties;defaultStrategies 是 DispatcherServlet 中的一个属性类型为 Properties 。在 DispatcherServlet 的静态代码块中加载了 DispatcherServlet.properties 文件到了 defaultStrategies 中。 DispatcherServlet.properties org.springframework.web.servlet.LocaleResolverorg.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolverorg.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMappingorg.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapterorg.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolverorg.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslatororg.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolverorg.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManagerorg.springframework.web.servlet.support.SessionFlashMapManager 在 DispatcherServlet.properties 中定义了 HandlerMapping、HandlerAdapter、ViewResolver 等类。它们的获取逻辑是相似的。通过文件中定义的全限定名然后调用 Class.forName() 方法去创建对象。 3. DispatcherServlet 处理流程 以 get 请求为例。当发送 get 请求时由 FrameworkServlet # doGet 方法进行处理。 FrameworkServlet # doGet Override protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response); }该方法中又调用了 processRequest() 方法 FrameworkServlet # processRequest protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {long startTime System.currentTimeMillis();Throwable failureCause null;// 获取一个 LocaleContext 对象LocaleContext previousLocaleContext LocaleContextHolder.getLocaleContext();// 构建一个新的 LocaleContext 对象LocaleContext localeContext buildLocaleContext(request);// 获取一个 RequestAttributes 对象RequestAttributes previousAttributes RequestContextHolder.getRequestAttributes();// 构建一个新的 ServletRequestAttributes 对象ServletRequestAttributes requestAttributes buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 初始化资源持有者// 将 localeContext 保存到 LocaleContextHolder 中// 将 requestAttributes 保存到 RequestContextHolder 中initContextHolders(request, localeContext, requestAttributes);try {doService(request, response);}catch (ServletException | IOException ex) {failureCause ex;throw ex;}catch (Throwable ex) {failureCause ex;throw new NestedServletException(Request processing failed, ex);}finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes ! null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);} }该方法中新建了一个 LocaleContext 对象和一个 ServletRequestAttributes 对象并保存到了 LocaleContextHolder 与 RequestContextHolder 中。尤其是 RequestContextHolder 对象我们可以通过它去获取 HttpServletRequest、HttpServletResponse 等对象。最后调用了 doService() 方法。 DispatcherServlet # doService Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// 保留 request attributes 的快照MapString, Object attributesSnapshot null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot new HashMap();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());if (this.flashMapManager ! null) {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);}}} }该方法中又调用了 doDispatch() 方法 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);// 获取 HandlerExecutionChain // 该方法中确定了用哪个处理器处理当前请求// 并添加了拦截器mappedHandler getHandler(processedRequest);// 没有找到合适的处理器if (mappedHandler null) {noHandlerFound(processedRequest, response);return;}// 获取 HandlerAdapterHandlerAdapter ha getHandlerAdapter(mappedHandler.getHandler());// 获取请求方法String method request.getMethod();boolean isGet GET.equals(method);if (isGet || HEAD.equals(method)) {long lastModified ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) isGet) {return;}}// 执行拦截器的前置处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 处理请求并返回 ModelAndView 对象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) {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()) {if (mappedHandler ! null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}} }接下来我们看一下 getHandler() 方法 DispatcherServlet # getHandler protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings ! null) {// 遍历所有的 HandlerMapping 去获取 HandlerExecutionChain for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler mapping.getHandler(request);if (handler ! null) {return handler;}}}return null; }该方法中遍历了所有的 HandlerMapping 去获取 Handler AbstractHandlerMapping # getHandler public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 获取合适的 HandlerObject handler getHandlerInternal(request);if (handler null) {// 没有找到合适的 Handler则使用默认的 Handlerhandler getDefaultHandler();}if (handler null) {return null;}// 获取到了该 Handler 的字符串名则通过字符串名去获取对应的对象if (handler instanceof String) {String handlerName (String) handler;handler obtainApplicationContext().getBean(handlerName);}// 为当前请求添加拦截器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());}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);}return executionChain; }该方法中去获取合适的 Handler获取失败的话则会使用默认的 Handler。并为该请求添加拦截器。 RequestMappingInfoHandlerMapping # getHandlerInternal protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);try {return super.getHandlerInternal(request);}finally {ProducesRequestCondition.clearMediaTypesAttribute(request);} }AbstractHandlerMethodMapping # getHandlerInternal protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 获取请求路径String lookupPath getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);// 加读锁this.mappingRegistry.acquireReadLock();try {// 根据请求路径获取对应的 HandlerMethod HandlerMethod handlerMethod lookupHandlerMethod(lookupPath, request);return (handlerMethod ! null ? handlerMethod.createWithResolvedBean() : null);}finally {// 释放读锁this.mappingRegistry.releaseReadLock();} }该方法中根据请求路径去获取对应的 HandlerMethod AbstractHandlerMethodMapping # lookupHandlerMethod protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {ListMatch matches new ArrayList();// 根据请求路径获取对应的映射ListT 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()) {Match bestMatch matches.get(0);if (matches.size() 1) {ComparatorMatch 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 });}}request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;}// 没有找到合适的方法返回 nullelse {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);} }该方法中会去获取与请求最匹配的方法获取不到则返回 null。 这里就已经获取完 handler 了也就是一个 HandlerMethod 对象比如 TestController # test 。我们往下走。 Controller public class TestController {GetMapping(/test)public String test() {return test;} }DispatcherServlet # getHandlerAdapter protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters ! null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}throw new ServletException(No adapter for handler [ handler ]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler); }该方法就是去找一个对应的 HandlerAdapter一般是 RequestMappingHandlerAdapter。 AbstractHandlerMethodAdapter # handle public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler); }RequestMappingHandlerAdapter # handleInternal protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {HttpSession session request.getSession(false);if (session ! null) {Object mutex WebUtils.getSessionMutex(session);synchronized (mutex) {mav invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available - no mutex necessarymav invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...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() 方法 RequestMappingHandlerAdapter # invokeHandlerMethod protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest new ServletWebRequest(request, response);try {// 找到 InitBinder 标注的方法// InitBinder 与 WebDatabinder 一起使用用于注册自定义的属性编辑器WebDataBinderFactory binderFactory getDataBinderFactory(handlerMethod);// 找到 ModelAttribute 标注的方法ModelFactory modelFactory getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod createInvocableHandlerMethod(handlerMethod);// 注册参数解析器if (this.argumentResolvers ! null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}// 注册返回值解析器if (this.returnValueHandlers ! null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);// 注册参数名发现器invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));// 调用 ModelAttribute 标注的方法保证它在其他方法执行前先被执行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();LogFormatUtils.traceDebug(logger, traceOn - {String formatted LogFormatUtils.formatValue(result, !traceOn);return Resume with async result [ formatted ];});invocableMethod invocableMethod.wrapConcurrentResult(result);}// 调用该方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}// 返回 ModelAndView 对象return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();} }该方法中会找到并注册 InitBinder 标注的方法找到并调用 ModelAttribute 标注的的方法。调用 invokeAndHandleI() 方法最终返回 ModelAndView 对象 ServletInvocableHandlerMethod # invokeAndHandle public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 调用方法并获取返回值Object returnValue invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue null) {if (isRequestNotModified(webRequest) || getResponseStatus() ! null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers ! null, No return value handlers);try {// 使用对应的 HandlerMethodReturnValueHandler 去处理返回值this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;} }InvocableHandlerMethod # invokeForRequest public Object invokeForRequest(NativeWebRequest request, Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取形参值Object[] args getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace(Arguments: Arrays.toString(args));}// 调用方法return doInvoke(args); }InvocableHandlerMethod # getMethodArgumentValues protected Object[] getMethodArgumentValues(NativeWebRequest request, Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取 MethodParameter 数组MethodParameter[] parameters getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args new Object[parameters.length];for (int i 0; i parameters.length; i) {MethodParameter parameter parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] findProvidedArgument(parameter, providedArgs);if (args[i] ! null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, No suitable resolver));}try {// 解析参数args[i] this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg ex.getMessage();if (exMsg ! null !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args; }该方法中调用了 resolveArgument() 去解析参数 HandlerMethodArgumentResolverComposite # resolveArgument public Object resolveArgument(MethodParameter parameter, Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, Nullable WebDataBinderFactory binderFactory) throws Exception {// 获取 HandlerMethodArgumentResolver - RequestParamMethodArgumentResolverHandlerMethodArgumentResolver resolver getArgumentResolver(parameter);if (resolver null) {throw new IllegalArgumentException(Unsupported parameter type [ parameter.getParameterType().getName() ]. supportsParameter should be called first.);}// 解析参数return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }AbstractNamedValueMethodArgumentResolver # resolveArgument public final Object resolveArgument(MethodParameter parameter, Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, Nullable WebDataBinderFactory binderFactory) throws Exception {NamedValueInfo namedValueInfo getNamedValueInfo(parameter);MethodParameter nestedParameter parameter.nestedIfOptional();Object resolvedName resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName null) {throw new IllegalArgumentException(Specified name must not resolve to null: [ namedValueInfo.name ]);}Object arg resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg null) {if (namedValueInfo.defaultValue ! null) {arg resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}else if (namedValueInfo.required !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if (.equals(arg) namedValueInfo.defaultValue ! null) {arg resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}if (binderFactory ! null) {WebDataBinder binder binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg; }AbstractNamedValueMethodArgumentResolver # getNamedValueInfo private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {// 先从缓存中获取NamedValueInfo namedValueInfo this.namedValueInfoCache.get(parameter);// 缓存中获取不到if (namedValueInfo null) {// 获取 RequestParam 中的 value 属性值如果有则将参数名设置为该值namedValueInfo createNamedValueInfo(parameter);// 如果没有使用 RequestParam 注解或使用了但没有设置 value 属性// 则使用 ASM 框架去获取字节码来获取属性名namedValueInfo updateNamedValueInfo(parameter, namedValueInfo);this.namedValueInfoCache.put(parameter, namedValueInfo);}return namedValueInfo; }RequestParamMethodArgumentResolver # createNamedValueInfo protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {// 根据 RequestParam 注解去创建 NamedValueInfo // 如果 RequestParam 中的 value 属性有值则设置参数名为该值RequestParam ann parameter.getParameterAnnotation(RequestParam.class);return (ann ! null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo()); }AbstractNamedValueMethodArgumentResolver # updateNamedValueInfo private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {String name info.name;if (info.name.isEmpty()) {name parameter.getParameterName();if (name null) {throw new IllegalArgumentException(Name for argument of type [ parameter.getNestedParameterType().getName() ] not specified, and parameter name information not found in class file either.);}}String defaultValue (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);return new NamedValueInfo(name, info.required, defaultValue); }MethodParameter # getParameterName public String getParameterName() {if (this.parameterIndex 0) {return null;}ParameterNameDiscoverer discoverer this.parameterNameDiscoverer;if (discoverer ! null) {String[] parameterNames null;if (this.executable instanceof Method) {parameterNames discoverer.getParameterNames((Method) this.executable);}else if (this.executable instanceof Constructor) {parameterNames discoverer.getParameterNames((Constructor?) this.executable);}if (parameterNames ! null) {this.parameterName parameterNames[this.parameterIndex];}this.parameterNameDiscoverer null;}return this.parameterName; }LocalVariableTableParameterNameDiscoverer # getParameterNames public String[] getParameterNames(Method method) {Method originalMethod BridgeMethodResolver.findBridgedMethod(method);return doGetParameterNames(originalMethod); }LocalVariableTableParameterNameDiscoverer # doGetParameterNames private String[] doGetParameterNames(Executable executable) {Class? declaringClass executable.getDeclaringClass();MapExecutable, String[] map this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass);return (map ! NO_DEBUG_INFO_MAP ? map.get(executable) : null); }LocalVariableTableParameterNameDiscoverer # inspectClass private MapExecutable, String[] inspectClass(Class? clazz) {// 获取字节码文件InputStream is clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));if (is null) {if (logger.isDebugEnabled()) {logger.debug(Cannot find .class file for class [ clazz ] - unable to determine constructor/method parameter names);}return NO_DEBUG_INFO_MAP;}try {ClassReader classReader new ClassReader(is);MapExecutable, String[] map new ConcurrentHashMap(32);// ASM 框架提升字节码文件classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);return map;}catch (IOException ex) {if (logger.isDebugEnabled()) {logger.debug(Exception thrown while reading .class file for class [ clazz ] - unable to determine constructor/method parameter names, ex);}}catch (IllegalArgumentException ex) {if (logger.isDebugEnabled()) {logger.debug(ASM ClassReader failed to parse class file [ clazz ], probably due to a new Java class file version that isnt supported yet - unable to determine constructor/method parameter names, ex);}}finally {try {is.close();}catch (IOException ex) {}}return NO_DEBUG_INFO_MAP; }走到这一步便是通过 ASM 框架来提升字节码文件获取参数名称了。所以推荐使用 ReuquestParam 并设置 value 属性值这样可以直接获取到参数名避免了 ASM 框架编辑字节码所带来的性能消耗。 DispatcherServlet # processDispatchResult private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,Nullable HandlerExecutionChain mappedHandler, Nullable ModelAndView mv,Nullable 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);}}if (mv ! null !mv.wasCleared()) {// 渲染 ModelAndViewrender(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace(No view rendering, null ModelAndView returned.);}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler ! null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);} }本方法中调用了 render() 方法去渲染视图 DispatcherServlet # render protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {Locale locale (this.localeResolver ! null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName mv.getViewName();if (viewName ! null) {// 根据 viewName 解析出 Viewview resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view null) {throw new ServletException(Could not resolve view with name mv.getViewName() in servlet with name getServletName() );}}else {// No need to lookup: the ModelAndView object contains the actual View object.view mv.getView();if (view null) {throw new ServletException(ModelAndView [ mv ] neither contains a view name nor a View object in servlet with name getServletName() );}}// Delegate to the View object for rendering.if (logger.isTraceEnabled()) {logger.trace(Rendering view [ view ] );}try {if (mv.getStatus() ! null) {response.setStatus(mv.getStatus().value());}view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug(Error rendering view [ view ], ex);}throw ex;} }本方法中调用了 resolveViewName() 去获取 View DispatcherServlet # resolveViewName protected View resolveViewName(String viewName, Nullable MapString, Object model,Locale locale, HttpServletRequest request) throws Exception {if (this.viewResolvers ! null) {for (ViewResolver viewResolver : this.viewResolvers) {// 使用 ViewResolver 去解析出 ViewView view viewResolver.resolveViewName(viewName, locale);if (view ! null) {return view;}}}return null; }本方法中找到合适的 ViewResolver 去解析出 View public View resolveViewName(String viewName, Locale locale) throws Exception {// 是否启用了缓存功能默认启用了if (!isCache()) {return createView(viewName, locale);}else {// 先尝试从缓存中取 ViewObject cacheKey getCacheKey(viewName, locale);View view this.viewAccessCache.get(cacheKey);if (view null) {synchronized (this.viewCreationCache) {view this.viewCreationCache.get(cacheKey);if (view null) {// 缓存中没有去到交给子类去创建 Viewview createView(viewName, locale);if (view null this.cacheUnresolved) {view UNRESOLVED_VIEW;}if (view ! null this.cacheFilter.filter(view, viewName, locale)) {this.viewAccessCache.put(cacheKey, view);this.viewCreationCache.put(cacheKey, view);}}}}else {if (logger.isTraceEnabled()) {logger.trace(formatKey(cacheKey) served from cache);}}return (view ! UNRESOLVED_VIEW ? view : null);} }本方法中判断是否开启了视图缓存功能默认是开启了的。先尝试从缓存中取 View取不到的话则交给子类去创建 View。 UrlBasedViewResolver # createView Override protected View createView(String viewName, Locale locale) throws Exception {// 判断该视图解析器是否能处理这个视图// 如果不能的话交给其他视图解析器去处理if (!canHandle(viewName, locale)) {return null;}// 检查前缀是否为 redirect:// 有的话则是请求重定向if (viewName.startsWith(REDIRECT_URL_PREFIX)) {// 截取 redirect: 后的字符串String redirectUrl viewName.substring(REDIRECT_URL_PREFIX.length());// 创建重定视图RedirectView view new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible());String[] hosts getRedirectHosts();if (hosts ! null) {view.setHosts(hosts);}return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);}// 检查前缀是否为 forward:// 有的话则是请求转发if (viewName.startsWith(FORWARD_URL_PREFIX)) {String forwardUrl viewName.substring(FORWARD_URL_PREFIX.length());InternalResourceView view new InternalResourceView(forwardUrl);return applyLifecycleMethods(FORWARD_URL_PREFIX, view);}// 如果没有加这两个前缀的话则回调给父类处理// 其实最终的处理结果是跟请求转发一样的return super.createView(viewName, locale); }该方法中会获取字符串前缀。前缀为 “redirect:” 则是请求重定向为 “forward:” 则是请求转发。如果没有这两个前缀的话又回调到父类 AbstractCachingViewResolver # createView 方法中。 AbstractCachingViewResolver # createView protected View createView(String viewName, Locale locale) throws Exception {return loadView(viewName, locale); }回调到父类 AbstractCachingViewResolver # createView 方法并调用了 loadView() 方法 InternalResourceViewResolver # buildView protected AbstractUrlBasedView buildView(String viewName) throws Exception {// 可以看到这里创建的 View 类型跟请求转发的相同InternalResourceView view (InternalResourceView) super.buildView(viewName);if (this.alwaysInclude ! null) {view.setAlwaysInclude(this.alwaysInclude);}view.setPreventDispatchLoop(true);return view; }本方法中调用了 buildView() 方法创建 View可以看到这里创建的 View 类型跟请求转发的相同 UrlBasedViewResolver # buildView protected AbstractUrlBasedView buildView(String viewName) throws Exception {Class? viewClass getViewClass();Assert.state(viewClass ! null, No view class);AbstractUrlBasedView view (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);// 拼接上前缀和后缀// 可以从 web.xml 文件中获取view.setUrl(getPrefix() viewName getSuffix());view.setAttributesMap(getAttributesMap());String contentType getContentType();if (contentType ! null) {view.setContentType(contentType);}String requestContextAttribute getRequestContextAttribute();if (requestContextAttribute ! null) {view.setRequestContextAttribute(requestContextAttribute);}Boolean exposePathVariables getExposePathVariables();if (exposePathVariables ! null) {view.setExposePathVariables(exposePathVariables);}Boolean exposeContextBeansAsAttributes getExposeContextBeansAsAttributes();if (exposeContextBeansAsAttributes ! null) {view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);}String[] exposedContextBeanNames getExposedContextBeanNames();if (exposedContextBeanNames ! null) {view.setExposedContextBeanNames(exposedContextBeanNames);}return view; }本方法中将 viewName 拼接了前缀和后缀获取到的便是最终的完整路径。这里并将 View 获取成功了。 AbstractView # render public void render(Nullable MapString, ? model, HttpServletRequest request,HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {logger.debug(View formatViewName() , model (model ! null ? model : Collections.emptyMap()) (this.staticAttributes.isEmpty() ? : , static attributes this.staticAttributes));}// 创建并合并属性到 Map 中// 如保存到 ModelAndView 或 Model 中的属性MapString, Object mergedModel createMergedOutputModel(model, request, response);// 准备好 HttpServletResponse 对象prepareResponse(request, response);// 解析出视图renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }该方法中主要是将我们自己保存到 ModelAndView 或 Model 中的属性转换为 Map 对象。最后调用 renderMergedOutputModel() 方法。 InternalResourceView # renderMergedOutputModel protected void renderMergedOutputModel(MapString, Object model, HttpServletRequest request, HttpServletResponse response) throws Exception {// 将属性保存到 request 域中exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// 确定请求的 urlString dispatcherPath prepareForRendering(request, response);// 获取一个请求转发器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!);}// 是一个 include 请求jsp 中经常用到实现 jsp 页面的复用if (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug(Including [ getUrl() ]);}rd.include(request, response);}else {if (logger.isDebugEnabled()) {logger.debug(Forwarding to [ getUrl() ]);}// 请求转发rd.forward(request, response);} }4. 总结 SpringMVC 处理请求的大致流程就是这样了。内容有点多建议最好还是自己 debug 走一遍代码梳理出整个脉络才好。
http://www.zqtcl.cn/news/458030/

相关文章:

  • 高端网站哪种好WordPress媒体库丢失
  • 澄迈网站新闻建设宣传视频
  • 南昌优化网站排名公司建设网站的步骤
  • 一个人做网站wordpress如何加链接
  • 查网站服务器所在地笔记本电脑安装wordpress
  • 石家庄网站推广专家php网站分类目录源码
  • 盐城市城乡建设局门户网站低代码开发软件
  • 网站建设中的html深圳建设网站需要多少钱
  • 南阳公司网站制作品牌推广工作内容
  • 网站被刷流量怎么办红色php企业网站模板下载
  • 做现货黄金的金融网站设计平台app
  • 淘宝客手机网站搭建网站设计专业公司
  • 做网站用的图片怎样压缩钓鱼网站的制作教程
  • 建设网站类型wordpress竖版图片尺寸
  • 网站建设数据库ER图怎么画公司网站建设建议书
  • 网站建设网站制作有限排名优化课程
  • 绵竹网站建设佛山网络营销推广
  • 网站备案名称重复学会网站建设目的
  • 网站套餐到期什么意思孝感的网站建设
  • 网站制作费用多少钱房地产建筑设计公司
  • 网站优化要素做网站看百度脸色
  • 软件开发 网站开发区别seo怎么刷关键词排名
  • python 网站开发必会智能网站
  • 重庆建设摩托车官方网站网络是干什么的
  • 建筑工程网站源码wordpress 多域名 图片不显示
  • 大型网站建设优化排名wordpress 投稿 插件
  • 二维码的网站如何做静安免费网站制作
  • 微网站免费模板管理网络的网站
  • 网站下载软件政企网站建设
  • 网站设计为什么要域名北京移动端网站设计