zoukankan      html  css  js  c++  java
  • 第四章:(7)Web开发 之 配置嵌入式 Servlet 容器

    一、配置嵌入式 Servlet 容器

      SpringBoot默认使用Tomcat作为嵌入式的Servlet容器

      引发问题?

      1、如何定制和修改Servlet容器的相关配置?

      2、注册Servlet三大组件【ServletFilterListener】?

    二、如何定制和修改Servlet容器的相关配置

      1、修改和 server 有关的配置(与 ServerProperties 类相对应)

    server.port=8081
    server.context‐path=/crud
    server.tomcat.uri‐encoding=UTF‐8
    
    //通用的Servlet容器设置
    server.xxx
    
    //Tomcat的设置
    server.tomcat.xxx

      2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置

    @Configuration
    public class MyMvcConfig extends WebMvcConfigurerAdapter {
    
        @Bean  //一定要将这个定制器加入到容器中
        public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
            return new EmbeddedServletContainerCustomizer() {
    
                //定制嵌入式的Servlet容器相关的规则
                @Override
                public void customize(ConfigurableEmbeddedServletContainer container) {
                    container.setPort(8083);
                }
            };
        }
    }

      其实这两种方式本质是一样的,ServerProperties 类也实现了 EmbededServletContainerCustomizer 接口,重写了 customize 方法,然后把配置文件中的值设置给 container。

    三、注册Servlet三大组件【ServletFilterListener

      由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBootweb应用,没有web.xml文件。

      注册三大组件:

      (1)注册 servlet

    public class MyServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req,resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().write("Hello, MyServlet!");
        }
    }
    
    ---
        @Bean
        public ServletRegistrationBean myServlet() {
            ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
            servletRegistrationBean.setLoadOnStartup(1);
            return servletRegistrationBean;
        }

      (2)注册 filter

    public class MyFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("MyFilter...process");
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    ---
        @Bean
        public FilterRegistrationBean myFilter() {
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            filterRegistrationBean.setFilter(new MyFilter());
            filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
            return filterRegistrationBean;
        }

      (3)注册 listener

    public class MyListener implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            System.out.println("contextInitialized... Web 应用启动");
    
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            System.out.println("contextDestroyed... Web 应用销毁");
        }
    }
    
    ---
        @Bean
        public ServletListenerRegistrationBean myListener() {
            ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
            return servletListenerRegistrationBean;
        }

      SpringBoot 中应用

      SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet

      DispatcherServletAutoConfiguration中:

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public ServletRegistrationBean dispatcherServletRegistration(
          DispatcherServlet dispatcherServlet) {
       ServletRegistrationBean registration = new ServletRegistrationBean(
             dispatcherServlet, this.serverProperties.getServletMapping());
        //默认拦截: /  所有请求;包静态资源,但是不拦截jsp请求;   /*会拦截jsp
        //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
        
       registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
       registration.setLoadOnStartup(
             this.webMvcProperties.getServlet().getLoadOnStartup());
       if (this.multipartConfig != null) {
          registration.setMultipartConfig(this.multipartConfig);
       }
       return registration;
    }

    四、切换嵌入式 Servlet 容器

      1、默认使用 Tomcat  容器

        Tomcat 默认使用:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
    </dependency>

        SpringBoot 提供了同时还提供了 Jetty(长连接) 和 Undertow(不支持JSP),可以根据场景进行切换。

        

      2、切换为 Jetty

    <!-- 引入web模块 -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <exclusions>
          <exclusion>
             <artifactId>spring-boot-starter-tomcat</artifactId>
             <groupId>org.springframework.boot</groupId>
          </exclusion>
       </exclusions>
    </dependency>
    
    <!--引入其他的Servlet容器-->
    <dependency>
       <artifactId>spring-boot-starter-jetty</artifactId>
       <groupId>org.springframework.boot</groupId>
    </dependency>

      3、切换为 Undertow

    <!-- 引入web模块 -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <exclusions>
          <exclusion>
             <artifactId>spring-boot-starter-tomcat</artifactId>
             <groupId>org.springframework.boot</groupId>
          </exclusion>
       </exclusions>
    </dependency>
    
    <!--引入其他的Servlet容器-->
    <dependency>
       <artifactId>spring-boot-starter-undertow</artifactId>
       <groupId>org.springframework.boot</groupId>
    </dependency>

    五、嵌入式Servlet容器自动配置原理

       SpringBoot 是如何做到嵌入式 Servlet 容器的自动配置呢?

      参照 EmbeddedServletContainerAutoConfiguration 类 嵌入式的Servlet容器自动配置

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @ConditionalOnWebApplication
    @Import(BeanPostProcessorsRegistrar.class)
    //导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
    //导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
    //后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
    public class EmbeddedServletContainerAutoConfiguration {
        
    
        /**
         * Nested configuration if Tomcat is being used.
         */
        @Configuration
        @ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
        @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
        public static class EmbeddedTomcat {
    
            @Bean
            public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
                return new TomcatEmbeddedServletContainerFactory();
            }
    
        }
        
        /**
         * Nested configuration if Jetty is being used.
         */
        @Configuration
        @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
                WebAppContext.class })
        @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
        public static class EmbeddedJetty {
    
            @Bean
            public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
                return new JettyEmbeddedServletContainerFactory();
            }
    
        }
    
        /**
         * Nested configuration if Undertow is being used.
         */
        @Configuration
        @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
        @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
        public static class EmbeddedUndertow {
    
            @Bean
            public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
                return new UndertowEmbeddedServletContainerFactory();
            }
    
        }

      1、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)

    public interface EmbeddedServletContainerFactory {
    
       //获取嵌入式的Servlet容器
       EmbeddedServletContainer getEmbeddedServletContainer(
             ServletContextInitializer... initializers);
    
    }

        SpringBoot 提供的嵌入式 Servlet 容器工厂:

        

         如果当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂,SpringBoot 就会根据当前依赖创建一个 Servlet容器工厂,每一个嵌入式 Servlet 容器工厂都会创建对应的 servlet 容器;

      2、EmbeddedServletContainer:(嵌入式的Servlet容器)

    public interface EmbeddedServletContainer {
    
        void start() throws EmbeddedServletContainerException;
    
        void stop() throws EmbeddedServletContainerException;
    
        int getPort();
    
    }

        SpringBoot 提供的嵌入的 servlet 容器:

        

      3、SpringBoot 是如何自动配置 servlet 容器的呢?

        TomcatEmbeddedServletContainerFactory为例:

        (1)当导入 Tomcat 相关的依赖后,会创建一个 TomcatEmbeddedServletContainerFactory

        /**
         * Nested configuration if Tomcat is being used.
         */
        @Configuration
        @ConditionalOnClass({ Servlet.class, Tomcat.class })
        @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
        public static class EmbeddedTomcat {
    
            @Bean
            public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
                return new TomcatEmbeddedServletContainerFactory();
            }
    
        }

        (2)TomcatEmbeddedServletContainerFactory 类

          Servlet容器工厂会创建一个对应的 Servlet 容器

    public class TomcatEmbeddedServletContainerFactory
            extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    
        @Override
        public EmbeddedServletContainer getEmbeddedServletContainer(
                ServletContextInitializer... initializers) {
                    //创建一个Tomcat
            Tomcat tomcat = new Tomcat();
    
                    //配置Tomcat的基本环节
            File baseDir = (this.baseDirectory != null ? this.baseDirectory
                    : createTempDir("tomcat"));
            tomcat.setBaseDir(baseDir.getAbsolutePath());
            Connector connector = new Connector(this.protocol);
            tomcat.getService().addConnector(connector);
            customizeConnector(connector);
            tomcat.setConnector(connector);
            tomcat.getHost().setAutoDeploy(false);
            configureEngine(tomcat.getEngine());
            for (Connector additionalConnector : this.additionalTomcatConnectors) {
                tomcat.getService().addConnector(additionalConnector);
            }
            prepareContext(tomcat.getHost(), initializers);
    
            //将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器
            return getTomcatEmbeddedServletContainer(tomcat);
        }
    
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
        }
    }

        (3)TomcatEmbeddedServletContainer 类

          TomcatEmbeddedServletContainer :创建内置Tomcat的servlet容器,并启动容器

    public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
    
        public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
            Assert.notNull(tomcat, "Tomcat Server must not be null");
            this.tomcat = tomcat;
            this.autoStart = autoStart;
            initialize();
        }
    
        private void initialize() throws EmbeddedServletContainerException {
            TomcatEmbeddedServletContainer.logger
                    .info("Tomcat initialized with port(s): " + getPortsDescription(false));
            synchronized (this.monitor) {
                try {
                    addInstanceIdToEngineName();
                    try {
                        // Remove service connectors to that protocol binding doesn't happen
                        // yet
                        removeServiceConnectors();
    
                        // Start the server to trigger initialization listeners
                        this.tomcat.start();
    
                        // We can re-throw failure exception directly in the main thread
                        rethrowDeferredStartupExceptions();
    
                        Context context = findContext();
                        try {
                            ContextBindings.bindClassLoader(context, getNamingToken(context),
                                    getClass().getClassLoader());
                        }
                        catch (NamingException ex) {
                            // Naming is not enabled. Continue
                        }
    
                        // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                        // blocking non-daemon to stop immediate shutdown
                        startDaemonAwaitThread();
                    }
                    catch (Exception ex) {
                        containerCounter.decrementAndGet();
                        throw ex;
                    }
                }
                catch (Exception ex) {
                    throw new EmbeddedServletContainerException(
                            "Unable to start embedded Tomcat", ex);
                }
            }
        }
    
    }

        所以只要在项目中导入对应的 servlet 容器的依赖,就可以创建不同的 servlet 容器。

      4、我们对嵌入式容器的配置修改是怎么生效?

        方式一:在配置文件中进行修改,其实是与 ServerProperties 绑定的;

          其实 ServerProperties 也是 EmbeddedServletContainerCustomizer 下的接口,并且重写了 customize 方法:

          

          

        方式二:配置一个 EmbeddedServletContainerCustomizer 组件;

        //一定要将这个定制器加入到容器中
        //配置嵌入式的 servlet 容器
        @Bean
        public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
            return new EmbeddedServletContainerCustomizer() {
    
                //定制嵌入式的Servlet容器相关的规则
                @Override
                public void customize(ConfigurableEmbeddedServletContainer container) {
                    container.setPort(8083);
                }
            };
        }

        其实都是使用 EmbeddedServletContainerCustomizer 定制器帮我们修改了Servlet容器的配置

      5、那么配置是怎么修改的呢?

        容器中导入了 EmbeddedServletContainerCustomizerBeanPostProcessor

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @ConditionalOnWebApplication
    @Import(BeanPostProcessorsRegistrar.class)
    //导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
    //导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
    //后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
    public class EmbeddedServletContainerAutoConfiguration {}

        来看一下 BeanPostProcessorsRegistrar 给容器中导入了什么组件?

        /**
         * Registers a {@link EmbeddedServletContainerCustomizerBeanPostProcessor}. Registered
         * via {@link ImportBeanDefinitionRegistrar} for early registration.
         */
        public static class BeanPostProcessorsRegistrar
                implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
    
            private ConfigurableListableBeanFactory beanFactory;
    
            @Override
            public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
                if (beanFactory instanceof ConfigurableListableBeanFactory) {
                    this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
                }
            }
    
            @Override
            public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                    BeanDefinitionRegistry registry) {
                if (this.beanFactory == null) {
                    return;
                }
                registerSyntheticBeanIfMissing(registry,
                        "embeddedServletContainerCustomizerBeanPostProcessor",
                        EmbeddedServletContainerCustomizerBeanPostProcessor.class);
                registerSyntheticBeanIfMissing(registry,
                        "errorPageRegistrarBeanPostProcessor",
                        ErrorPageRegistrarBeanPostProcessor.class);
            }
    
            private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
                    String name, Class<?> beanClass) {
                if (ObjectUtils.isEmpty(
                        this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                    RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                    beanDefinition.setSynthetic(true);
                    registry.registerBeanDefinition(name, beanDefinition);
                }
            }
    
        }

        可以看到给容器中注册了 EmbeddedServletContainerCustomizerBeanPostProcessor 和 ErrorPageRegistrarBeanPostProcessor。

        来看一下 EmbeddedServletContainerCustomizerBeanPostProcessor 这个后置处理器的作用:

    public class EmbeddedServletContainerCustomizerBeanPostProcessor
            implements BeanPostProcessor, BeanFactoryAware {
    
        private ListableBeanFactory beanFactory;
    
        private List<EmbeddedServletContainerCustomizer> customizers;
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) {
            Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
                    "EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
                            + "with a ListableBeanFactory");
            this.beanFactory = (ListableBeanFactory) beanFactory;
        }
    
        //初始化之前
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            //如果当前初始化的是一个 ConfigurableEmbeddedServletContainer 类型的组件
            if (bean instanceof ConfigurableEmbeddedServletContainer) {
                postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName)
                throws BeansException {
            return bean;
        }
    
        private void postProcessBeforeInitialization(
                ConfigurableEmbeddedServletContainer bean) {
            //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
            for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
                customizer.customize(bean);
            }
        }
    
        private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
            if (this.customizers == null) {
                // Look up does not include the parent context
                this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                        this.beanFactory
                          //从容器中获取所有这个类型的组件:EmbeddedServletContainerCustomizer
                          //定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
                                .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                        false, false)
                                .values());
                Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
                this.customizers = Collections.unmodifiableList(this.customizers);
            }
            return this.customizers;
        }
    
    }

      6、总结步骤

      (1)SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】,会创建对应的Servlet容器工厂,然后由Servlet容器工厂来创建对应的servlet容器;

      (2)容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;

          只要是嵌入式的Servlet容器工厂,后置处理器就工作;

      (3)后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法;

     

    六、嵌入式Servlet容器启动原理

      什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat?

      在下面两处打上断点:

       

      1、SpringBoot应用启动运行run方法

        

         

         

      2、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext

      

       (1)创建容器:如果是 web 环境,创建一个org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext 容器,否则创建默认的 org.springframework.context.annotation.AnnotationConfigApplicationContext

      

       (2)刷新容器

      

       

       

      3、refresh(context);刷新刚才创建好的ioc容器;

    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) {
             if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                      "cancelling refresh attempt: " + ex);
             }
    
             // Destroy already created singletons to avoid dangling resources.
             destroyBeans();
    
             // Reset 'active' flag.
             cancelRefresh(ex);
    
             // Propagate exception to caller.
             throw ex;
          }
    
          finally {
             // Reset common introspection caches in Spring's core, since we
             // might not ever need metadata for singleton beans anymore...
             resetCommonCaches();
          }
       }
    }

      4、onRefresh():web的ioc容器重写了onRefresh方法

      5、web的 Ioc 容器会创建嵌入式的Servlet容器:createEmbeddedServletContainer();

        

      6、获取嵌入式的Servlet容器工厂:

    EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
    

        

        从ioc容器中获取EmbeddedServletContainerFactory 组件;

        

        TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

      7、使用容器工厂获取嵌入式的Servlet容器

          

    this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());

      8、嵌入式的Servlet容器创建对象并启动Servlet容器;

        

         

      9、小结

        先启动嵌入式的Servlet容器,再将 ioc 容器中剩下没有创建出的对象获取出来;

        IOC容器启动创建嵌入式的Servlet容器

  • 相关阅读:
    (三)openwrt主Makefile解析
    (二)我的Makefile学习冲动&&编译过程概述
    openwrt修改flash大小
    (一)openwrt源码目录概述
    git_sop 脚本使用说明
    Openwrt LuCI模块练习详细步骤
    openwrt简单ipk生成及Makefile解释
    oracle中比较两表表结构差异和数据差异的方法
    C#泛型集合之Dictionary<k, v>使用技巧
    SQL语句添加,删除主键
  • 原文地址:https://www.cnblogs.com/niujifei/p/15679363.html
Copyright © 2011-2022 走看看