深入讲解spring boot中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<?> contextClass = this.applicationContextClass;
 if (contextClass == null) {
  try {
          //根据应用环境,创建不同的IOC容器
  contextClass = Class.forName(this.webEnvironment
   ? 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<String> 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<Object>() {
            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<?> factory = (FactoryBean)this.getBean("&" + beanName);
     boolean isEagerInit;
     if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
      isEagerInit = ((Boolean)AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
       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.
  * <p>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.
  * <p>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.
  * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
  * they will be instantiated (and <em>sorted</em> 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<Class<?>> webAppInitializerClasses, ServletContext servletContext)
   throws ServletException {

  List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

  if (webAppInitializerClasses != null) {
   for (Class<?> 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容器了。

总结

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

(0)

相关推荐

  • 使用Servlet处理一个上传的文件

    Servlet中可以使用post请求上传文件,使用getReader()和getInputStream()自己处理,也可以使用getPart()或getParts()封装了一些功能的方法处理,getParts()可以同时上传多个文件.接下来使用四个Demo来练习一下使用方法 一.使用getReader()和getInputStream() Demo1 <!-- 这是HTML代码块,窗体网页上显示的是一个选择文件的input框和一个upload的button --> <!DOCTYPE h

  • 基于 IntelliJ IDEA 模拟 Servlet 网络请求示例

    最近观看 Android 开发视频,里面使用的集成开发工具为 Eclipse .使用 Eclipse 可以很快捷的编写 Web 项目,而我使用的 Androi Studio 因为专业就把建立其他工程的功能给阉割了.所以,不能忍受只能听老师讲而不能实际操作时望洋兴叹般的尴尬,我选择了使用 IntelliJ IDEA 来替代 Eclipse 模拟网络请求.下面结合一个简单网络请求的实现,来介绍 IntelliJ IDEA 的使用. 首先当然是下载 IntelliJ IDEA 集成工具,这个 Goog

  • IDEA新建javaWeb以及Servlet简单实现小结

    刚开始用IDEA开发,还不太熟悉,因此写一个教程,加深印象 1.新建一个Web项目 两种方法:java 和Java Enterprise(推荐) 第一种)通过Java工程创建,这个方法需要手动导入Tomcat的servlet包才能使用servlet 导入包的方法是创建项目并配置好Tomcat后,File->Project Structure,点击 Modules --> 选中项目"JavaWeb" -->切换到 Dependencies 选项卡 --> 点击右边

  • Servlet实现分页效果

    本文实例为大家分享了Servlet实现分页效果的具体代码,供大家参考,具体内容如下 分页的算法: 需要定义四个变量,它们有各自的用处 int pageSize:每页显示多少条记录 int pageNow:希望显示第几页 int pageCount:一共有多少页 int rowCount:一共有多少条记录 说明: pageSize是指定,pageNow是指用户的选择. rowCount是从表中查询得到的. pageCount是计算出来的,该计算公式为: if(rowCount%pageSize==

  • 基于Eclipse 的JSP/Servlet的开发环境的搭建(图文)

    基于Eclipse 的JSP的开发环境的搭建 说明:顺利地开发.测试和运行JSP程序少不了规范而安全的开发环境,本文将会详细地展示如何搭建JSP的开发环境. 内容详尽,适合零基础学者作为学习参考 . 一.JSP介绍: 1.JSP全称Java Server Pages,是一种动态网页开发技术.它使用JSP标签(通常以<%开头以%>结束)在HTML网页中插入Java代码. 2.JSP是一种Java servlet,主要用于实现Java web应用程序的用户界面部分.网页开发者们通过结合HTML代码

  • AJAX+Servlet实现的数据处理显示功能示例

    本文实例讲述了AJAX+Servlet实现的数据处理显示功能.分享给大家供大家参考,具体如下: 实现功能:在输入框中输入字符,用AJAX传到后台Servlet处理后加上随机数,并返回到前台显示. 一.写前台jsp页面index.jsp <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML PUBLIC "-

  • 详解如何使用IntelliJ IDEA新建一个Servlet项目

    本文介绍了使用IntelliJ IDEA新建一个Servlet项目,一步步很详细,有需要的朋友可以了解一下 创建项目 创建完后的目录结构为: web项目配置 在WEB-INF目录下新建两个文件夹,分别命名未classes和lib(classes目录用于存放编译后的class文件,lib用于存放依赖的jar包) 项目设置:File –> Project Structure-,进入 Project Structure窗口,点击 Modules –> 选中项目"JavaWeb"

  • Java service层获取HttpServletRequest工具类的方法

    大家都知道 能在Controller/action层获取HttpServletRequest ,但是这里给大家备份的是从代码内部service层获取HttpServletRequest工具类. 具体如下: package com.base.common.sessionutils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframewo

  • 详解如何通过tomcat的ManagerServlet远程部署项目

    介绍 之前在邮政实习时,leader让我阅读tomcat的源代码,尝试自己实现远程部署项目的功能,于是便有了这此实践. 在Tomact中有一个Manager应用程序,它是用来管理已经部署的web应用程序,在这个应用程序中,ManagerServlet是他的主servlet,通过它我们可以获取tomcat的部分指标,远程管理web应用程序,不过这个功能会受到web应用程序部署中安全约束的保护. 当你请求ManagerServlet时,它会检查getPathInfo()返回的值以及相关的查询参数,以

  • SpringBoot如何注册Servlet、Filter、Listener的几种方式

    在Servlet 3.0之前都是使用web.xml文件进行配置,需要增加Servlet.Filter或者Listener都需要在web.xml增加相应的配置.Servlet 3.0之后可以使用注解进行配置Servlet.Filter或者Listener:springboot也提供了使用代码进行注册Servlet.Filter或者Listener.所以springboot有两种方式进行Servlet.Filter或者Listener配置. 方式一:使用注解 (1)注册Servlet 使用@WebS

随机推荐