• 【Spring】内嵌Tomcat&去Xml&调试Mvc


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

    水稻:你说的是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.FilterType;
      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.tmpdir"));
                  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";
      
              @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};
              }
      
              @ComponentScan(value = PACKAGE_PATH,
                      excludeFilters = {
                              @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),
                              // 避免扫描到加了注解(@Configuration)的子容器
                              @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ServletConfig.class)})
              static class AppConfig {
              }
      
              @ComponentScan(value = PACKAGE_PATH)
              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()
    •  1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       2     。。。
       3   processedRequest = checkMultipart(request);
       4   multipartRequestParsed = (processedRequest != request);
       5   // 1.返回一个持有methodHandler(按照URL匹配得出的被调用bean对象以及目标方法)调用链(拦截器链)对象
       6   mappedHandler = getHandler(processedRequest);
       7   。。。
       8   // 2.按照我们现在写代码的方式,只会用到HandlerMethod,其他三种基本不会用
       9   HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      10   。。。
      11   // 3.前置过滤器 - 顺序调用
      12   if (!mappedHandler.applyPreHandle(processedRequest, response)) {
      13       return;
      14   }
      15   // 4.Actually invoke the handler.
      16   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
      17   。。。
      18     applyDefaultViewName(processedRequest, mv);
      19   // 5.后置过滤器 - 逆序调用
      20   mappedHandler.applyPostHandle(processedRequest, response, mv);
      21   。。。
      22   // 6.处理试图 - 内部render
      23   processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
      24   }
      25   catch (Exception ex) {
      26      // 异常处理
      27      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
      28   }
      29       // 异常处理
      30   catch (Throwable err) {
      31      triggerAfterCompletion(processedRequest, response, mappedHandler,
      32                     new NestedServletException("Handler processing failed", err));
      33   }
      34     。。。

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

    总结:

    • 目前基本互联网项目都是SpringBoot起手了,再难遇到SpringMvc的项目,不过熟悉该流程有利于我们更加深刻的理解Ioc容器
    • Mvc拦截器链也是日常开发中会用到的功能,顺便熟悉一下请求的执行过程
  • 相关阅读:
    为什么Java中 wait 方法需要在 synchronized 的方法中调用?
    XML常用解析API有哪几种?
    Dubbo 和 Spring Cloud 的区别?
    Java 线程池中 submit() 和 execute()方法有什么区别?
    详细描述一下 Elasticsearch 搜索的过程?
    为表中得字段选择合适得数据类型 ?
    Json有什么作用?
    Ajax的乱码解决问题?
    eclipse安装配置记录
    srs部署/webrtc拉流
  • 原文地址:https://www.cnblogs.com/nightOfStreet/p/13205646.html
Copyright © 2020-2023  润新知