菜瓜:今天听到个名词“父子容器”,百度了一下,感觉概念有点空洞,这是什么核武器?

水稻:你说的是SpringMvc和Spring吧,其实只是一个概念而已,用来将两个容器做隔离,起到解耦的作用,其中子容器可以拿到父容器的bean,父容器拿不到子容器的。但是SpringBoot出来之后这个概念基本就被淡化掉,没有太大意义,SpringBoot中只有一个容器了。

菜瓜:能不能给个demo?

水稻:可以。由于现在SpringBoot已经大行其道,Mvc你可能接触的少,甚至没接触过。

  • 早些年启动一个Mvc项目费老鼻子劲了,要配置各种Xml文件(Web.xml,spring.xml,spring-dispather.xml),然后开发完的项目要打成War包发到Tomcat容器中
  • 现在可以直接引入Tomcat包,用main方法直接调起。为了调试方便,我就演示一个Pom引入Tomcat的例子
  • ①启动类
  • package com.vip.qc.mvc;
    
    import org.apache.catalina.Context;
    import org.apache.catalina.LifecycleException;
    import org.apache.catalina.LifecycleListener;
    import org.apache.catalina.startup.Tomcat;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /**
    * 参考: * https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet
    * <p>
    * 嵌入tomcat,由Tomcat发起对Spring容器的初始化调用过程
    * <p>
    * - 启动过程
    * * - Servlet规范,Servlet容器在启动之后会SPI加载META-INF/services目录下的实现类并调用其onStartup方法
    * * - Spring遵循规范实现了ServletContainerInitializer接口。该接口在执行时会收集WebApplicationInitializer接口实现类并循环调用其onStartup方法
    * * - 其中AbstractDispatcherServletInitializer
    * * * - 将spring上下文放入ContextLoaderListener监听器,该监听会发起对refresh方法的调用
    * * * - 注册dispatcherServlet,后续会由tomcat调用HttpServletBean的init方法,完成子容器的refresh调用
    * *
    *
    * @author QuCheng on 2020/6/28.
    */
    public class SpringWebStart { public static void main(String[] args) {
    Tomcat tomcat = new Tomcat();
    try {
    // 此处需要取一个目录
    Context context = tomcat.addContext("/", System.getProperty("java.io.tmp"));
    context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
    tomcat.setPort(8081);
    tomcat.start();
    tomcat.getServer().await();
    } catch (LifecycleException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
    e.printStackTrace();
    }
    } static class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { private final static String PACKAGE_PATH = "com.vip.qc.mvc.controller";
    private final static String PACKAGE_PATH_CHILD = "com.vip.qc.mvc.service"; @Override
    protected String[] getServletMappings() {
    return new String[]{"/"};
    } @Override
    protected Class<?>[] getRootConfigClasses() {
    // spring 父容器
    return new Class[]{AppConfig.class};
    } @Override
    protected Class<?>[] getServletConfigClasses() {
    // servlet 子容器
    return new Class[]{ServletConfig.class};
    } @Configuration
    @ComponentScan(value = PACKAGE_PATH_CHILD, excludeFilters = @ComponentScan.Filter(classes = Controller.class))
    static class AppConfig {
    } @Configuration
    @ComponentScan(value = PACKAGE_PATH, includeFilters = @ComponentScan.Filter(classes = Controller.class))
    static class ServletConfig {
    }
    } } 
  • ②Controller&Service
  • package com.vip.qc.mvc.controller;
    
    import com.vip.qc.mvc.service.ServiceChild;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; /**
    * @author QuCheng on 2020/6/28.
    */
    @Controller
    public class ControllerT implements ApplicationContextAware { @Resource
    private ServiceChild child; @RequestMapping("/hello")
    @ResponseBody
    public String containter() {
    child.getParent();
    System.out.println("parentContainer");
    return "containter";
    } @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println("子容器" + applicationContext);
    System.out.println("子容器中获取父容器bean" + applicationContext.getBean(ServiceChild.class));
    }
    } package com.vip.qc.mvc.service; import com.vip.qc.mvc.controller.ControllerT;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Service; /**
    * @author QuCheng on 2020/6/28.
    */
    @Service
    public class ServiceChild implements ApplicationContextAware { // @Resource
    private ControllerT controllerT; public void getParent() { System.out.println(controllerT);
    } @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println("父容器" + applicationContext);
    try {
    System.out.println("父容器中获取子容器bean" + applicationContext.getBean(ControllerT.class));
    } catch (NoSuchBeanDefinitionException e) {
    System.out.println("找不到子容器的bean");
    }
    }
    } // 调用SpringWebStart的main方法启动-会有如下打印
    父容器Root WebApplicationContext, started on Sun Jun 28 22:03:52 CST 2020
    找不到子容器的bean
    子容器WebApplicationContext for namespace 'dispatcher-servlet', started on Sun Jun 28 22:03:58 CST 2020, parent: Root WebApplicationContext
    子容器中获取父容器beancom.vip.qc.mvc.service.ServiceChild@4acfc43a
    
    
  • Demo比较简单,不过也能反映父子容器的关系

菜瓜:嗯,效果看到了,能不能讲一下启动过程

