Spring注解驱动开发第51讲——ServletContainerInitializer来了,傻孩子们,快跑啊!
发布日期:2021-06-30 17:56:34 浏览次数:3 分类:技术文章

本文共 5795 字,大约阅读时间需要 19 分钟。

写在前面

在前一讲中,我们也大概知道了Servlet 3.0规范里面的一些简单注解,利用它们可以来注册Servlet、Filter以及Listener等组件。但是,这些注解不是我们要讲述的重点,因为毕竟原生的Servlet开发场景用到的还是比较少的。

那么,在这一讲中,我们来讲述什么呢?打开Servlet 3.0标准规范文档,找到Annotations and pluggability这一章节下的8.2 Pluggability这一小节,找到之后,再找到该小节下的最后一个小节,即Shared libraries / runtimes pluggability,翻译过来,应该是共享库/运行时插件能力

在这里插入图片描述

这是一个非常重要的机制,该机制在后来我们框架整合里面用到的非常多,所以,这一讲我们就来讲下它。

Shared libraries(共享库)/ runtimes pluggability(运行时插件能力)

我们好好看看Servlet 3.0标准规范文档中Shared libraries / runtimes pluggability这一小节,大概在该小节的第二段描述中,有句话说的是,container(即Servlet容器,比如Tomcat服务器之类的)在启动我们的应用的时候,它会来扫描jar包里面的ServletContainerInitializer的实现类

哦豁,我们现在知道了,当Servlet容器启动我们的应用时,它会扫描我们当前应用中每一个jar里面的ServletContainerInitializer的实现类。那究竟是一个怎么扫描法呢?我们再好好看看该小节的第二段描述,它说,我们得提供ServletContainerInitializer的一个实现类,提供完这个实现类之后还不行,我们还必须得把它绑定在META-INF/services/目录下面的名字叫javax.servlet.ServletContainerInitializer的文件里面。

也就是说,必须将提供的实现类绑定在META-INF/services/javax.servlet.ServletContainerInitializer文件中,所谓的绑定就是在javax.servlet.ServletContainerInitializer文件里面写上ServletContainerInitializer实现类的全类名,也就是说,javax.servlet.ServletContainerInitializer文件中的内容就是咱们提供的ServletContainerInitializer实现类的全类名。

至此,我们才总算搞清楚了这个非常重要的机制,总结一下就是,Servlet容器在启动应用的时候,会扫描当前应用每一个jar包里面的META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,然后,再运行该实现类中的方法

接下来,我们就来测试一下该机制。

