servlet通信与jdk、与springboot和spring(一)

在正常学习当中,我们总是会过度忽略标准的定义,如jdk的定义,而是用更多的高级抽象概念,例如servlet容器、数组库路由等,然而对于在不同源码当中找到关键点来说,找到标准的定义十分重要的事情。

servlet的定义

java EE的servlet定义

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>{version}</version>
  <scope>provided</scope>
</dependency>

Oracle 在 2017 年把 Java EE 捐赠给了 Eclipse 基金会,成立项目 EE4J,为避免商标问题,在后续把包名从 javax.* 迁到 jakarta.*。
ps: jdk属于java SE。

<dependency>
  <groupId>jakarta.servlet</groupId>
  <artifactId>jakarta.servlet-api</artifactId>
  <version>6.0.0</version> 
  <scope>provided</scope>   
</dependency>

常见接口

jakarta.servlet.Servlet: servlet的标准,
jakarta.servlet.filter: 过滤器是一个对象,它对对资源的请求(Servlet 或静态内容)或来自资源的响应或两者执行过滤任务
jakarta.servlet.ServletConfig:Servlet 容器在初始化期间用于将信息传递给 Servlet 的 Servlet 配置对象。

在servlet实现中必须要实现的六个接口当中,存在一个叫init的方法


    @Override
    public void init(ServletConfig config) throws ServletException {
        this.servletConfig = config;
        System.out.println("test01Servlet初始化成功!!");
    }

断点查看后会发现一个叫ServletContext的接口,他同样来自servlet定义包,该类是作为上下文使用,在日后的springboot、spring当中都有很大的用处

定义 Servlet 用于与其 Servlet 容器通信的一组方法,例如,获取文件的 MIME 类型、分派请求或写入日志文件。
每个 Java 虚拟机每个“Web 应用程序”都有一个上下文。(“Web 应用程序”是安装在服务器 URL 命名空间的特定子集下的 servlet 和内容的集合,例如 /catalog ,可能通过文件安装 .war 
public interface ServletContext {
.....
}

注册servlet

jakarta.servlet.ServletContainerInitializer: 
此接口的实现可以用 进行注释 HandlesTypes,以便(在其方法中 onStartup )接收实现、扩展或已使用注释指定的类类型进行注释的应用程序类集。
如果此接口的实现不使用 HandlesTypes 注释,或者没有一个应用程序类与注释指定的类匹配,则容器必须将 null 集合类传递给 onStartup。
jakarta.servlet.annotation.HandlesTypes: 此注释用于声明可以 ServletContainerInitializer 处理的接口类


简单来说ServletContainerInitializer中仅存在一个方法onStartup,该方法存在一个参数Set<Class<?>> c,如果某个类上标记了HandlesTypes并指定需要使用的接口类型的实现,那么c参数中就会存在该接口所有的实现
spring使用


// 1) 标在 SCI 上:告诉容器“帮我找出所有 WebApplicationInitializer 的实现类”
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
  @Override
  public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext ctx) throws ServletException {
    // 这里的 webAppInitializerClasses 就是容器扫描出来的“候选类”,
    // 例如:MyWebAppInitializer.class、AnotherInitializer.class ...
    if (webAppInitializerClasses != null) {
      for (Class<?> clazz : webAppInitializerClasses) {
        // 你可以在这里反射实例化并调用它们的初始化方法
      }
    }
  }
}

// 2) 这是“被发现的实现类”(满足 @HandlesTypes 指定的类型条件)
public class MyWebAppInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext ctx) throws ServletException {
    // 做注册 Servlet/Filter/Listener 等
  }
}


SCI(ServletContainerInitializer)机制:HandlesTypes注解就会通过反射将MyServlet这个类型注入到webAppInitializerClasses参数当中,关键点不在于WebApplicationInitializer的实现,而是sci的实现标记了某个接口,然后c参数中会携带所有的实现类。ps: 该机制是tomcat等外部容器实现的一个机制。

那么这和注册有servlet有什么关系?通过这种方式我们可以只需要实现指定接口,例如WebApplicationInitializer接口,我们可以通过SpringServletContainerInitializer进行注册不同应用,而这些应用又具备不同servletContenxt.

没错,servletContenxt才是注册映射和servlet服务的关键

@Component
public class MyServletContainerInitializer implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic test = servletContext.addServlet("test", "cn.wenzhuo4657.test01.servlet.test01Servlet");

        test.addMapping("/test");
        test.setLoadOnStartup(1);
        System.out.println("MyServletContainerInitializer初始化成功!!");

    }


}

springboot和spring的注册

对于spring来说他通过SpringServletContainerInitializer来标记WebApplicationInitializer实现
对springboot则直接使用ServletContextInitializer,或者手动注册

这是为什么呢? 首先我们要明白servletContext实际上就是大部分映射的导航,例如springMVC容器中DispatcherServlet#getHandler中的关键参数handlerMappings最终来源就是ServletContext。

那么问题就很清楚了,问题不在于如何加载servletContext,而是正确加载,spring加载的方式实际上还是通过借助SCI机制,而springboot的嵌入式web容器不支持这种方式,所以他通过迂回的方式使用ServletContextInitializer进行注入管理。

posted @ 2025-10-03 17:35  wenzhuo4657  阅读(0)  评论(0)    收藏  举报