水稻:稍等,我去下载源码。上面代码演示中已经提前说明了,父子容器的加载是Tomcat依据Servlet规范发起调用完成的

  • spring-web源码包的/META-INF中能找到SPI的实际加载类SpringServletContainerInitializer#onStartup()方法会搜集实现WebApplicationInitializer接口的类,并调用其onStartup方法
  • 上面MyWebApplicationInitializer启动类是WebApplicationInitializer的子类,未实现onStartup,实际调用的是其抽象父类AbstractDispatcherServletInitializer的方法。跟进去
  • @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    //① 创建Spring父容器上下文-对象放入ContextLoadListener,后续调起完成初始化,
    super.onStartup(servletContext);
    //② 创建DispatcherServlet对象,后续会由tomcat调用其init方法,完成子容器的初始化工作
    registerDispatcherServlet(servletContext);
    } // ①进来
    protected void registerContextLoaderListener(ServletContext servletContext) {
    // 此处会回调我们启动类的getRootConfigClasses()方法 - 父容器配置
    WebApplicationContext rootAppContext = createRootApplicationContext();
    if (rootAppContext != null) {
    ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
    istener.setContextInitializers(getRootApplicationContextInitializers());
    servletContext.addListener(listener);
    }
    else {
    logger.debug("No ContextLoaderListener registered, as " +
    "createRootApplicationContext() did not return an application context");
    }
    } // ②进来
    protected void registerDispatcherServlet(ServletContext servletContext) {
    。。。
    // 此处会回调我们启动类的getServletConfigClasses()方法 - 子容器配置
    WebApplicationContext servletAppContext = createServletApplicationContext();
    。。。
    // 初始化的dispatcherServlet,会加入Tomcat容器中-后续调用
    // FrameworkServlet#initServletBean()会完成上下文初始化工作
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    。。。
    }

菜瓜:这样容器就可以用了吗?

水稻:是的,这样就可以直接在浏览器上面访问http://localhost:8081/hello,不过这是一个最简陋的web项目

菜瓜:懂了,最简陋是什么意思

水稻:如果我们想加一些常见的Web功能,譬如说拦截器,过滤器啥的。可以通过@EnableWebMvc注解自定义一些功能

  • package com.vip.qc.mvc;
    
    import com.vip.qc.mvc.interceptor.MyInterceptor1;
    import com.vip.qc.mvc.interceptor.MyInterceptor2;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /**
    * @author QuCheng on 2020/6/28.
    */
    @Configuration
    @EnableWebMvc
    public class WebMvcConfig implements WebMvcConfigurer { @Resource
    private MyInterceptor1 interceptor1;
    @Resource
    private MyInterceptor2 interceptor2; @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(interceptor1).addPathPatterns("/interceptor/**");
    registry.addInterceptor(interceptor2).addPathPatterns("/interceptor/**");
    }
    } package com.vip.qc.mvc.interceptor; import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse; /**
    * @author QuCheng on 2020/6/28.
    */
    @Configuration
    public class MyInterceptor1 implements HandlerInterceptor { @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("嘻嘻 我是拦截器1 pre");
    return true;
    } @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("嘻嘻 我是拦截器1 post");
    } @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("嘻嘻 我是拦截器1 after");
    }
    } package com.vip.qc.mvc.interceptor; import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse; /**
    * @author QuCheng on 2020/6/28.
    */
    @Configuration
    public class MyInterceptor2 implements HandlerInterceptor { @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("嘻嘻 我是拦截器2 pre");
    return true;
    } @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("嘻嘻 我是拦截器2 post");
    } @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("嘻嘻 我是拦截器2 after");
    } } 

菜瓜:我知道,这里还有个Mvc请求调用流程和这个拦截器有关。而且这个拦截器不是MethodInterceptor(切面)

水稻:没错,说到这里顺便复习一下Mvc的请求过程

  • 请求最开始都是通过Tomcat容器转发过来的,调用链:HttpServlet#service() -> FrameworkServlet#processRequest() -> DispatcherServlet#doDispather()
  •  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    。。。
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);
    // 1.返回一个持有methodHandler(按照URL匹配得出的被调用bean对象以及目标方法)调用链(拦截器链)对象
    mappedHandler = getHandler(processedRequest);
    。。。
    // 2.按照我们现在写代码的方式,只会用到HandlerMethod,其他三种基本不会用
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    。。。
    // 3.前置过滤器 - 顺序调用
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }
    // 4.Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    。。。
    applyDefaultViewName(processedRequest, mv);
    // 5.后置过滤器 - 逆序调用
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    。。。
    // 6.处理试图 - 内部render
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    // 异常处理
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    // 异常处理
    catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
    }
    。。。

菜瓜:这个之前看过不少,百度一大堆,不过还是源码亲切

总结:

  • 目前基本互联网项目都是SpringBoot起手了,再难遇到SpringMvc的项目,不过熟悉该流程有利于我们更加深刻的理解Ioc容器
  • Mvc拦截器链也是日常开发中会用到的功能,顺便熟悉一下请求的执行过程

