在前一讲中,我们也大概知道了Servlet 3.0规范里面的一些简单注解,利用它们可以来注册Servlet、Filter以及Listener等组件。但是,这些注解不是我们要讲述的重点,因为毕竟原生的Servlet开发场景用到的还是比较少的。
那么,在这一讲中,我们来讲述什么呢?打开Servlet 3.0标准规范文档,找到Annotations and pluggability
这一章节下的8.2 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接口。
imeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;public class MyServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(Set<Class<?>> 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,如下所示。
imeixia.service;public interface HelloService {}
旋即,我们就可以在咱们自己编写的MyServletContainerInitializer实现类上写上这样一个@HandlesTypes(value={HelloService.class})
注解了。
imeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;imeixia.service.HelloService;@HandlesTypes(value={HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {/** 参数:* ServletContext arg1:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一,* 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到* * Set<Class<?>> arg0:我们感兴趣的类型的所有后代类型* */@Overridepublic void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {// TODO Auto-generated method stub}}
只要在@HandlesTypes
注解里面指定上我们感兴趣的类型,那么Servlet容器在启动的时候就会自动地将该类型(即HelloService接口)下面的子类,包括实现类或者子接口等全部都传递过来,很显然,参数Set<Class<?>> arg0
指的就是我们感兴趣的类型的所有后代类型。
接着,我们就为以上HelloService接口来写上几个实现。比如,先来写一个该接口的子接口,就叫HelloServiceExt,如下所示。
imeixia.service;public interface HelloServiceExt extends HelloService {}
再来创建一个实现该接口的抽象类,可以叫AbstractHelloService,如下所示。
imeixia.service;public abstract class AbstractHelloService implements HelloService {}
再再来创建一个该接口的实现类,例如HelloServiceImpl,如下所示。
imeixia.service;public class HelloServiceImpl implements HelloService {}
现在,HelloService接口下面有以上这三种不同的后代类型了。如此一来,Servlet容器在一启动的时候,就会把我们感兴趣的所有类型能传递过来,这时,我们就可以来输出一下了。
imeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;imeixia.service.HelloService;@HandlesTypes(value={HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {/** 参数:* ServletContext arg1:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一,* 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到* * Set<Class<?>> arg0:我们感兴趣的类型的所有后代类型* */@Overridepublic void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {// TODO Auto-generated method stubSystem.out.println("我们感兴趣的所有类型:");// 好,我们把这些类型来遍历一下for (Class<?> clz : arg0) {System.out.println(clz);}}}
可以看到,目前,我们暂时还用不到ServletContext对象参数。
最后,我们来启动项目,看一看Eclipse控制台会不会打印我们感兴趣的所有类型,如下图所示,确实是打印出了我们感兴趣的所有类型。
而且,还可以看到我们感兴趣的类型本身(即HelloService接口)没有打印之外,它下面的所有后代类型,不管是抽象类,还是子接口,还是实现类,都给打印出来了。
这也验证了这一点,即Servlet容器在启动应用的时候,会将@HandlesTypes
注解里面指定的类型下面的子类,包括实现类或者子接口等,全部都给我们传递过来。那这样有啥子用呢?只要给我们传入了某一感兴趣的类型,我们是不是就可以利用反射来创建对象了啊!
以上就是基于运行时插件的ServletContainerInitializer机制。这个机制最重要的就是要启动ServletContainerInitializer的实现类,然后就能传入我们感兴趣的类型了,该机制有两个核心,一个是ServletContainerInitializer,一个是@HandlesTypes
注解。
该机制我们就介绍到这,等到我们整合Spring MVC的时候,我们就会用到它了。
本文发布于:2024-02-04 14:31:42,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170709399256388.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |