Spring 梳理 - JavaConfigSCISPI

Posted 手握太阳

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 梳理 - JavaConfigSCISPI相关的知识,希望对你有一定的参考价值。

  1. 总结1:
    1. SCI:Servlet容器(Tomcat)提供的初始化Servlet容器本身的接口,可替换web.xml
    2. SpringSCI:SpringServletContainerInitializer,Srping提供的SCI的一个实现,起到中转桥接作用,桥接到 WebApplicationInitializer 接口上
    3. WebApplicationInitializer :可以自定义配置servlet、listener,进而可以通过代码手动控制两个上下文的创建过程(参考:基于xml     参考:支持注解);只能创建上线文但不能配置上下文详细内容,但可以指定上下文配置文件的路径或配置类的类路径
    4. AbstractAnnotationConfigDispatcherServletInitializer:将两个上下文自动已创建好,已创建好的上下文的类型都是支持注解的;手动继承后只需要指定两个上下文配置类路径即可。
    5. WebMvcConfigurer:

      WebMvcConfigurationAdapter已经废弃,最好用implements WebMvcConfigurer代替

      @Configuration
      public class MyConfig implements WebMvcConfigurer {
          
      }
      如果使用继承,WebMvcConfigurationSupport,DelegatingWebMvcConfiguration,或者使用@EnableWebMvc
    6. If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

      If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

  2. 总结2:
    1. java提供SPI,用于加载指定接口的 特定实现类
    2. servlet容器提供SCI接口,用于配置servlet容器初始化;这个过程使用了java的SPI机制;类似Logfactory的工作原理
    3. Spring提供SpringServletContainerInitializer类(该类实现类SCI接口),该类简称SpringSCI,该类不直接配置servlet容器,而是通过查找WebApplicationInitializer接口(用于web初始化)的实现类,间接配置servlet容器;
    4. Spring MVC提供基类 AbstractAnnotationConfigDispatcherServletInitializer,用于DispatcherServlet初始化(实现了WebApplicationInitializer接口),该基类既要完成WebApplicationInitializer接口中配置servlet容器的功能,又完成了配置MVC的功能,即同时配置了DispatcherServlet 和 ContextLoaderListener
    5. Spring提供的接口WebApplicationInitializer,用于配置servlet容器,并不是只用于配置MVC,可通过自定义二次开发继承WebApplicationInitializer,结合ServletRegistration.Dynamic,配置servlet容器,添加自定义的servlet、filter等,可完全替换掉web.xml
    6. 如果是在MVC项目中,想添加自定义的servlet、filter,可不用实现WebApplicationInitializer,直接只要重写 AbstractAnnotationConfigDispatcherServletInitializer 类的 getServletFilters() 方法就行了;不需要 mapping,因为会自动 mapping 到 DispatcherServlet 上,通过返回多个 filter,可以添加多个 filter。
  3. 总结3:spring  依据“java的SPI(Service Provider Interface)机制”和“servlet容器的SCI(ServletContainerInitializer)接口”,通过SpringServletContainerInitializer实现spring组件和servlet容器解耦,解耦后,spring组件可以直接使用spring提供的WebApplicationInitializer接口,实现类似SCI功能,通过实现WebApplicationInitializer,可以向servlet容器添加servlet,listener等。
    1. Servlet 3.0 规范和 Spring DispatcherServlet 配置
        在 Servlet 3.0 的环境中,容器会在 classpath 中寻找继承了 javax.servlet.ServletContainerInitializer 接口的类,用它来配置 servlet 容器。
        Spring 提供了一个继承这个接口的类 SpringServletContainerInitializer,在这个类中,它会寻找任何继承了 WebApplicationInitializer 接口的类并用其来配置 servlet 容器。
    2. Spring 3.2 提供了一个继承了 WebApplicationInitializer 接口的基类 AbstractAnnotationConfigDispatcherServletInitializer,用于配置Spring MVC项目。Spring 3.2 开始引入一个简易的 WebApplicationInitializer 实现类,这就是 AbstractAnnotationConfigDispatcherServletInitializer。
    3. AbstractAnnotationConfigDispatcherServletInitializer中,给我们留下了三个抽象方法要求我们去实现:

      • protected abstract String[] getServletMappings();    这个方法在AbstractDispatcherServletInitializer 中,这个方法可以将一个或多个路径映射到DispatcherServlet上,如果路径设置为“/”,则所有的请求都会由DispatcherServlet处理。
      • protected abstract Class<?>[] getRootConfigClasses();    这两个方法在AbstractAnnotationConfigDispatcherServletInitializer中
      • protected abstract Class<?>[] getServletConfigClasses();
    4. 在Spring MVC项目中
      1. 你的 servlet 配置类只需要继承 AbstractAnnotationConfigDispatcherServletInitializer,就会被发现而用于 servlet 容器的配置。
      2. AbstractAnnotationConfigDispatcherServletInitializer用于创建两种应用上下文:DispatcherServlet 创建的和拦截器 ContextLoaderListener 创建的上下文
      3. 在 AbstractAnnotationConfigDispatcherServletInitializer 中 DispatcherServlet 和 ContextLoaderListener 都会被创建,而基类中的方法就可用来创建不同的应用上下文:

        1. getServletConfigClasses():定义 DispatcherServlet 应用上下文中的 beans;

        2. getRootConfigClasses():定义拦截器 ContextLoaderListener 应用上下文中的 beans
      4. 示例
        1.   
          package spittr.config;
          import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
          import spittr.web.WebConfig;
          public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
            @Override
            protected Class<?>[] getRootConfigClasses() {
              return new Class<?>[] { RootConfig.class };
            }
            @Override
            protected Class<?>[] getServletConfigClasses() {
              return new Class<?>[] { WebConfig.class };
            }
            @Override
            protected String[] getServletMappings() {
              return new String[] { "/" };
            }
          }

           

      5. 配置额外的 servlets 和 filters(非Bean,非DispatcherServlet 和 ContextLoaderListener中的元素)
        1. 如果需要定义额外的 servlets 或 filters,只需要创建额外的初始化类。在 Spring MVC 中可以通过继承 WebApplicationInitializer 接口来实现。
        2. 定义一个新的 servlet:
          
          package com.myapp.config;
          import javax.servlet.ServletContext;
          import javax.servlet.ServletException;
          import javax.servlet.ServletRegistration.Dynamic;
          import org.springframework.web.WebApplicationInitializer;
          import com.myapp.MyServlet;
          public class MyServletInitializer implements WebApplicationInitializer {
              @Override
              public void onStartup(ServletContext throws ServletException {
                  Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
                  myServlet.addMapping("/custom/**");
              }
          }
          
          定义 filters 和 listeners:
          @Override
          public void onStartup(ServletContext servletContext)
          throws ServletException {
              // 定义filter
              javax.servlet.FilterRegistration.Dynamic filter =
                      servletContext.addFilter("myFilter", MyFilter.class);
              filter.addMappingForUrlPatterns(null, false, "/custom/*");
          
          }

           

        3. 如果你需要为 DispatcherServlet 添加 filter 的话,就不用这么麻烦了,你只要重写 AbstractAnnotationConfigDispatcherServletInitializer 类的 getServletFilters() 方法就行了:

          @Override
          protected Filter[] getServletFilters() {
              return new Filter[] { new MyFilter() };
          }

          不需要 mapping,因为会自动 mapping 到 DispatcherServlet 上,通过返回多个 filter,可以添加多个 filter

    5. 参考:https://blog.csdn.net/w1196726224/article/details/52687324
    6. 参考:[Servlet3.0研究之ServletContainerInitializer接口 - 夫礼者的专栏 - CSDN博客: https://blog.csdn.net/lqzkcx3/article/details/78507169]
    7. 参考:[java中的SPI机制 - 司刚军的个人专栏 - CSDN博客: https://blog.csdn.net/sigangjun/article/details/79071850]
    8. 参考:[JavaSPI机制学习笔记 - 琴水玉 - 博客园: http://www.cnblogs.com/lovesqcc/p/5229353.html]
    9. 参考:[Servlet3.0研究之ServletContainerInitializer接口 - 夫礼者的专栏 - CSDN博客: https://blog.csdn.net/lqzkcx3/article/details/78507169]
    10. Service Provider Interface:

      java spi的具体使用如下  :

      当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 

      基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。

      jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

  4. 使用JavaConfig普通web应用(非MVC)
    1. public class App {
          public static void main(String[] args) {
              ApplicationContext context = new AnnotationConfigApplicationContext(
                      AppConfig.class);
              CustomerBo customer = (CustomerBo) context.getBean("customer");
              customer.printMsg("Hello 1");
              SchedulerBo scheduler = (SchedulerBo) context.getBean("scheduler");
              scheduler.printMsg("Hello 2");
          }
      
      }
      
      
      ##############
      
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Import;
      @Configuration
      
      @Import({ CustomerConfig.class, SchedulerConfig.class })
      
      public class AppConfig {
      }
      
      ##############
      
      @Configuration
      public class CustomerConfig {
          @Bean(name="customer")
          public CustomerBo customerBo(){
              return new CustomerBo();
          }
      }
      
      ##############
      public class SchedulerBo {
          public void printMsg(String msg) {
              System.out.println("SchedulerBo : " + msg);
      
          }
      }

       

以上是关于Spring 梳理 - JavaConfigSCISPI的主要内容,如果未能解决你的问题,请参考以下文章

Spring核心组件知识梳理

Spring知识梳理

Spring源码解析一(框架梳理)

Spring源码解析一(框架梳理)

Spring知识梳理

Spring事务源码梳理