`
yinwufeng
  • 浏览: 277149 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论
阅读更多

这两天利用空余时间学习了webx3.0,基于spring mvc开发的一款mvc;由于对webx2.0以及spring mvc没有进行过深入的研究,在学习webx3.0的时候,肯定会出现理解上的偏差甚至错误,希望大家积极提出,有问题才是进步的动力;以前很少写文章,文笔不好,还请大家见谅,:)!无废话,开始:

Web.xml,tomcat加载war包开始:

Xml代码 复制代码
  1. <!-- 装载/WEB-INF/webx.xml, /WEB-INF/webx-*.xml -->  
  2.  <listener>     
  3.  <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class>  
  4. </listener>  
<!-- 装载/WEB-INF/webx.xml, /WEB-INF/webx-*.xml -->
 <listener>  
 <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class>
</listener>
 

其中WebxContextLoaderListener 信息如下:

 

Java代码 复制代码
  1. import org.springframework.web.context.ContextLoader;   
  2. import org.springframework.web.context.ContextLoaderListener;   
  3.   
  4. public class WebxContextLoaderListener extends ContextLoaderListener {   
  5. ……  
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener;

public class WebxContextLoaderListener extends ContextLoaderListener {
……
 

WebxContextLoaderListener继承了spring mvc的监听类:ContextLoaderListener;

 

可想而知,容器启动时,将调用: contextInitialized(ServletContextEvent event)  该方法,如下:

Java代码 复制代码
  1. public void contextInitialized(ServletContextEvent event) {   
  2.                 //1.初始化加载类   
  3.         this.contextLoader = createContextLoader();   
  4.                //2.出示话web上下文   
  5.         this.contextLoader.initWebApplicationContext(event.getServletContext());   
  6.     }  
public void contextInitialized(ServletContextEvent event) {
                //1.初始化加载类
		this.contextLoader = createContextLoader();
               //2.出示话web上下文
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

 

由于WebxContextLoaderListener覆盖了createContextLoader()方法,

Java代码 复制代码
  1. @Override  
  2.    protected final ContextLoader createContextLoader() {   
  3.        return new WebxComponentsLoader() {   
  4.   
  5.            @Override  
  6.            protected Class<?> getDefaultContextClass() {   
  7.                Class<?> defaultContextClass = WebxContextLoaderListener.this.getDefaultContextClass();   
  8.   
  9.                if (defaultContextClass == null) {   
  10.                    defaultContextClass = super.getDefaultContextClass();   
  11.                }   
  12.   
  13.                return defaultContextClass;   
  14.            }   
  15.        };   
  16.    }  
 @Override
    protected final ContextLoader createContextLoader() {
        return new WebxComponentsLoader() {

            @Override
            protected Class<?> getDefaultContextClass() {
                Class<?> defaultContextClass = WebxContextLoaderListener.this.getDefaultContextClass();

                if (defaultContextClass == null) {
                    defaultContextClass = super.getDefaultContextClass();
                }

                return defaultContextClass;
            }
        };
    }
 

contextLoader已经是这个了:WebxComponentsLoader 重点关注这个类,

 

Java代码 复制代码
  1. public class WebxComponentsLoader extends ContextLoader  
public class WebxComponentsLoader extends ContextLoader

 

将负责加载webx中的componet组件,还继承了spring mvc的加载类 ContextLoader,


  所以initWebApplicationContext 就在子类WebxComponentsLoader中执行了:

 

Java代码 复制代码
  1. @Override  
  2.    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) throws IllegalStateException,   
  3.            BeansException {   
  4.        this.servletContext = servletContext;   
  5.        init();   
  6.        //开始spring mvc加载…           
  7.        return super.initWebApplicationContext(servletContext);   
  8.    }  
 @Override
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) throws IllegalStateException,
            BeansException {
        this.servletContext = servletContext;
        init();
        //开始spring mvc加载…        
        return super.initWebApplicationContext(servletContext);
    }

  经过自己的init后

  super.initWebApplicationContext(servletContext);

  又回到父类里面去了:

 

Java代码 复制代码
  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext)   
  2.             throws IllegalStateException, BeansException {   
  3.   
  4.                   //.....先忽略   
  5.         long startTime = System.currentTimeMillis();   
  6.   
  7.         try {   
  8.             // Determine parent for root web application context, if any.   
  9.                         //1.先找parent,一般是空的吧   
  10.             ApplicationContext parent = loadParentContext(servletContext);   
  11.   
  12.             // Store context in local instance variable, to guarantee that   
  13.             // it is available on ServletContext shutdown.   
  14.                         //2.创建   
  15.             this.context = createWebApplicationContext(servletContext, parent);   
  16.                         //3.设为root   
  17.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);   
  18.             currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);   
  19.   
  20.             //再忽略...   
  21.             return this.context;   
  22.         }   
  23.         catch (RuntimeException ex) {   
  24.             logger.error("Context initialization failed", ex);   
  25.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);   
  26.             throw ex;   
  27.         }   
  28.         catch (Error err) {   
  29.             logger.error("Context initialization failed", err);   
  30.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);   
  31.             throw err;   
  32.         }   
  33.     }  
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
			throws IllegalStateException, BeansException {

                  //.....先忽略
		long startTime = System.currentTimeMillis();

		try {
			// Determine parent for root web application context, if any.
                        //1.先找parent,一般是空的吧
			ApplicationContext parent = loadParentContext(servletContext);

			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
                        //2.创建
			this.context = createWebApplicationContext(servletContext, parent);
                        //3.设为root
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
			currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);

			//再忽略...
			return this.context;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

  createWebApplicationContext方法主要包括下面4步:

 1.找到上下文类

Java代码 复制代码
  1. Class contextClass = determineContextClass(servletContext);  
Class contextClass = determineContextClass(servletContext);

 定位上下文class,这个class可配置;这个方法被WebxContextLoaderListener覆盖,最后返回的class:

return WebxComponentsContext.class;

第二个重要的类出现:WebxComponentsContext
WebxComponentsContext

继承

WebxApplicationContext(webx重写)

继承

XmlWebApplicationContext(spring原生)

 

 

2.实例化上下文类

Java代码 复制代码
  1. ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);   
  2. wac.setParent(parent);   
  3. wac.setServletContext(servletContext);     
  4. wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));  
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setServletContext(servletContext);	
wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));

 

3.可以自定义操作上下文

Java代码 复制代码
  1. customizeContext(servletContext, wac);   
  2. //被子类覆盖  
customizeContext(servletContext, wac);
//被子类覆盖

 此方法也被WebxContextLoaderListener覆盖:

Java代码 复制代码
  1. protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext componentsContext) {   
  2.      this.componentsContext = componentsContext;   
  3.      if (componentsContext instanceof WebxComponentsContext) {   
  4.         // componentsContext就是上面实例化的上下文,铁定是WebxComponentsContext类   
  5.                ((WebxComponentsContext) componentsContext).setLoader(this);   
  6.         //为load webx组件配置文件而准备   
  7.      }   
  8. }  
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext componentsContext) {
     this.componentsContext = componentsContext;
     if (componentsContext instanceof WebxComponentsContext) {
		// componentsContext就是上面实例化的上下文,铁定是WebxComponentsContext类
               ((WebxComponentsContext) componentsContext).setLoader(this);
		//为load webx组件配置文件而准备
     }
}
 

4.初始化IOC容器

Java代码 复制代码
  1. wac.refresh();  
wac.refresh();

 
看到熟悉的refush方法了,ConfigurableWebApplicationContext是没有refush方法,AbstractApplicationContext这个类有refush方法:

Java代码 复制代码
  1. public void refresh() throws BeansException, IllegalStateException {   
  2.         synchronized (this.startupShutdownMonitor) {   
  3.             // Prepare this context for refreshing.   
  4.             prepareRefresh();   
  5.   
  6.             // Tell the subclass to refresh the internal bean factory.   
  7.             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();   
  8.   
  9.             // Prepare the bean factory for use in this context.   
  10.             prepareBeanFactory(beanFactory);   
  11.   
  12.             try {   
  13.                 // Allows post-processing of the bean factory in context subclasses.   
  14.                 postProcessBeanFactory(beanFactory);   
  15.                                 //被覆盖,重点关注   
  16.   
  17.                                 // Invoke factory processors registered as beans in the context.   
  18.                 invokeBeanFactoryPostProcessors(beanFactory);   
  19.   
  20.                 // Register bean processors that intercept bean creation.   
  21.                 registerBeanPostProcessors(beanFactory);   
  22.   
  23.                 // Initialize message source for this context.   
  24.                 initMessageSource();   
  25.   
  26.                 // Initialize event multicaster for this context.   
  27.                 initApplicationEventMulticaster();   
  28.   
  29.                 // Initialize other special beans in specific context subclasses.   
  30.                 onRefresh();   
  31.   
  32.                 // Check for listener beans and register them.   
  33.                 registerListeners();   
  34.   
  35.                 // Instantiate all remaining (non-lazy-init) singletons.   
  36.                 finishBeanFactoryInitialization(beanFactory);   
  37.   
  38.                 // Last step: publish corresponding event.   
  39.                 finishRefresh();   
  40.                                 //被覆盖,重点关注             
  41.                         }   
  42.   
  43.             catch (BeansException ex) {   
  44.                 // Destroy already created singletons to avoid dangling resources.   
  45.                 beanFactory.destroySingletons();   
  46.   
  47.                 // Reset 'active' flag.   
  48.                 cancelRefresh(ex);   
  49.   
  50.                 // Propagate exception to caller.   
  51.                 throw ex;   
  52.             }   
  53.         }   
  54.     }  
public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
                                //被覆盖,重点关注

                                // Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
                                //被覆盖,重点关注			
                        }

			catch (BeansException ex) {
				// Destroy already created singletons to avoid dangling resources.
				beanFactory.destroySingletons();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}
		}
	}
 

postProcessBeanFactory(beanFactory);有覆盖吗?有!

实例化前,webx覆盖了此方法来,进行把webx组件实例放入spring中,怎么放?哪里执行?

Java代码 复制代码
  1. ((WebxComponentsContext) componentsContext).setLoader(this);  
((WebxComponentsContext) componentsContext).setLoader(this);

 这里看出是WebxComponentsLoader来进行这个工作:

Java代码 复制代码
  1. /**  
  2.      * 在创建beanFactory之初被调用。  
  3.      *   
  4.      * @param webxComponentsContext  
  5.      */  
  6.     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {   
  7.         // 由于初始化components依赖于webxConfiguration,而webxConfiguration可能需要由PropertyPlaceholderConfigurer来处理,   
  8.         // 此外,其它有一些BeanFactoryPostProcessors会用到components,   
  9.         // 因此components必须在PropertyPlaceholderConfigurer之后初始化,并在其它BeanFactoryPostProcessors之前初始化。   
  10.         //   
  11.         // 下面创建的WebxComponentsCreator辅助类就是用来确保这个初始化顺序:   
  12.         // 1. PriorityOrdered - PropertyPlaceholderConfigurer   
  13.         // 2. Ordered - WebxComponentsCreator   
  14.         // 3. 普通 - 其它BeanFactoryPostProcessors   
  15.         BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(WebxComponentsCreator.class);   
  16.         builder.addConstructorArgValue(this);   
  17.         BeanDefinition componentsCreator = builder.getBeanDefinition();   
  18.         componentsCreator.setAutowireCandidate(false);   
  19.   
  20.         BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;   
  21.         String name = SpringExtUtil.generateBeanName(WebxComponentsCreator.class.getName(), registry);   
  22.   
  23.         registry.registerBeanDefinition(name, componentsCreator);   
  24.     }  
/**
     * 在创建beanFactory之初被调用。
     * 
     * @param webxComponentsContext
     */
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 由于初始化components依赖于webxConfiguration,而webxConfiguration可能需要由PropertyPlaceholderConfigurer来处理,
        // 此外,其它有一些BeanFactoryPostProcessors会用到components,
        // 因此components必须在PropertyPlaceholderConfigurer之后初始化,并在其它BeanFactoryPostProcessors之前初始化。
        //
        // 下面创建的WebxComponentsCreator辅助类就是用来确保这个初始化顺序:
        // 1. PriorityOrdered - PropertyPlaceholderConfigurer
        // 2. Ordered - WebxComponentsCreator
        // 3. 普通 - 其它BeanFactoryPostProcessors
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(WebxComponentsCreator.class);
        builder.addConstructorArgValue(this);
        BeanDefinition componentsCreator = builder.getBeanDefinition();
        componentsCreator.setAutowireCandidate(false);

        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        String name = SpringExtUtil.generateBeanName(WebxComponentsCreator.class.getName(), registry);

        registry.registerBeanDefinition(name, componentsCreator);
    }
 

一个内部类,WebxComponentsCreator辅助类就是用来确保初始化顺序,见上面:

Java代码 复制代码
  1.  public static class WebxComponentsCreator implements BeanFactoryPostProcessor, Ordered {   
  2.         private final WebxComponentsLoader loader;   
  3.   
  4.         public WebxComponentsCreator(WebxComponentsLoader loader) {   
  5.             this.loader = assertNotNull(loader, "WebxComponentsLoader");   
  6.         }   
  7.   
  8.         public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {   
  9.             if (loader.components == null) {   
  10.                 WebxComponentsImpl components = loader.<SPAN style="BACKGROUND-COLOR: #ffffff; COLOR: #ff0000">createComponents</SPAN>   
  11. (loader.getParentConfiguration(), beanFactory);   
  12.                 AbstractApplicationContext wcc = (AbstractApplicationContext) components.getParentApplicationContext();   
  13.                 wcc.addApplicationListener(new SourceFilteringListener(wcc, components));   
  14.                 loader.components = components;   
  15.             }   
  16.         }   
  17.   
  18.         public int getOrder() {   
  19.             return Ordered.LOWEST_PRECEDENCE;   
  20.         }   
  21.     }  
 public static class WebxComponentsCreator implements BeanFactoryPostProcessor, Ordered {
        private final WebxComponentsLoader loader;

        public WebxComponentsCreator(WebxComponentsLoader loader) {
            this.loader = assertNotNull(loader, "WebxComponentsLoader");
        }

        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            if (loader.components == null) {
                WebxComponentsImpl components = loader.createComponents
(loader.getParentConfiguration(), beanFactory);
                AbstractApplicationContext wcc = (AbstractApplicationContext) components.getParentApplicationContext();
                wcc.addApplicationListener(new SourceFilteringListener(wcc, components));
                loader.components = components;
            }
        }

        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE;
        }
    }

 初始化components:

Java代码 复制代码
  1. /**  
  2.     * 初始化components。  
  3.     */  
  4.    private WebxComponentsImpl createComponents(WebxConfiguration parentConfiguration,   
  5.                                                ConfigurableListableBeanFactory beanFactory) {   
  6.           
  7.        //1.取得一组关于components的配置,也就是component的配置文件,ComponentsConfigImpl中   
  8.        ComponentsConfig componentsConfig = getComponentsConfig(parentConfiguration);   
  9.           
  10.   
  11.   
  12.        // 2.假如isAutoDiscoverComponents==true,试图自动发现components    
  13.        Map<String, String> componentNamesAndLocations = findComponents(componentsConfig, getServletContext());   
  14.   
  15.        // 3.取得特别指定的components   
  16.        Map<String, ComponentConfig> specifiedComponents = componentsConfig.getComponents();   
  17.   
  18.        // 4.实际要初始化的comonents,为上述两种来源的并集   
  19.        Set<String> componentNames = createTreeSet();   
  20.   
  21.        componentNames.addAll(componentNamesAndLocations.keySet());   
  22.        componentNames.addAll(specifiedComponents.keySet());   
  23.   
  24.        // 5.创建root controller   
  25.        WebxRootController rootController = componentsConfig.getRootController();   
  26.   
  27.        if (rootController == null) {   
  28.            rootController = (WebxRootController) BeanUtils.instantiateClass(componentsConfig.getRootControllerClass());   
  29.        }   
  30.   
  31.        // 6.创建并将components对象置入resolvable dependencies,以便注入到需要的bean中   
  32.        WebxComponentsImpl components = new WebxComponentsImpl(componentsContext, componentsConfig   
  33.                .getDefaultComponent(), rootController, parentConfiguration);   
  34.   
  35.        beanFactory.registerResolvableDependency(WebxComponents.class, components);   
  36.   
  37.        // 7.初始化每个component   
  38.        for (String componentName : componentNames) {   
  39.            ComponentConfig componentConfig = specifiedComponents.get(componentName);   
  40.   
  41.            String componentPath = null;   
  42.            WebxController controller = null;   
  43.   
  44.            if (componentConfig != null) {   
  45.                componentPath = componentConfig.getPath();   
  46.                controller = componentConfig.getController();   
  47.            }   
  48.   
  49.            if (controller == null) {   
  50.                controller = (WebxController) BeanUtils.instantiateClass(componentsConfig.getDefaultControllerClass());   
  51.            }   
  52.   
  53.            WebxComponentImpl component = new WebxComponentImpl(components, componentName, componentPath, componentName   
  54.                    .equals(componentsConfig.getDefaultComponent()), controller, getWebxConfigurationName());   
  55.   
  56.            components.addComponent(component);   
  57.   
  58.            prepareComponent(component, componentNamesAndLocations.get(componentName));   
  59.        }   
  60.   
  61.        return components;   
  62.    }  
 /**
     * 初始化components。
     */
    private WebxComponentsImpl createComponents(WebxConfiguration parentConfiguration,
                                                ConfigurableListableBeanFactory beanFactory) {
        
        //1.取得一组关于components的配置,也就是component的配置文件,ComponentsConfigImpl中
        ComponentsConfig componentsConfig = getComponentsConfig(parentConfiguration);
        


        // 2.假如isAutoDiscoverComponents==true,试图自动发现components 
        Map<String, String> componentNamesAndLocations = findComponents(componentsConfig, getServletContext());

        // 3.取得特别指定的components
        Map<String, ComponentConfig> specifiedComponents = componentsConfig.getComponents();

        // 4.实际要初始化的comonents,为上述两种来源的并集
        Set<String> componentNames = createTreeSet();

        componentNames.addAll(componentNamesAndLocations.keySet());
        componentNames.addAll(specifiedComponents.keySet());

        // 5.创建root controller
        WebxRootController rootController = componentsConfig.getRootController();

        if (rootController == null) {
            rootController = (WebxRootController) BeanUtils.instantiateClass(componentsConfig.getRootControllerClass());
        }

        // 6.创建并将components对象置入resolvable dependencies,以便注入到需要的bean中
        WebxComponentsImpl components = new WebxComponentsImpl(componentsContext, componentsConfig
                .getDefaultComponent(), rootController, parentConfiguration);

        beanFactory.registerResolvableDependency(WebxComponents.class, components);

        // 7.初始化每个component
        for (String componentName : componentNames) {
            ComponentConfig componentConfig = specifiedComponents.get(componentName);

            String componentPath = null;
            WebxController controller = null;

            if (componentConfig != null) {
                componentPath = componentConfig.getPath();
                controller = componentConfig.getController();
            }

            if (controller == null) {
                controller = (WebxController) BeanUtils.instantiateClass(componentsConfig.getDefaultControllerClass());
            }

            WebxComponentImpl component = new WebxComponentImpl(components, componentName, componentPath, componentName
                    .equals(componentsConfig.getDefaultComponent()), controller, getWebxConfigurationName());

            components.addComponent(component);

            prepareComponent(component, componentNamesAndLocations.get(componentName));
        }

        return components;
    }

  怎么找到components,只要找到对应配置项即可

Java代码 复制代码
  1. public String getComponentConfigurationLocationPattern() {   
  2.             return componentConfigurationLocationPattern == null ? "/WEB-INF/webx-*.xml"  
  3.                     : componentConfigurationLocationPattern;   
  4.         }   
  5. //找到了配置文件,接下去要解析了,里面实现比较啰嗦,@#¥#@¥@#¥一大堆,返回  
public String getComponentConfigurationLocationPattern() {
            return componentConfigurationLocationPattern == null ? "/WEB-INF/webx-*.xml"
                    : componentConfigurationLocationPattern;
        }
//找到了配置文件,接下去要解析了,里面实现比较啰嗦,@#¥#@¥@#¥一大堆,返回
  引用自:http://thinkinmylife.iteye.com/blog/712885
分享到:
评论
1 楼 programming 2012-04-05  
很蛋痛的webx   工程与jarsource编码不一直,相关文档给的太乱了。

相关推荐

Global site tag (gtag.js) - Google Analytics