热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

深入讲解springboot中servlet的启动过程与原理

这篇文章主要给大家介绍了关于springboot中servlet启动过程与原理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

本文主要介绍了关于spring boot中servlet启动过程与原理的相关内容,下面话不多说了,来一起看看详细的介绍吧

启动过程与原理:

1 spring boot 应用启动运行run方法

StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext cOntext= null;
 FailureAnalyzers analyzers = null;
 configureHeadlessProperty();
 SpringApplicationRunListeners listeners = getRunListeners(args);
 listeners.starting();
 try {
  ApplicationArguments applicatiOnArguments= new DefaultApplicationArguments(
   args);
  ConfigurableEnvironment envirOnment= prepareEnvironment(listeners,
   applicationArguments);
  Banner printedBanner = printBanner(environment);
   //创建一个ApplicationContext容器
  cOntext= createApplicationContext();
  analyzers = new FailureAnalyzers(context);
  prepareContext(context, environment, listeners, applicationArguments,
   printedBanner);
   //刷新IOC容器
  refreshContext(context);
  afterRefresh(context, applicationArguments);
  listeners.finished(context, null);
  stopWatch.stop();
  if (this.logStartupInfo) {
  new StartupInfoLogger(this.mainApplicationClass)
   .logStarted(getApplicationLog(), stopWatch);
  }
  return context;
 }
 catch (Throwable ex) {
  handleRunFailure(context, listeners, analyzers, ex);
  throw new IllegalStateException(ex);
 }

2  createApplicationContext():创建IOC容器,如果是web应用则创建AnnotationConfigEmbeddedWebApplacation的IOC容器,如果不是,则创建AnnotationConfigApplication的IOC容器

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
  + "annotation.AnnotationConfigApplicationContext";

 /**
 * The class name of application context that will be used by default for web
 * environments.
 */
 public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
  + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";


protected ConfigurableApplicationContext createApplicationContext() {
 Class<&#63;> cOntextClass= this.applicationContextClass;
 if (cOntextClass== null) {
  try {
          //根据应用环境,创建不同的IOC容器
  cOntextClass= Class.forName(this.webEnvironment
   &#63; DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
  }
  catch (ClassNotFoundException ex) {
  throw new IllegalStateException(
   "Unable create a default ApplicationContext, "
    + "please specify an ApplicationContextClass",
   ex);
  }
 }
 return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
 }

3    refreshContext(context) spring boot刷新IOC容器(创建容器对象,并初始化容器,创建容器每一个组件)

private void refreshContext(ConfigurableApplicationContext context) {
 refresh(context);
 if (this.registerShutdownHook) {
  try {
  context.registerShutdownHook();
  }
  catch (AccessControlException ex) {
  // Not allowed in some environments.
  }
 }
 }

4 refresh(context);刷新刚才创建的IOC容器

protected void refresh(ApplicationContext applicationContext) {
 Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
 ((AbstractApplicationContext) applicationContext).refresh();
 }

5 调用父类的refresh()的方法

public void refresh() throws BeansException, IllegalStateException {
 Object var1 = this.startupShutdownMonitor;
 synchronized(this.startupShutdownMonitor) {
  this.prepareRefresh();
  ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
  this.prepareBeanFactory(beanFactory);

  try {
  this.postProcessBeanFactory(beanFactory);
  this.invokeBeanFactoryPostProcessors(beanFactory);
  this.registerBeanPostProcessors(beanFactory);
  this.initMessageSource();
  this.initApplicationEventMulticaster();
  this.onRefresh();
  this.registerListeners();
  this.finishBeanFactoryInitialization(beanFactory);
  this.finishRefresh();
  } catch (BeansException var9) {
  if (this.logger.isWarnEnabled()) {
   this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
  }

  this.destroyBeans();
  this.cancelRefresh(var9);
  throw var9;
  } finally {
  this.resetCommonCaches();
  }

 }
 }

6  抽象父类AbstractApplicationContext类的子类EmbeddedWebApplicationContext的onRefresh方法

@Override
 protected void onRefresh() {
 super.onRefresh();
 try {
  createEmbeddedServletContainer();
 }
 catch (Throwable ex) {
  throw new ApplicationContextException("Unable to start embedded container",
   ex);
 }
 }

7  在createEmbeddedServletContainer放啊发中会获取嵌入式Servlet容器工厂,由容器工厂创建Servlet

private void createEmbeddedServletContainer() {
 EmbeddedServletContainer localCOntainer= this.embeddedServletContainer;
 ServletContext localServletCOntext= getServletContext();
 if (localCOntainer== null && localServletCOntext== null) {
                //获取嵌入式Servlet容器工厂
  EmbeddedServletContainerFactory cOntainerFactory= getEmbeddedServletContainerFactory();
          //根据容器工厂获取对应嵌入式Servlet容器
  this.embeddedServletCOntainer= containerFactory
   .getEmbeddedServletContainer(getSelfInitializer());
 }
 else if (localServletContext != null) {
  try {
  getSelfInitializer().onStartup(localServletContext);
  }
  catch (ServletException ex) {
  throw new ApplicationContextException("Cannot initialize servlet context",
   ex);
  }
 }
 initPropertySources();
 }

8  从IOC容器中获取Servlet容器工厂

//EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory 
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() { 
 // Use bean names so that we don't consider the hierarchy 
 String[] beanNames = getBeanFactory() 
 .getBeanNamesForType(EmbeddedServletContainerFactory.class); 
 if (beanNames.length == 0) { 
 throw new ApplicationContextException( 
  "Unable to start EmbeddedWebApplicationContext due to missing " 
  + "EmbeddedServletContainerFactory bean."); 
 } 
 if (beanNames.length > 1) { 
 throw new ApplicationContextException( 
  "Unable to start EmbeddedWebApplicationContext due to multiple " 
  + "EmbeddedServletContainerFactory beans : " 
  + StringUtils.arrayToCommaDelimitedString(beanNames)); 
 } 
 return getBeanFactory().getBean(beanNames[0], 
     EmbeddedServletContainerFactory.class); 
}

9  使用Servlet容器工厂获取嵌入式Servlet容器,具体使用哪一个容器工厂看配置环境依赖

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

10  上述创建过程  首先启动IOC容器,接着启动嵌入式Servlet容器,接着将IOC容器中剩下没有创建的对象获取出来,比如自己创建的controller

// Instantiate all remaining (non-lazy-init) singletons.
  finishBeanFactoryInitialization(beanFactory);
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
 // Initialize conversion service for this context.
 if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
  beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
  beanFactory.setConversionService(
   beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
 }

 // Register a default embedded value resolver if no bean post-processor
 // (such as a PropertyPlaceholderConfigurer bean) registered any before:
 // at this point, primarily for resolution in annotation attribute values.
 if (!beanFactory.hasEmbeddedValueResolver()) {
  beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
  @Override
  public String resolveStringValue(String strVal) {
   return getEnvironment().resolvePlaceholders(strVal);
  }
  });
 }

 // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
 String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
 for (String weaverAwareName : weaverAwareNames) {
  getBean(weaverAwareName);
 }

 // Stop using the temporary ClassLoader for type matching.
 beanFactory.setTempClassLoader(null);

 // Allow for caching all bean definition metadata, not expecting further changes.
 beanFactory.freezeConfiguration();

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

看看 preInstantiateSingletons方法

public void preInstantiateSingletons() throws BeansException {
  if (this.logger.isDebugEnabled()) {
   this.logger.debug("Pre-instantiating singletons in " + this);
  }

  List beanNames = new ArrayList(this.beanDefinitionNames);
  Iterator var2 = beanNames.iterator();

  while(true) {
   while(true) {
    String beanName;
    RootBeanDefinition bd;
    do {
     do {
      do {
       if (!var2.hasNext()) {
        var2 = beanNames.iterator();

        while(var2.hasNext()) {
         beanName = (String)var2.next();
         Object singletOnInstance= this.getSingleton(beanName);
         if (singletonInstance instanceof SmartInitializingSingleton) {
          final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton)singletonInstance;
          if (System.getSecurityManager() != null) {
           AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
             smartSingleton.afterSingletonsInstantiated();
             return null;
            }
           }, this.getAccessControlContext());
          } else {
           smartSingleton.afterSingletonsInstantiated();
          }
         }
        }

        return;
       }

       beanName = (String)var2.next();
       bd = this.getMergedLocalBeanDefinition(beanName);
      } while(bd.isAbstract());
     } while(!bd.isSingleton());
    } while(bd.isLazyInit());

    if (this.isFactoryBean(beanName)) {
     final FactoryBean<&#63;> factory = (FactoryBean)this.getBean("&" + beanName);
     boolean isEagerInit;
     if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
      isEagerInit = ((Boolean)AccessController.doPrivileged(new PrivilegedAction() {
       public Boolean run() {
        return ((SmartFactoryBean)factory).isEagerInit();
       }
      }, this.getAccessControlContext())).booleanValue();
     } else {
      isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean)factory).isEagerInit();
     }

     if (isEagerInit) {
      this.getBean(beanName);
     }
    } else {
            //注册bean
     this.getBean(beanName);
    }
   }
  }
 }