【Spring】内嵌Tomcat&去Xml&调试Mvc的更多相关文章

  1. 学习Tomcat(七)之Spring内嵌Tomcat

    前面的文章中,我们介绍了Tomcat容器的关键组件和类加载器,但是现在的J2EE开发中更多的是使用SpringBoot内嵌的Tomcat容器,而不是单独安装Tomcat应用.那么Spring是怎么和T ...

  2. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. Spring Boot移除内嵌Tomcat,使用非web方式启动

    前言:当我们使用Spring Boot编写了一个批处理应用程序,该程序只是用于后台跑批数据,此时不需要内嵌的tomcat,简化启动方式使用非web方式启动项目,步骤如下: 1.在pom.xml文件中去 ...

  4. Spring Boot 内嵌Tomcat的端口号的修改

    操作非常的简单,不过如果从来没有操作过,也是需要查找一下资料的,所以,在此我简单的记录一下自己的操作步骤以备后用! 1:我的Eclipse版本,不同的开发工具可能有所差异,不过大同小异 2:如何进入对 ...

  5. Spring Boot内嵌Tomcat session超时问题

    最近让Spring Boot内嵌Tomcat的session超时问题给坑了一把. 在应用中需要设置session超时时间,然后就习惯的在application.properties配置文件中设置如下, ...

  6. spring boot 2 内嵌Tomcat Stopping service [Tomcat]

    我在使用springboot时,当代码有问题时,发现控制台打印下面信息: Connected to the target VM, address: '127.0.0.1:42091', transpo ...

  7. 如何优雅的关闭基于Spring Boot 内嵌 Tomcat 的 Web 应用

    背景 最近在搞云化项目的启动脚本,觉得以往kill方式关闭服务项目太粗暴了,这种kill关闭应用的方式会让当前应用将所有处理中的请求丢弃,响应失败.这种形式的响应失败在处理重要业务逻辑中是要极力避免的 ...

  8. 查看和指定SpringBoot内嵌Tomcat的版本

    查看当前使用的Tomcat版本号 Maven Repository中查看 比如我们需要查Spring Boot 2.1.4-RELEASE的内嵌Tomcat版本, 可以打开链接: https://mv ...

  9. 基于内嵌Tomcat的应用开发

    为什么使用内嵌Tomcat开发? 开发人员无需搭建Tomcat的环境就可以使用内嵌式Tomcat进行开发,减少搭建J2EE容器环境的时间和开发时容器频繁启动所花时间,提高开发的效率. 怎么搭建内嵌To ...

随机推荐

  1. Java实现偶数矩阵(Even Parity, UVa 11464)

    偶数矩阵(Even Parity, UVa 11464) 问题描述 给你一个n×n的01矩阵(每个元素非0即1),你的任务是把尽量少的0变成1, 使得每个元素的上.下.左.右的元素(如果存在的话)之和 ...

  2. Java实现 LeetCode 204 计数质数

    204. 计数质数 统计所有小于非负整数 n 的质数的数量. 示例: 输入: 10 输出: 4 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 . class Solutio ...

  3. Java实现 蓝桥杯VIP 算法提高 现代诗如蚯蚓

    算法提高 现代诗如蚯蚓 时间限制:1.0s 内存限制:256.0MB 问题描述 现代诗如蚯蚓 断成好几截都不会死 字符串断成好几截 有可能完全一样 请编写程序 输入字符串 输出该字符串最多能断成多少截 ...

  4. Java实现 LeetCode 29 两数相除

    29. 两数相除 给定两个整数,被除数 dividend 和除数 divisor.将两数相除,要求不使用乘法.除法和 mod 运算符. 返回被除数 dividend 除以除数 divisor 得到的商 ...

  5. Android如何使用OKHttp

    首先要在build.gradle里面写入 // 添加OKHttp支持 implementation("com.squareup.okhttp3:okhttp:4.3.1") 下面是 ...

  6. PAT 有几个PAT

    字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位(P),第 4 位(A),第 6 位(T):第二个 PAT 是第 3 位(P),第 4 位(A),第 6 位(T). 现 ...

  7. 关于nginx的源码安装方式

    Nginx(engine x)是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器, 也是一个 IMAP/POP3/SMTP 代理服务器.在高连接并发的情况下, ...

  8. 温故知新-Mysql锁&事务&MVCC

    文章目录 锁概述 锁分类 MyISAM 表锁 InnoDB 行锁 事务及其ACID属性 InnoDB 的行锁模式 注意 MVCC InnoDB 中的 MVCC 参考 你的鼓励也是我创作的动力 Post ...

  9. 诸葛亮vs司马懿,排序算法大战谁能笑到最后?

    阵前对峙 公元234年,蜀汉丞相诸葛孔明再次北伐. 一日,与司马仲达所率魏军两军相峙,二人阵前舌战. 司马曰:"诸葛村夫,吾与汝相斗数年,斗兵斗阵斗谋略,均已疲乏.今日,何不一改陈规,斗点新 ...

  10. 使用vw进行移动端适配(nuxt项目)

    基于nuxt 2.0.0 一.安装postcss-px-to-viewport npm安装 npm install postcss-px-to-viewport --save-dev 或 yarn安装 ...