本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!