是使用getBean方法来通过反射将所有未创建的实例创建出来

  使用嵌入式Servlet容器:

     优点:   简单,便携

     缺点:   默认不支持jsp,优化定制比较复杂

使用外置Servlet容器的步骤:

  1  必须创建war项目,需要剑豪web项目的目录结构

  2  嵌入式Tomcat依赖scope指定provided

  3  编写SpringBootServletInitializer类子类,并重写configure方法

public class ServletInitializer extends SpringBootServletInitializer { 
 
 @Override 
 protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 
  return application.sources(SpringBoot04WebJspApplication.class); 
 } 
}

        4  启动服务器

jar包和war包启动区别

    jar包:执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式Servlet容器

 war包:  先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器

Servlet 3.0+规则

    1  服务器启动(web应用启动),会创建当前web应用里面所有jar包里面的ServletContainerlnitializer实例

     2 ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下

   3  还可以使用@HandlesTypes注解,在应用启动的时候加载指定的类。

外部Tomcat流程以及原理

  ①  启动Tomcat

  ②  根据上述描述的Servlet3.0+规则,可以在Spring的web模块里面找到有个文件名为javax.servlet.ServletContainerInitializer的文件,而文件的内容为org.springframework.web.SpringServletContainerInitializer,用于加载SpringServletContainerInitializer类

  ③看看SpringServletContainerInitializer定义

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

 /**
  * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
  * implementations present on the application classpath.
  * 

Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, * Servlet 3.0+ containers will automatically scan the classpath for implementations * of Spring's {@code WebApplicationInitializer} interface and provide the set of all * such types to the {@code webAppInitializerClasses} parameter of this method. *

If no {@code WebApplicationInitializer} implementations are found on the classpath, * this method is effectively a no-op. An INFO-level log message will be issued notifying * the user that the {@code ServletContainerInitializer} has indeed been invoked but that * no {@code WebApplicationInitializer} implementations were found. *

Assuming that one or more {@code WebApplicationInitializer} types are detected, * they will be instantiated (and sorted if the @{@link * org.springframework.core.annotation.Order @Order} annotation is present or * the {@link org.springframework.core.Ordered Ordered} interface has been * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)} * method will be invoked on each instance, delegating the {@code ServletContext} such * that each instance may register and configure servlets such as Spring's * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener}, * or any other Servlet API componentry such as filters. * @param webAppInitializerClasses all implementations of * {@link WebApplicationInitializer} found on the application classpath * @param servletContext the servlet context to be initialized * @see WebApplicationInitializer#onStartup(ServletContext) * @see AnnotationAwareOrderComparator */ @Override public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List initializers = new LinkedList(); if (webAppInitializerClasses != null) { for (Class<&#63;> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try {                 //为所有的WebApplicationInitializer类型创建实例,并加入集合中 initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers);       //调用每一个WebApplicationInitializer实例的onstartup方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }

 在上面一段长长的注释中可以看到,SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer这个类型的类都传入到onStartup方法的Set参数中,并通过反射为这些WebApplicationInitializer类型的类创建实例;

  ④  方法最后,每一个WebApplicationInitilizer实现调用自己onstartup方法

  ⑤  而WebApplicationInitializer有个抽象实现类SpringBootServletInitializer(记住我们继承了该抽象类),则会调用每一个WebApplicationInitializer实例(包括SpringBootServletInitializer)的onStartup方法:

public abstract class SpringBootServletInitializer implements WebApplicationInitializer { 
 
  //other code... 
   
  @Override 
  public void onStartup(ServletContext servletContext) throws ServletException { 
    // Logger initialization is deferred in case a ordered 
    // LogServletContextInitializer is being used 
    this.logger = LogFactory.getLog(getClass()); 
    //创建IOC容器 
    WebApplicationContext rootAppCOntext= createRootApplicationContext( 
        servletContext); 
    if (rootAppContext != null) { 
      servletContext.addListener(new ContextLoaderListener(rootAppContext) { 
        @Override 
        public void contextInitialized(ServletContextEvent event) { 
          // no-op because the application context is already initialized 
        } 
      }); 
    } 
    else { 
      this.logger.debug("No ContextLoaderListener registered, as " 
          + "createRootApplicationContext() did not " 
          + "return an application context"); 
    } 
  } 
 
  protected WebApplicationContext createRootApplicationContext( 
      ServletContext servletContext) { 
    //创建Spring应用构建器,并进行相关属性设置 
    SpringApplicationBuilder builder = createSpringApplicationBuilder(); 
    StandardServletEnvironment envirOnment= new StandardServletEnvironment(); 
    environment.initPropertySources(servletContext, null); 
    builder.environment(environment); 
    builder.main(getClass()); 
    ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); 
    if (parent != null) { 
      this.logger.info("Root context already created (using as parent)."); 
      servletContext.setAttribute( 
          WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); 
      builder.initializers(new ParentContextApplicationContextInitializer(parent)); 
    } 
    builder.initializers( 
        new ServletContextApplicationContextInitializer(servletContext)); 
    builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); 
     
    //调用configure方法,创建war类型的web项目后,由于编写SpringBootServletInitializer的子类重写configure方法,所以此处调用的是我们定义的子类重写的configure方法 
    builder = configure(builder); 
     
    //通过构建器构建了一个Spring应用 
    SpringApplication application = builder.build(); 
    if (application.getSources().isEmpty() && AnnotationUtils 
        .findAnnotation(getClass(), Configuration.class) != null) { 
      application.getSources().add(getClass()); 
    } 
    Assert.state(!application.getSources().isEmpty(), 
        "No SpringApplication sources have been defined. Either override the " 
            + "configure method or add an @Configuration annotation"); 
    // Ensure error pages are registered 
    if (this.registerErrorPageFilter) { 
      application.getSources().add(ErrorPageFilterConfiguration.class); 
    } 
    //启动Spring应用 
    return run(application); 
  } 
   
  //Spring应用启动,创建并返回IOC容器 
  protected WebApplicationContext run(SpringApplication application) { 
    return (WebApplicationContext) application.run(); 
  }   
}