首先,我们来编写一个类,例如MyServletContainerInitializer,来实现ServletContainerInitializer接口。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override public void onStartup(Set
> arg0, ServletContext arg1) throws ServletException {
// TODO Auto-generated method stub }}

然后,按照Servlet 3.0标准规范文档中所说的,将以上类的全类名配置在META-INF/services目录下的javax.servlet.ServletContainerInitializer文件中。一开始甚至连META-INF/services目录都没有,更何谈什么javax.servlet.ServletContainerInitializer文件,因此,我们得在当前项目的 类路径(即src目录) 下把META-INF/services这个目录给创建出来,接着在该目录下创建一个名字为javax.servlet.ServletContainerInitializer的文件。

这一切都弄完之后,我们就将咱们自己编写的MyServletContainerInitializer类的全类名配置在javax.servlet.ServletContainerInitializer文件,如下图所示。

在这里插入图片描述

这样的话,Servlet容器在我们的应用一启动的时候,就会找到以上这个实现类,并来运行它其中的方法。

那么运行该实现类的什么方法呢?我们发现MyServletContainerInitializer实现类中就只有一个叫onStartup的方法,因此Servlet容器在我们的应用一启动的时候,就会运行该实现类中的onStartup方法。

而且,我们还可以看到该方法里面有两个参数,其中一个参数是ServletContext对象,我们对它已经很熟悉了,它就是用来代表当前web应用的,一个web应用就对应着一个ServletContext对象。此外,它也是我们常说的四大域对象之一,我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到。

说完其中一个参数,我们着重来说第二个参数,即Set<Class<?>> arg0,它又是什么呢?我可以参照Servlet 3.0标准规范文档中的下面第三段描述,描述说,我们可以在ServletContainerInitializer的实现类上使用一个@HandlesTypes注解,而且在该注解里面我们可以写上一个类型数组哟,也就是说可以指定各种类型。

在这里插入图片描述

那么,@HandlesTypes注解有什么作用呢?Servlet容器在启动应用的时候,会将@HandlesTypes注解里面指定的类型下面的子类,包括实现类或者子接口等,全部给我们传递过来。

实践是检验真理的唯一标准,我们用案例说话。我们不妨先写一个我们感兴趣的类型,比如一个接口,名字可以叫HelloService,如下所示。

package com.meimeixia.service;public interface HelloService {
}

旋即,我们就可以在咱们自己编写的MyServletContainerInitializer实现类上写上这样一个@HandlesTypes(value={HelloService.class})注解了。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext arg1:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext arg1) throws ServletException {
// TODO Auto-generated method stub }}

只要在@HandlesTypes注解里面指定上我们感兴趣的类型,那么Servlet容器在启动的时候就会自动地将该类型(即HelloService接口)下面的子类,包括实现类或者子接口等全部都传递过来,很显然,参数Set<Class<?>> arg0指的就是我们感兴趣的类型的所有后代类型。

接着,我们就为以上HelloService接口来写上几个实现。比如,先来写一个该接口的子接口,就叫HelloServiceExt,如下所示。

package com.meimeixia.service;public interface HelloServiceExt extends HelloService {
}

再来创建一个实现该接口的抽象类,可以叫AbstractHelloService,如下所示。

package com.meimeixia.service;public abstract class AbstractHelloService implements HelloService {
}

再再来创建一个该接口的实现类,例如HelloServiceImpl,如下所示。

package com.meimeixia.service;public class HelloServiceImpl implements HelloService {
}

现在,HelloService接口下面有以上这三种不同的后代类型了。如此一来,Servlet容器在一启动的时候,就会把我们感兴趣的所有类型能传递过来,这时,我们就可以来输出一下了。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext arg1:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext arg1) throws ServletException {
// TODO Auto-generated method stub System.out.println("我们感兴趣的所有类型:"); // 好,我们把这些类型来遍历一下 for (Class
clz : arg0) {
System.out.println(clz); } }}

可以看到,目前,我们暂时还用不到ServletContext对象参数。

最后,我们来启动项目,看一看Eclipse控制台会不会打印我们感兴趣的所有类型,如下图所示,确实是打印出了我们感兴趣的所有类型。

在这里插入图片描述

而且,还可以看到我们感兴趣的类型本身(即HelloService接口)没有打印之外,它下面的所有后代类型,不管是抽象类,还是子接口,还是实现类,都给打印出来了。

这也验证了这一点,即Servlet容器在启动应用的时候,会将@HandlesTypes注解里面指定的类型下面的子类,包括实现类或者子接口等,全部都给我们传递过来。那这样有啥子用呢?只要给我们传入了某一感兴趣的类型,我们是不是就可以利用反射来创建对象了啊!

以上就是基于运行时插件的ServletContainerInitializer机制。这个机制最重要的就是要启动ServletContainerInitializer的实现类,然后就能传入我们感兴趣的类型了,该机制有两个核心,一个是ServletContainerInitializer,一个是@HandlesTypes注解。

该机制我们就介绍到这,等到我们整合Spring MVC的时候,我们就会用到它了。

转载地址:https://liayun.blog.csdn.net/article/details/114844767 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第52讲——使用ServletContext注册web三大组件
下一篇:Spring注解驱动开发第50讲——带你走进Servlet 3.0的世界

发表评论

最新留言

不错!
[***.144.177.141]2024年04月18日 22时18分34秒