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进行注入管理。