SpringBootServletInitializer实例执行onStartup方法的时候会通过createRootApplicationContext方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • SpringMVC工作流程概述
    SpringMVC工作流程概述 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 标题: ... [详细]
  • 本文总结了淘淘商城项目的功能和架构,并介绍了传统架构中遇到的session共享问题及解决方法。淘淘商城是一个综合性的B2C平台,类似京东商城、天猫商城,会员可以在商城浏览商品、下订单,管理员、运营可以在平台后台管理系统中管理商品、订单、会员等。商城的架构包括后台管理系统、前台系统、会员系统、订单系统、搜索系统和单点登录系统。在传统架构中,可以采用tomcat集群解决并发量高的问题,但由于session共享的限制,集群数量有限。本文探讨了如何解决session共享的问题。 ... [详细]
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 本文探讨了容器技术在安全方面面临的挑战,并提出了相应的解决方案。多租户保护、用户访问控制、中毒的镜像、验证和加密、容器守护以及容器监控都是容器技术中需要关注的安全问题。通过在虚拟机中运行容器、限制特权升级、使用受信任的镜像库、进行验证和加密、限制容器守护进程的访问以及监控容器栈,可以提高容器技术的安全性。未来,随着容器技术的发展,还需解决诸如硬件支持、软件定义基础设施集成等挑战。 ... [详细]
  • 在IDEA中运行CAS服务器的配置方法
    本文介绍了在IDEA中运行CAS服务器的配置方法,包括下载CAS模板Overlay Template、解压并添加项目、配置tomcat、运行CAS服务器等步骤。通过本文的指导,读者可以轻松在IDEA中进行CAS服务器的运行和配置。 ... [详细]
author-avatar
dajiang
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有