SpringMVC 入口及父子容器源码解析

共 30534字,需浏览 62分钟

 ·

2021-04-06 09:23

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

76套java从入门到精通实战课程分享

1 工程简介

描述:web工程


1.1 pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <packaging>war</packaging>
    <groupId>org.example</groupId>
    <artifactId>rosh-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.2.8.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!--上下文-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--切面-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--事务-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring-mvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
        <!--servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- 日志相关依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
    </dependencies>


</project>

1.2 配置文件

public class RoshWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     *  父容器配置文件
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        System.out.println("RoshWebInitializer invoke getRootConfigClasses");
        return new Class<?>[]{SpringRootConfig.class};
    }
    /**
     * 子容器配置
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        System.out.println("RoshWebInitializer invoke getServletConfigClasses");
        return new Class<?>[]{ SpringServletConfig.class};
    }
    /**
     *拦截请求(静态资源、js、css.......)
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}


/**
 * 父容器配置文件,只扫描service
 */
@Configuration
@ComponentScan(value = "com.rosh.service")
public class SpringRootConfig {

}


/**
 *  子容器配置文件,仅仅扫描@Controller、@RestController
 */
@Configuration
@ComponentScan(value="com.rosh",includeFilters={
        @ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class, RestController.class})
},useDefaultFilters=false)
@EnableWebMvc
public class SpringServletConfig {

}


1.3 HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {


    @Autowired
    private HelloService helloService;


    @GetMapping("")
    public String printHello() {

        return helloService.printHello();
    }


}

1.4 HelloService

@Service
public class HelloService {


    public String printHello() {

        return "Hello World";
    }

}


1.5 启动tomcat


2 servlet3.0规范

在web容器启动时为提供给第三方组件机会做一些初始化的工作,servlet规范(JSR356)中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。


2.1 spring-web包

/**
 *
 * servlet 初始化时,会加载实现WebApplicationInitializer接口的所有类,调取onStartup方法。
 *
 */
@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(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
   throws ServletException {

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

  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 {
      initializers.add((WebApplicationInitializer)
        ReflectionUtils.accessibleConstructor(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);
  for (WebApplicationInitializer initializer : initializers) {
   initializer.onStartup(servletContext);
  }
 }

}

2.2 RoshWebInitializer

描述:查看RoshWebInitializer类图,发现该类实现了WebApplicationInitializer接口,所以当servlet容器启动时,会加载该配置文件,并且执行onStartup方法。

public class RoshWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     *  父容器配置文件
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        System.out.println("RoshWebInitializer invoke getRootConfigClasses");
        return new Class<?>[]{SpringRootConfig.class};
    }
    /**
     * 子容器配置
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        System.out.println("RoshWebInitializer invoke getServletConfigClasses");
        return new Class<?>[]{ SpringServletConfig.class};
    }
    /**
     *拦截请求(静态资源、js、css.......)
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

3 父容器源码解析

描述:打断点,debug。



3.1 创建父容器

描述:创建父容器(AnnotationConfigWebApplicationContext)、创建监听器。

 protected void registerContextLoaderListener(ServletContext servletContext) {

  /**
   * 创建上下文
   */
  WebApplicationContext rootAppContext = createRootApplicationContext();
  if (rootAppContext != null) {
   /**
    *创建监听器
    */
   ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
   listener.setContextInitializers(getRootApplicationContextInitializers());

   /**
    * 把listener加入到上下文
    */
   servletContext.addListener(listener);
  }
  else {
   logger.debug("No ContextLoaderListener registered, as " +
     "createRootApplicationContext() did not return an application context");
  }
 }

描述:创建父容器,直接new AnnotationConfigWebApplicationContext,加载父容器配置文件,然后返回。

@Override
 @Nullable
 protected WebApplicationContext createRootApplicationContext() {
  
  /**
   * 获取父容器配置文件
   */   
  Class<?>[] configClasses = getRootConfigClasses();
  if (!ObjectUtils.isEmpty(configClasses)) {
   
   /**
    * 创建父容器设置父容器配置文件
    */   
   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
   context.register(configClasses);
   return context;
  }
  else {
   return null;
  }
 }


3.2 父容器初始化

3.2.1 监听器

描述:ContextLoaderListener 监听器,实现ServletContextListener接口。ServletContextListener接口包含两个方法,一个是contextInitialized()方法,用来监听ServletContext的启动和初始化;一个是contextDestroyed()方法,用来监听ServletContext的销毁。


3.2 ContextLoaderListener

描述:在servletContext容器初始化时,调用方法contextInitialized



描述:ioc容器初始化,可以看前面几篇文章。

