SpringMVC

作者: 林亚希 | 来源:发表于2022-11-21 18:01 被阅读0次

    首先我们要了解下SPI机制

    Service Provider Interface ,即服务提供者接口的意思。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

    SpringMVC原理.jpg

    就是我们启动的时候会自动加载这个类。

    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
          throws ServletException {
    
       List<WebApplicationInitializer> initializers = Collections.emptyList();
    
       if (webAppInitializerClasses != null) {
          initializers = new ArrayList<>(webAppInitializerClasses.size());
          for (Class<?> waiClass : webAppInitializerClasses) {
             // Be defensive: Some servlet containers provide us with invalid classes,
             // no matter what @HandlesTypes says... 所有的非接口非抽象的WebApplicationInitializer实现类
             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;
       }
       //下面会遍历所有满足要求的WebApplicationInitializer,调用他们的onStartup
       servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
       AnnotationAwareOrderComparator.sort(initializers);
       for (WebApplicationInitializer initializer : initializers) {
          initializer.onStartup(servletContext); //所有的 WebApplicationInitializer 的 onStartup
       }
    }
    

    然后我们去看官方文档。https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet

    public class AppStarter  implements WebApplicationInitializer  {
    //  @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            //1、创建ioc容器
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(SpringConfig.class); //2、传入一个配置类
            //以上截止,ioc容器都没有启动
            //3、配置了 DispatcherServlet,利用Servlet的初始化机制
            DispatcherServlet servlet = new DispatcherServlet(context);
            ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
            registration.setLoadOnStartup(1);
            registration.addMapping("/"); //映射路径
    
            //启动了容器?上面的Servlet添加到 servletContext 里面以后,Tomcat就会对 DispatcherServlet进行初始化
            //<servlet></servlet>
    //      servletContext.addServlet("abc",XXXX.class)
    
        }
    }
    

    通过继承这个WebApplicationInitializer让tomcat启动的时候就加载。

    上面的Servlet添加到 servletContext 里面以后,Tomcat就会对 DispatcherServlet进行初始化

    接下来是HttpServletBean的init方法

    @Override
    public final void init() throws ServletException {
    
       // Set bean properties from init parameters.
       PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
       if (!pvs.isEmpty()) {
          try {
             BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
             ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
             bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
             initBeanWrapper(bw);
             bw.setPropertyValues(pvs, true);
          }
          catch (BeansException ex) {
             if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
             }
             throw ex;
          }
       }
    
       //模板方法模式。给子类留的喜欢干的事 Let subclasses do whatever initialization they like.
       initServletBean();
    }
    

    然后是FrameworkServlet的initServletBean()

    @Override
    protected final void initServletBean() throws ServletException {
       getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
       if (logger.isInfoEnabled()) {
          logger.info("Initializing Servlet '" + getServletName() + "'");
       }
       long startTime = System.currentTimeMillis();
    
       try {
          this.webApplicationContext = initWebApplicationContext(); //初始化WebIOC容器
          initFrameworkServlet();
       }
       catch (ServletException | RuntimeException ex) {
          logger.error("Context initialization failed", ex);
          throw ex;
       }
    
       if (logger.isDebugEnabled()) {
          String value = this.enableLoggingRequestDetails ?
                "shown which may lead to unsafe logging of potentially sensitive data" :
                "masked to prevent unsafe logging of potentially sensitive data";
          logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                "': request parameters and headers will be " + value);
       }
    
       if (logger.isInfoEnabled()) {
          logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
       }
    }
    

    初始化WebIOC容器

    protected WebApplicationContext initWebApplicationContext() {
       WebApplicationContext rootContext =
             WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //父容器
       WebApplicationContext wac = null; //先会获取之前的 WebApplicationContext(构建父子容器)
    
       if (this.webApplicationContext != null) {
          // A context instance was injected at construction time -> use it
          wac = this.webApplicationContext; //当前的web-ioc容器
          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
                   cwac.setParent(rootContext); //父子容器的体现,
                }
                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;
    }
    

    父子容器

    这里有个父子容器的体现cwac.setParent(rootContext); //父子容器的体现,

    [图片上传失败...(image-8fc8b8-1668562028847)]

    好了想要搞这个父子容器,我们要用一个新的启动类

    /**
     * 最快速的整合注解版SpringMVC和Spring的
     */
    public class QuickAppStarter  extends AbstractAnnotationConfigDispatcherServletInitializer {
    
       @Override //根容器的配置(Spring的配置文件===Spring的配置类)
       protected Class<?>[] getRootConfigClasses() {
          return new Class<?>[]{SpringConfig.class};
       }
    
       @Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
       protected Class<?>[] getServletConfigClasses() {
          return new Class<?>[]{SpringMVCConfig.class};
       }
    
       @Override //Servlet的映射,DispatcherServlet的映射路径
       protected String[] getServletMappings() {
          return new String[]{"/"};
       }
    
       @Override
       protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    //    super.customizeRegistration(registration);
    
    //    registration.addMapping("");//
       }
    }
    
    @ComponentScan(value = "com.demo.web",excludeFilters = {
          @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
    })
    @Configuration
    public class SpringConfig {
       //Spring的父容器
    
    }
    
    @ComponentScan(value = "com.demo.web",includeFilters = {
          @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
    },useDefaultFilters = false)
    public class SpringMVCConfig {
       //SpringMVC的子容器,能扫描的Spring容器中的组件
    
    
    }
    

    我们现在从新启动断点重新从onStartup进来进入AbstractDispatcherServletInitializer的onStartup

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
       super.onStartup(servletContext);
       registerDispatcherServlet(servletContext);
    }
    
    • super.onStartup(servletContext);
    • registerDispatcherServlet(servletContext);

    首先 super.onStartup(servletContext); 中启动方法中调用的是父类的方法

    @Override //注册ContextLoaderListener;contextInitialized
    public void onStartup(ServletContext servletContext) throws ServletException {
       registerContextLoaderListener(servletContext);
    }
    
    /**
     * Register a {@link ContextLoaderListener} against the given servlet context. The
     * {@code ContextLoaderListener} is initialized with the application context returned
     * from the {@link #createRootApplicationContext()} template method.
     * @param servletContext the servlet context to register the listener against
     */
    protected void registerContextLoaderListener(ServletContext servletContext) {
       WebApplicationContext rootAppContext = createRootApplicationContext(); //创建一个根容器
       if (rootAppContext != null) {
          ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
          listener.setContextInitializers(getRootApplicationContextInitializers());
          servletContext.addListener(listener);//注册监听器
       }
       else {
          logger.debug("No ContextLoaderListener registered, as " +
                "createRootApplicationContext() did not return an application context");
       }
    }
    

    WebApplicationContext rootAppContext = createRootApplicationContext(); //创建一个根容器

    这边是模版方法给我们注册父组件配置

    @Override
    @Nullable //重写了爷爷类的创建根容器方法
    protected WebApplicationContext createRootApplicationContext() {
       Class<?>[] configClasses = getRootConfigClasses(); //获取根配置
       if (!ObjectUtils.isEmpty(configClasses)) {
          AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
          context.register(configClasses); //创建了一个IOC容器并把配置类注册进来
          return context;
       }
       else {
          return null;
       }
    }
    

    接下来再次回到 registerDispatcherServlet(servletContext);

    protected void registerDispatcherServlet(ServletContext servletContext) {
       String servletName = getServletName();
       Assert.hasLength(servletName, "getServletName() must not return null or empty");
    
       WebApplicationContext servletAppContext = createServletApplicationContext(); //创建Servlet容器
       Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    
       FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
       Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
       dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    
       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()); //根据我们指定的DispatcherServlet的路径进行注册
       registration.setAsyncSupported(isAsyncSupported());
    
       Filter[] filters = getServletFilters();
       if (!ObjectUtils.isEmpty(filters)) {
          for (Filter filter : filters) {
             registerServletFilter(servletContext, filter);
          }
       }
    
       customizeRegistration(registration);
    }
    

    WebApplicationContext servletAppContext = createServletApplicationContext(); //创建Servlet容器

    @Override
    protected WebApplicationContext createServletApplicationContext() {
       AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); //这里又创建了一个容器
       Class<?>[] configClasses = getServletConfigClasses(); //获取web应用的配置
       if (!ObjectUtils.isEmpty(configClasses)) {
          context.register(configClasses);
       }
       return context;
    }
    

    registration.addMapping(getServletMappings()); //根据我们指定的DispatcherServlet的路径进行注册

    到此为止创建好了2个父子容器,那什么时候初始化。

    父容器初始化

    我们在做父容器的时候有注册过一个监听器,监听器在tomcat启动后会回调。

    ContextLoaderListener下的

    @Override
    public void contextInitialized(ServletContextEvent event) { //根容器初始化
       initWebApplicationContext(event.getServletContext()); //初始化webioc容器
    }
    
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
       if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
          throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
       }
    
       servletContext.log("Initializing Spring root WebApplicationContext");
       Log logger = LogFactory.getLog(ContextLoader.class);
       if (logger.isInfoEnabled()) {
          logger.info("Root WebApplicationContext: initialization started");
       }
       long startTime = System.currentTimeMillis();
    
       try {
          // Store context in local instance variable, to guarantee that
          // it is available on ServletContext shutdown.
          if (this.context == null) {
             this.context = createWebApplicationContext(servletContext);
          }
          if (this.context instanceof ConfigurableWebApplicationContext) {
             ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
             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 ->
                   // determine parent for root web application context, if any.
                   ApplicationContext parent = loadParentContext(servletContext);
                   cwac.setParent(parent);
                }
                            configureAndRefreshWebApplicationContext(cwac, servletContext);//配置和刷新容器
             }
          }
          servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
          ClassLoader ccl = Thread.currentThread().getContextClassLoader();
          if (ccl == ContextLoader.class.getClassLoader()) {
             currentContext = this.context;
          }
          else if (ccl != null) {
             currentContextPerThread.put(ccl, this.context);
          }
    
          if (logger.isInfoEnabled()) {
             long elapsedTime = System.currentTimeMillis() - startTime;
             logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
          }
    
          return this.context;
       }
       catch (RuntimeException | Error ex) {
          logger.error("Context initialization failed", ex);
          servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
          throw ex;
       }
    }
    

    configureAndRefreshWebApplicationContext(cwac, servletContext);//配置和刷新容器

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
       if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
          // The application context id is still set to its original default value
          // -> assign a more useful id based on available information
          String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
          if (idParam != null) {
             wac.setId(idParam);
          }
          else {
             // Generate default id...
             wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                   ObjectUtils.getDisplayString(sc.getContextPath()));
          }
       }
    
       wac.setServletContext(sc);
       String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
       if (configLocationParam != null) {
          wac.setConfigLocation(configLocationParam);
       }
    
       // The wac environment's #initPropertySources will be called in any case when the context
       // is refreshed; do it eagerly here to ensure servlet property sources are in place for
       // use in any post-processing or initialization that occurs below prior to #refresh
       ConfigurableEnvironment env = wac.getEnvironment();
       if (env instanceof ConfigurableWebEnvironment) {
          ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
       }
    
       customizeContext(sc, wac);
       wac.refresh(); //容器初始化
    }
    

    wac.refresh(); //容器初始化这里就是我们最熟悉的容器刷新12大步了。

    到这边我们的父容器AOP,事务,IOC,自动装配组件功能都装载进来了。

    子容器初始化

    我们在创建子容器的时候我们有创建了一个DispatcherServlet,在tomcat启动后会调用init方法。这样又回到我们最最开始的流程。

    protected WebApplicationContext initWebApplicationContext() {
       WebApplicationContext rootContext =
             WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //父容器
       WebApplicationContext wac = null; //先会获取之前的 WebApplicationContext(构建父子容器)
    
       if (this.webApplicationContext != null) {
          // A context instance was injected at construction time -> use it
          wac = this.webApplicationContext; //当前的web-ioc容器
          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
                   cwac.setParent(rootContext); //父子容器的体现,
                }
                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;
    }
    

    获取父容器的时候就能找到父容器。

    onRefresh(wac); //容器刷新 然后后面也执行了容器刷新

    到这里就是把MVC的原理源码梳理了一遍,这里挖个坑.现在我们知道一个controller被注册到容器中了。那我们一个请求进来/test/a. 他是怎么找到对应的接口呢。

    相关文章

      网友评论

          本文标题:SpringMVC

          本文链接:https://www.haomeiwen.com/subject/epnjgrtx.html