Tomcat中的可插拔以及SCI的实现原理

常用计算机的朋友一定记得, U盘,硬盘等设备流行的时候,当时对于这项技术的介绍是热插拔。

成都创新互联服务项目包括紫云网站建设、紫云网站制作、紫云网页制作以及紫云网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,紫云网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到紫云省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!

这个介绍最主要的是想说明这些外接设备的便利性,同时也说明他们的无侵入性。

在 Servlet 3.x 的时候,也增加了这种可插拔的能力,让我们在项目组织上,可以接近于设备的接入。

例如在 Servlet 3 之前只能在web.xml中声明 Servlet、Filter 等, 在 Servlet 3 之后,除了 @WebFilter 这种注解的方式外

还可以在单独的fragement 打包文件,在web-fragement.xml 声明的组件,容器启动时就会扫描到。

当然,也可以在运行时动态的添加Servlet/Filter,即Servlet 3.x中的 Dynamic Servlet。

除此之外,对于 SCI 的实现,提供的也是这种能力。通过对标准接口的实现,在特定阶段触发动作执行。

比如我们前面说到的 Spring Boot 的应用,其以 Jar 的方式启动,来启动容器,提供服务的实现,就是通过SCI的方式来触发的。Tomcat 是怎样处理 SpringBoot应用的?

甚至容器自行的一些组件,如JSP Container的实现,也使用 SCI 的能力来进行实现。

我们本次主要来分析 Tomcat 通过 SCI 实现的这种可插拔性(pluggability)。

首先,什么是 SCI?

全称 ServletContainerInitializer,是一个用于接收Web 应用在启动阶段通知的接口,再根据通知进行一些编程式的处理,比如动态注册Servlet、Filter等。

如何使用?

SCI 的使用也比较容易,将实现 ServletContainerInitializer 接口的类增加 HandlesTypes ,注解内指定的一系列类,接口,注解的 class 集合, 会在启动阶段 class 扫描的时候,将与这些 class 相关的 文件都扫描出来,做为 SCI 的onStartup方法参数传递。

这一类实现了 SCI 的接口,如果做为独立的包发布,在打包时,会在 JAR 文件的 META-INF/services/javax.servlet.ServletContainerInitializer 文件中进行注册。 容器在启动时,就会扫描所有带有这些注册信息的类进行解析,启动时会调用其 onStartup方法。

这就是可插拔性? 类加载***个表示不服。“我还可以热替换啊!” 这里是有区别的, 热替换,类加载,都是根据限定的名称去加载,并没有相关的标准去加载未知的内容,而这里SCI则根据约定的标准,扫描META-INF中包含注册信息的 class 并在启动阶段调用其onStartup,这就是区别啊。

百闻不如一见,光说不练假把式,我们来看除了前面说的 Spring Boot 外,谁还在用SCI。

我们先来看在 Tomcat 关于 WebSocket的实现。

 
 
 
  1. @HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class,
  2.         Endpoint.class})
  3. public class WsSci implements ServletContainerInitiali

这里的HandlesTypes里指明了实现 WebSocket需要关注的几个类,将通过注解方式声明WebSocket和通过编程方式声明都包含了进来。

在应用启动时,触发onStartup方法执行,然后初始化WebSocket相关的内容,解析注解等

 
 
 
  1. public void onStartup(Set> clazzes, ServletContext ctx)
  2.         throws ServletException {
  3.     WsServerContainer sc = init(ctx, true);
  4.     if (clazzes == null || clazzes.size() == 0) {
  5.         return;
  6.     }
  7.     // Group the discovered classes by type
  8.     Set serverApplicationConfigs = new HashSet<>();
  9.     Set> scannedEndpointClazzes = new HashSet<>();
  10.     Set> scannedPojoEndpoints = new HashSet<>();

这里注意,由于WebSocket并不是为特定应用提供的,而是做为容器的基础能力提供,并且其是在 Tomcat_home/lib 目录内,因此,每个应用在启动时,都会触发 WebSocket,来解析其是否包含了对于 WebSocket的引用,从而为其提供支持。

这一条流程是如何串连的呢?我们前面的文章曾分析过应用的部署,提到过HostConfig, ContextConfig这些类。 应用在启动时startup事件会触发 ContextConfig 这个Listener 的执行,此时会扫描应用包含的JAR文件,解析web-fragement.xml等, 这其中也包含对于SCI实现的解析。

 
 
 
  1. // Step 11. Apply the ServletContainerInitializer config to the
  2. // context
  3. if (ok) {
  4.     for (Map.Entry
  5.             Set>> entry :
  6.                 initializerClassMap.entrySet()) {
  7.         if (entry.getValue().isEmpty()) {
  8.             context.addServletContainerInitializer(
  9.                     entry.getKey(), null);
  10.         } else {
  11.             context.addServletContainerInitializer(
  12.                     entry.getKey(), entry.getValue());
  13.         }
  14.     }
  15. }

这里解析出来的类会添加到Context中,在应用启动阶段,会调用每个SCI实现的onStartup方法

 
 
 
  1. // Call ServletContainerInitializers
  2. for (Map.Entry>> entry :
  3.     initializers.entrySet()) {
  4.     try {
  5.         entry.getKey().onStartup(entry.getValue(),
  6.                 getServletContext());
  7.     } catch (ServletException e) {
  8.         log.error(sm.getString("standardContext.sciFail"), e);
  9.         ok = false;
  10.         break;
  11.     }
  12. }

SpringBoot 也是这样被点燃的

 
 
 
  1. public void onStartup(ServletContext servletContext) throws ServletException {
  2.     this.logger = LogFactory.getLog(this.getClass());
  3.     WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
  4.     if(rootAppContext != null) {
  5.         servletContext.addListener(new ContextLoaderListener(rootAppContext) {
  6.             public void contextInitialized(ServletContextEvent event) {
  7.             }
  8.         });
  9.     } else {
  10.         this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
  11.     }
  12. }

而且 JSP 的容器也开始使用这种方式进行工厂的初始化,以便于后面继续使用。

 
 
 
  1. /**
  2.  * Initializer for the Jasper JSP Engine.
  3.  */
  4. public class JasperInitializer implements ServletContain

那这个Jasper 的SCI,难道就为了初始化一个工厂吗?这和 Servlet 3.x之前也没啥区别是吧?

别急,我们继续看其onStartup方法

 
 
 
  1. public void onStartup(Set> types, ServletContext context) throws ServletException {
  2.  
  3. ...
  4.     // scan the application for TLDs
  5.     TldScanner scanner = newTldScanner(context, true, validate, blockExternal);
  6.     try {
  7.         scanner.scan();
  8.     } catch (IOException | SAXException e) {
  9.         throw new ServletException(e);
  10.     }

原来将 TLD文件的扫描移到了这里, WebContainer 只需要处理web.xml 和 web-fragement.xml的处理即可, JSP 的工作就交给他来做嘛,各司其职,挺好的。用 spec 的话来形容,是更好的分离了 Web Container 和 JSP Container职责。

【本文为专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】

当前名称:Tomcat中的可插拔以及SCI的实现原理
文章网址:http://www.gawzjz.com/qtweb/news39/207939.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联