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

水稻:你说的是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. 使用turtle库画国际象棋棋盘

    import turtle n = 60 # 每行间隔,小格子边长 x = -300 # x初始值 y = -300 # x初始值 def main(): turtle.speed(11) turtl ...

  2. Java实现 LeetCode 523 连续的子数组和(ง •_•)ง

    523. 连续的子数组和 给定一个包含非负数的数组和一个目标整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数. 示 ...

  3. Java实现 LeetCode 518 零钱兑换 II

    518. 零钱兑换 II 给定不同面额的硬币和一个总金额.写出函数来计算可以凑成总金额的硬币组合数.假设每一种面额的硬币有无限个. 示例 1: 输入: amount = 5, coins = [1, ...

  4. java实现洛谷P1308统计单词数

    题目描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给 ...

  5. Mysql的默认最大连接数及如何修改

    一.Mysql默认最大连接数 通过查看mysql安装目录的my.ini文件,发现mysql的默认最大的连接数为100,实际场景中,以及进行压测时,100时远远不够的,一般都会设置最大的连接数. 二.如 ...

  6. 消费者启动,允许期间报task supervisor timed out 异常,解决

    如何解决后端项目启动时抛出 task supervisor timed out 异常 现象描述 后端项目启动时抛出如下异常,但是该后段项目能正常启动并注册到注册中心,不影响功能使用. 2018-10- ...

  7. effictive c++

    c++条款 num 1:尽量以const enum inline替换#define 1)对于单纯常量,最好以const对象或enums替换#defines 2)对于形似函数的宏,最好改用inline函 ...

  8. 聊一聊高并发高可用那些事 - Kafka篇

    目录 为什么需要消息队列 1.异步 :一个下单流程,你需要扣积分,扣优惠卷,发短信等,有些耗时又不需要立即处理的事,可以丢到队列里异步处理. 2.削峰 :按平常的流量,服务器刚好可以正常负载.偶尔推出 ...

  9. 恕我直言,我怀疑你并不会用 Java 枚举

    开门见山地说吧,enum(枚举)是 Java 1.5 时引入的关键字,它表示一种特殊类型的类,默认继承自 java.lang.Enum. 为了证明这一点,我们来新建一个枚举 PlayerType: p ...

  10. 全网最全95道MongoDB面试题1万字详细解析

    1.mongodb是什么? MongoDB 是由 C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoDB 旨在给 WEB ...