/**
  * 该方法是Spring容器初始化核心方法,采用模板设计模式。根据不同的上下文对象会调用不同对象子类方法。
  *
  * 核心上下文子类:
  *   AbstractXmlApplicationContext(XML上下文)
  *   AnnotationConfigApplicationContext(注解上下文)
  *
  *
  */
 @Override
 public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {

   //容器初始化
   prepareRefresh();

   /**
    * AbstractXmlApplicationContext:
    *
    * (1) 创建BeanFacotry
    * (2) 解析Xml
    */
   ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

   // 给beanFactory设置一些值
   prepareBeanFactory(beanFactory);

   try {
    // Allows post-processing of the bean factory in context subclasses.
    postProcessBeanFactory(beanFactory);

    //完成对BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor接口的调用
    invokeBeanFactoryPostProcessors(beanFactory);

    //实现了BeanPostProcessor接口的实例化,并且加入到BeanFactory中
    registerBeanPostProcessors(beanFactory);

    // 国际化
    initMessageSource();

    // Initialize event multicaster for this context.
    //初始化事件管理器
    initApplicationEventMulticaster();

    // 钩子方法
    onRefresh();

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

    // Instantiate all remaining (non-lazy-init) singletons.
    /**
     *
     * bean的实例化、IOC
     *
     */
    finishBeanFactoryInitialization(beanFactory);

    // Last step: publish corresponding event.
    /**
     * publicsh 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();
   }
  }
 }


3.3 子容器初始化


protected void registerDispatcherServlet(ServletContext servletContext) {
  String servletName = getServletName();
  Assert.hasLength(servletName, "getServletName() must not return null or empty");

  /**
   * 【1】 创建mvc上下文
   */
  WebApplicationContext servletAppContext = createServletApplicationContext();
  Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

  /**
   * 【2】 创建DispatcherServlet对象,把springmvc上下文设置到dispatcherServlet中
   */
  FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
  Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
  dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

  /**
   * 【3】 把DispatcherServlet加入到servlet上下文中
   */
  ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
  if (registration == null) {
   throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
     "Check if there is another servlet registered under the same name.");
  }

  registration.setLoadOnStartup(1);
  //拦截器
  registration.addMapping(getServletMappings());
  registration.setAsyncSupported(isAsyncSupported());

  //过滤器
  Filter[] filters = getServletFilters();
  if (!ObjectUtils.isEmpty(filters)) {
   for (Filter filter : filters) {
    registerServletFilter(servletContext, filter);
   }
  }

  customizeRegistration(registration);
 }

3.3.1 创建子容器

描述:和父容器一样,获取子容器配置文件,创建AnnotationConfigWebApplicationContext,设置配置文件,返回。


3.3.2 子容器初始化

结论:DispatcherServlet继承了HttpServlet,子容器的初始化在servlet init方法中




3.3.3 设置父子容器


protected WebApplicationContext initWebApplicationContext() {

  /**
   * 【1】 从servletContext中获取父容器
   */
  WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  WebApplicationContext wac = null;

  if (this.webApplicationContext != null) {
   // A context instance was injected at construction time -> use it

   /**
    * 【2】 获取springMvc 容器,子容器
    */
   wac = this.webApplicationContext;
   if (wac instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    if (!cwac.isActive()) {
     // The context has not yet been refreshed -> provide services such as
     // setting the parent context, setting the application context id, etc
     if (cwac.getParent() == null) {
      // The context instance was injected without an explicit parent -> set
      // the root application context (if any; may be null) as the parent
      /**
       *【3】 设置父子关系
       */
      cwac.setParent(rootContext);
     }
     /**
      * 【4】 启动容器
      */
     configureAndRefreshWebApplicationContext(cwac);
    }
   }
  }
  if (wac == null) {
   // No context instance was injected at construction time -> see if one
   // has been registered in the servlet context. If one exists, it is assumed
   // that the parent context (if any) has already been set and that the
   // user has performed any initialization such as setting the context id
   wac = findWebApplicationContext();
  }
  if (wac == null) {
   // No context instance is defined for this servlet -> create a local one
   wac = createWebApplicationContext(rootContext);
  }

  if (!this.refreshEventReceived) {
   // Either the context is not a ConfigurableApplicationContext with refresh
   // support or the context injected at construction time had already been
   // refreshed -> trigger initial onRefresh manually here.
   synchronized (this.onRefreshMonitor) {
    onRefresh(wac);
   }
  }

  if (this.publishContext) {
   // Publish the context as a servlet context attribute.
   String attrName = getServletContextAttributeName();
   getServletContext().setAttribute(attrName, wac);
  }

  return wac;
 }


3.3.4 初始化自容器其它属性

描述:当子容器完成bean的创建后,会触发publish事件初始化spring mvc 相关属性。



————————————————

版权声明:本文为CSDN博主「你携秋月揽星河丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:

https://blog.csdn.net/qq_34125999/article/details/115273102





粉丝福利:Java从入门到入土学习路线图

👇👇👇

👆长按上方微信二维码 2 秒


感谢点赞支持下哈 

浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报