副标题#e#
深入领略OSGi WEB应用措施类型和GlassFish OSGi/WEB容器
相关文章:GlassFish OSGi-JavaEE (一) GlassFish与企业级OSGi开拓
http://www.bianceng.cn/Programming/Java/201312/38601.htm
在Part1中,我们提到了企业级OSGi拟定了一系列的类型来与JavaEE集成,个中,最具代表性的类型是OSGi WEB应用措施类型,这部门将教育各人深入领略OSGi WEB应用措施类型和GlassFish OSGi/WEB容器。本文将分成以下几个部门:
领略OSGi WEB应用措施类型
构建一个OSGi WEB应用措施
利用GlassFish 4陈设OSGi WEB应用措施
深入分解GlassFish OSGi/WEB容器
思考
领略OSGi WEB应用措施类型
为什么需要OSGi WEB
在信息和网络发家的本日,WEB应用措施已经很是得风行和普遍,一些任务要害型(Mission-Critical)的WEB应用措施天天都在高负荷地运行,很少有间断,因为一次不经意的间断大概造成数据的大局限丢失,以至损失大量的资金而造成严重的效果。这些任务要害型的WEB应用往往呈此刻证券和股票等相关的金融行业。此刻,我们开始思量一个场景: 几个礼拜可能几个月甚至几年后,WEB应用的客户可能提供商但愿在WEB前端增加一些新的模块或成果,可是,为了增加这些新的模块,我们不能遏制WEB应用,并且也不但愿再次重构可能改变WEB应用的现有架构和模块。这听起来不行思议,对付这样一个场景,至少应该遏制应用程处事的实例吧。可是,客户不会承诺。另一方面,在当今大数据的时代,每一秒钟城市有大量的数据进入我们的应用之中。那么,如何办理这样的场景?
一个可行的谜底是: 利用OSGi WEB构建我们的应用。
WAB-OSGi WEB的焦点
简朴地说,OSGi WEB应用措施类型(chapter 128 in the OSGi Enterprise Release 5 Specification[1])界说了OSGi WEB的全部内容。对付OSGi WEB应用措施,典范环境下,它由一个WEB应用措施Bundle(即Web Application Bundle,简称为WAB)所组成。
因此,首先我们需要领略WAB以及和WAR的区别。
WAB简述
在Part1中,我们已经提到Bundle是OSGi中的根基陈设和打点实体。所以,WAB首先是一个Bundle,必需提供成为Bundle的OSGi元数据(如, Bundle-SymbolicName, Bundle-Version…),其次,WAB与JavaEE中的WAR一样,依然是处事于WEB应用措施,可以或许利用Servlet 2.5或更高版本的Servlet类型,因此,WAB必需包括可会见的WEB内容,详细的说,Java Servlet类型界说了一个WEB应用措施的布局并界说了一个基于JAR的文件名目(WAR),WAB必需包括WAR中的静态和动态的内容。
进一步地,要成为一个WAB,需要在MANIFEST.MF文件中通过Import-Package来描写它的依赖,譬喻: 通过导入javax.servlet来利用Servlet的成果,别的,假如需要向外界提供处事,它也要通过Export-Package来导出处事地址的包。
我们可以或许通过差异的方法来安装WAB,譬喻,通过支持企业级OSGi的应用处事器所提供的呼吁行节制台(如,GlassFish Admin CLI),也可以通过措施的方法挪用OSGi底层API来安装WAB(如,BundleContext.installBundle)。无论哪一种方法,WAB安装后,它的生命周期打点就像OSGi运行时的其他Bundle一样。只不外WAB的生命周期被一个Web Extender跟踪,一旦WAB筹备处事WEB请求时,Web Extender需要将WAB中可会见的WEB内容陈设到WEB运行时。今后当WAB不再处事WEB请求时,Web Extender也需要将这些可会见的WEB内容从WEB运行时卸载掉。
关于WAB的安装,有一点需要特别说明,一个WEB应用措施可以或许在开拓阶段通过东西(譬喻, Maven插件)被打包成WAB然后举办安装,可能这个WEB应用措施可以或许在Bundle安装阶段通过Web URL Handler对尺度WAR举办转换来透明地建设WAB。GlassFish 4已经实现了后一种机制,我将在后续章节具体叙述。
关于Web Extender和Web URL Handler,它们都是OSGi WEB容器的一部门,我们将在后头章节具体叙述。
从上面的论述,我们已经看到了安装WAB与安装普通Bundle的明明的差异之处: 除了安装WAB到OSGi运行时,还需要将WAB中可会见的WEB内容陈设到WEB运行时。关于这一点,OSGi WEB应用措施类型界说了WAB的生命周期状态图,
图1: WAB的生命周期状态图
摘自: OSGi Enterprise Release 5 Specification
我们将在后续章节中深入叙述图1中的每个阶段。
WAB界说
WAB自己就是一个OSGi Bundle,因此,对付尺度OSGi Bundle的界说同样合用于WAB,可是,WAB与尺度OSGi Bundle本质的区别在于: WAB需要在MANIFEST.MF中界说Web-ContextPath属性。Web-ContextPath属性界说了这个WEB应用措施会见的上下文路径(Context Path)[2],在WEB处事器上,这个WEB应用措施中所有可会见的资源都要相对付这个上下文路径。譬喻, 假如在MANIFEST.MF界说了以下Web-ContextPath属性,
Web-ContextPath: /uas
那么会见这个WEB应用措施的URL老是相对付http://host:port/uas,需要留意的是: Web-ContextPath属性的值老是以斜杠’/’开始。
#p#分页标题#e#
当安装WAB时,除非Web-ContextPath属性呈此刻MANIFEST.MF中且Web-ContextPath的值是一个有效的值,不然,Web Extender会认为这不是一个WAB,而视为一个普通的Bundle。
#p#副标题#e#
WAB布局和相关的OSGi元数据
上面已经看到,除了尺度OSGi元数据,WAB必需要在META-INF/MANIFEST.MF文件中界说Web-ContextPath属性。譬喻,以下是一个WAB的布局,
图2: 一个WAB的布局示例
这个WAB界说的OSGi元数据如下所示,
图3:图2的WAB的OSGi元数据示例
在图2中,我们界说了一个WAB,这个WAB中有一个Servlet,被放在了WEB-INF/classes目次下,并且这个WAB有两个内部依赖,lib1.jar和lib2.jar。当安装WAB时,为了使这些动态的内容都可以或许被Web处事器会见到,我们就必需在这个WAB的MANIFEST.MF中凭据必然的法则指定OSGi元数据,也就是图3所示的那样,
指定一些必需的属性包罗Bundle-ManifestVersion、Bundle-SymbolicName、Bundle-Version。Bundle-Name是可选的,这是一个汗青遗留的属性,你可以不消指定它,可是我凡是也会指定这个属性,因为,Bundle-Name属性的值可以用来反应Bundle的用途。
指定Import-Package属性,因为这个WAB正在利用Servlet,所以我们导入了Servlet相关的包。
指定Bundle-ClassPath属性,这个属性很是重要,它界说了如何加载内部的依赖和WAB自身的类,我们把WEB-INF/classes/放在WEB-INF/lib/lib1.jar和WEB-INF/lib/lib2.jar的前面,这样做是为了和传统WAR文件搜索类的顺序一致,简朴地说,优先搜索WAB自身的Class,然后再搜索依赖的库文件。
指定Web-ContextPath属性。
通过对MANIFEST.MF追加OSGi元数据,也再次说明白WAB利用OSGi生命周期和类/资源加载法则而不是尺度JavaEE情况的加载法则,这点至关重要。
WAB的生命周期
在图1中已经提到了WAB的生命周期,仔细地与尺度OSGi Bundle的生命周期较量一下,你会发明,WAB的生命周期多了四个阶段(DEPLOYING、DEPLOYED、UNDEPLOYING和UNDEPLOYED)。
当一个WAB处于DEPLOYED阶段时,它已经做好了筹备来处事即将到来的WEB请求。处于DEPLOYED阶段也意味着这个WAB可能处于ACTIVE状态,可能处于STARTING状态(因为有一个懒惰的激活计策)。关于懒惰的激活计策,在《OSGi In Action》一书第 9.3节“Starting bundles lazily”有出色的先容。
对付具有懒惰的激活计策的WAB来说,Web Extender应该确保当处事WEB的静态内容(如图像资源、HTML和CSS等)时不能改变该WAB所处的状态,即仍然使它处于STARTING状态。
从图1中,我们可以或许清楚地看到,为了让WAB可以或许处事即将到来的WEB请求,WAB需要从 DEPLOYING迁移到DEPLOYED阶段,Web Extender必需陈设WAB中的WEB应用措施相关的类和资源到Web运行时。详细地,
期待WAB处于ACTIVE状态或STARTING状态
发送org/osgi/service/web/DEPLOYING事件
验证Web-ContextPath属性的值没有和其他已经被陈设的WEB应用措施的上下文路径斗嘴,也就是说担保上下文路径的独一性。假如有斗嘴,那么陈设WAB失败,Web Extender应该记录下陈设失败的日志。
假如3的验证通过,那么凭据以下的顺序,Web运行时开始处理惩罚陈设相关的细节,假如web.xml存在的话,它也会处理惩罚web.xml中的内容。
为这个WEB应用措施建设一个Servlet上下文
初始化设置的Servlet事件侦听器
初始化设置的应用措施过滤器等
注册Servlet上下文作为OSGi处事
发送org/osgi/service/web/DEPLOYED事件通知当前的WAB已经筹备好了,可以处事WEB请求。
假如在org/osgi/service/web/DEPLOYED事件发送前的任何时候有异常或错误产生,那么WAB的陈设将失败。
图1中我们也可以或许发明,一旦不再需要该WAB处事Web请求时,那么该WAB需要从DEPLOYED颠末UNDEPLOYING迁移到UNDEPLOYED阶段(UNDEPLOYING是一个暂态)。
有几种要领可以或许使WAB处于UNDEPLOYED阶段,
要领1: 遏制WAB
一旦吸收到WAB STOPPING事件,Web Extender必需立即从Web运行时中undeploy Web应用措施资源。Undeploy的主要步调如下:
发送org/osgi/service/web/UNDEPLOYING事件通知Web应用措施资源将被undeploy。
从OSGi注册表中移去Servlet上下文。
Web运行时必需让该Web应用措施遏制处事请求。
Web运行时必需清理所有Web应用措施相关的资源,如占用的JAR,以及清理ClassLoader制止内存泄漏等。
发送org/osgi/service/web/UNDEPLOYED事件。
要领2: 卸载(Uninstall)WAB
除了遏制WAB,也可以或许通过从OSGi运行时中卸载WAB来undeploy对应的Web应用措施资源,undeploy步和谐要领1一样。
要领3:遏制Web Extender
当遏制Web Extender时,所有被陈设的WAB都将被undeploy,可是,尽量WAB被undeploy了,它任然处于ACTIVE状态。
#p#分页标题#e#
从以上可以得出,WAB生命周期的四个特有状态差异于尺度OSGi Bundle的状态,WAB生命周期的特有状态并不受OSGi生命周期层节制,不是尺度的OSGi状态,这些特有的状态仅仅由Web Extender节制。
关于WAB生命周期,在“深入分解GlassFish OSGi/WEB容器”中将再次叙述。
别的,当你阅读OSGi Enterprise Release 5 Specification时,出格要留意不能将Uninstall和Undeploy等量齐观,尽量在一些场所下这两个术语都可以或许领略为“卸载”。
OSGi Web容器
最后我们来谈一下OSGi Web容器,在上面的章节中我们已经多次提到了Web Extender,Web 运行时以及Web URL Handler。这些实体组成了OSGi Web容器,而OSGi Web容器是OSGi Web类型的实现。按照OSGi Web类型,OSGi Web容器由以下三个实体组成:
Web Extender
验证是否为WAB而且跟踪WAB的生命周期,同时认真陈设WAB到Web运行时以及undeploy一个被陈设的WAB。
Web运行时
Web应用措施运行时情况,对付GlassFish来说,Web运行时基于Tomcat Catalina。
Web URL Handler
一个URL Stream Handler,这个URL Stream Handler可以或许处理惩罚webbundle: scheme,这个scheme可以或许被用来转换以及安装WAR到OSGi运行时中,GlassFish 4提供了一个新的特性,即通过实现这个URL Stream Handler在陈设时自动转换和安装WAR到OSGi运行时。
构建一个OSGi WEB应用措施
回到开始提出的问题场景,即如安在不断止JVM的环境下,构建一个动态的Web应用措施?
以下的Sample应用措施源于我曾经观测的一个GlassFish问题 [3],先看一下需求,
问题场景
我们但愿构建这样一个Web应用措施,当启动这个Web应用措施时,没有任何Web模块被加载,界面显示“No modules available.”,然后,当陈设一个Web模块后,在欣赏器上点击刷新按钮(不重启应用措施),这个Web模块随即呈此刻界面上。
开拓东西
在本文中我将利用如下的一些东西来构筑开拓情况,个中,按照小我私家的利用习惯,你也大概利用NetBeans或IntelliJ IDEA。
JavaSE7
Maven 3.0.4
Eclipse Kepler
应用措施架构
下面具体地说明一下图4,
将要建设的应用措施分成Web前端,存放模块接口的Core,以及实现模块接口的各个模块。Web前端回收JSF 2+CDI,也就是利用JavaEE CDI Bean与JSF页面举办绑定。应用措施的每个Web模块都需要实现Core中的模块接口。
Web前端以WAB方法打包,Core和每个模块打包成尺度OSGi Bundle。
每个模块有一个OSGi Activator,一旦陈设模块到GlassFish OSGi运行时,将首先执行模块的Activator要领来注册模块处事以便让Web前端的处事侦听器可以或许获取到相应的模块处事。
一旦Web前端的处事侦听器(ServiceListener)发明有新的模块被注册,那么该模块将被添加到应用措施Bean的模块荟萃中,雷同的,一旦发明既有的模块从OSGi处事注册表中删除,那么应用措施Bean的模块荟萃将移除该模块。
构筑开拓情况来建设应用措施
我们将利用Maven来一步一步地建设一个多模块的工程,我推荐利用如下的方法来建设多模块的工程,关于这种方法的具体说明,你可以或许参考[4]。
假设我利用Windows平台来建设Sample应用措施。
建设Sample应用措施的Parent Pom文件
运行Windows呼吁行,在当前的事情目次下,执行以下呼吁:
mvn archetype:create -DgroupId=cn.fujitsu.com.tangyong -DartifactId=glassfish.wab.sample -DarchetypeArtifactId=maven-archetype-site-simple
乐成执行后,你会发此刻当前事情目次下建设了一个“glassfish.wab.sample“目次,而且有一个pom.xml文件,这个文件就是Sample应用措施的Parent Pom文件。
设置Sample应用措施的Parent Pom文件
打开Sample应用措施的Parent Pom文件,放入以下的内容,
<build>
<finalName>${project.artifactId}</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<!– 2.2.0 and above have new bnd which has wab
instruction. 2.3.4 has
few important bug fixes. –>
<version>2.3.4</version>
<extensions>true</extensions>
<configuration>
<supportedProjectTypes>
<supportedProjectType>ejb</supportedProjectType>
<supportedProjectType>war</supportedProjectType>
<supportedProjectType>bundle</supportedProjectType>
<supportedProjectType>jar</supportedProjectType>
</supportedProjectTypes>
<instructions>
<!– Read all OSGi configuration info from this
optional file –>
<_include>-osgi.properties</_include>
<!– No packages are exported by default. Having
any pattern is dangerous, as the
plugin will add any package found in
dependency chain that matches the pattern as well.
Since there is no easy way to have an
include filter for just local packages, we don’t
export anything by default.–>
<Export-Package>!*</Export-Package>
</instructions>
</configuration>
…
</plugin>
…
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>4.2.0</version>
<scope>provided</scope>
</dependency>
…
</dependencies>
</dependencyManagement>
<dependencies>
<!– Add the the following dependencies to every module to save
user from
adding them to every one. –>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
…
</dependencies>
#p#分页标题#e#
以上内容基于https://svn.java.net/svn/glassfish~svn/trunk/fighterfish/sample/parent-pom/pom.xml ,完整的POM文件内容,请参照https://github.com/tangyong/GlassFishOSGiJavaEESample/blob/master/glassfish.wab.sample
/pom.xml。
你必然会问,为什么要放入这些内容?以下是几个重要的原因:
Maven工程的POM文件有很好的担任干系,就像面向工具的类设计一样,将子工程需要的一些共通插件(plugin)和共通的依赖(dependency)放入到Parent POM文件中老是很好的做法。
为了构建WAB,我们放入maven-bundle-plugin[5],maven-war-plugin[6]以及为了编译Java源文件所需要的maven-compiler-plugin[7]等。这里,需要说一下maven-bundle-plugin,这个插件的目标是将工程打包成尺度OSGi Bundle的文件名目,其内部利用了bnd[8],bnd是由OSGi同盟前主席Peter Kriens建设,用来简化开拓OSGi Bundle的疾苦。从上面的maven-bundle-plugin的设置看,有一个处所需要出格说明:
<instructions>
<_include>-osgi.properties</_include>
<Export-Package>!*</Export-Package>
</instructions>
上述的指令中,通过“_include”标签指定了一个设置OSGi元数据的文本文件,这个文本文件的位置相对付当前Maven工程的根目次(你也可以自行设置它的位置),osgi.properties中的内容是一组指定OSGi元数据的法则,以下是一个osgi.properties的示例:
Export-Package: \
sample.foo; \
sample.bar; version=${project.version}
Import-Package: \
sample.car;resolution:=optional, \
*
Bundle-SymbolicName: \
${project.groupId}.${project.artifactId}
…
关于具体的指定法则,请拜见[9]。
这里也要出格说明一下,我们利用Maven War插件的2.4版本而不是2.1版本,因为2.1版本在Windows平台上打包时,会生成两个web.xml文件。这个问题同样呈此刻fighterfish子工程的Sample Parent POM中,我将很快修复它。
Export-Package
#p#分页标题#e#
在上面的maven-bundle-plugin的设置中,还呈现了<Export-Package>!*</Export-Package>,这个标签以及标签值的寄义是,默认地,这个OSGi Bundle不导出任何包,除非我们显示地在osgi.properties中指定“Export-Package”值。
建设Core子工程
从Windows呼吁行进入“assfish.wab.sample“目次,执行以下呼吁:
mvn archetype:create -DgroupId=cn.fujitsu.com.tangyong -DartifactId=glassfish.wab.sample.core
乐成执行后,你会发此刻“glassfish.wab.sample“目次下建设了一个“glassfish.wab.sample.core“目次,进入“glassfish.wab.sample.core“目次并打开pom.xml文件,你会发明以下内容已经自动被添加了。
<parent>
<groupId>cn.fujitsu.com.tangyong</groupId>
<artifactId>glassfish.wab.sample</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
然后,在“glassfish.wab.sample.core“目次下建设一个osgi.properties文件,内容如下:
Export-Package={local-packages}; version=${project.version}
这样的话,当构建最终Bundle时,Bundle将导出内部的带有工程版本的包。
建设Web客户端子工程
雷同3,执行以下呼吁:
mvn archetype:create -DgroupId=cn.fujitsu.com.tangyong -DartifactId=glassfish
.wab.sample.web -DarchetypeArtifactId=maven-archetype-webapp
乐成执行后,你会发此刻“glassfish.wab.sample“目次下建设了一个“glassfish.wab.sample.web“目次。然后,新建src/main/java和src/main/resources/META-INF目次。默认地,这两个目次不会被建设。
接着,在“glassfish.wab.sample.web“目次下建设一个osgi.properties文件,内容如下:
Web-ContextPath:/wabsample
我指定了这个WAB的Web上下文路径为/wabsample,你也可以自行修改为其他的值。
建设WEB模块1子工程
雷同4,执行以下呼吁:
mvn archetype:create -DgroupId=cn.fujitsu.com.tangyong -DartifactId=glassfish
.wab.sample.module1 -DarchetypeArtifactId=maven-archetype-webapp
乐成执行后,你会发此刻“glassfish.wab.sample“目次下建设了一个“glassfish.wab.sample.module1“目次。
然后,打开该工程的pom文件,添加“glassfish.wab.sample.core“依赖声明,
<dependency>
<groupId>cn.fujitsu.com.tangyong</groupId>
<artifactId>glassfish.wab.sample.core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
建设WEB模块2子工程
雷同5,这里就跳过。
配置开拓情况
一旦这些Maven子工程工程建设乐成,我们将举办开拓情况的配置,进入编码阶段,以下的步调描写了如何将Maven集成到Eclipse。假定我的Eclipse事情空间(Workspace)是“E:\QCON\WS“。
修改Kepler中的M2_REPO变量
修改Kepler中的M2_REPO变量的目标是为了配置M2_REPO的值为你呆板上的Maven当地客栈(Local Repository)。默认地,Kepler中的M2_REPO变量的值为~/.m2/repository。详细的修改步调可以参照[10]。
为Maven工程建设Eclipse相关的文件(如,.project文件)
从Windows呼吁行进入“glassfish.wab.sample“目次,执行以下呼吁:
mvn eclipse:eclipse
然后将“glassfish.wab.sample“工程连同子工程导入到Eclipse中。假如一切乐成的话,在Eclipse中,应该看到雷同如下的画面。
图5: 乐成导入到Eclipse的Sample应用措施布局示意图
应用措施焦点逻辑
glassfish.wab.sample.core
新建一个名为“Module“的接口, 该接口的界说如下:
public interface Module {
public String getModuleName();
public URL getResource(String path);
public String getAboutPage();
}
glassfish.wab.sample.web
Web子工程的焦点是ApplicationBean类,这也是一个CDI Bean,而且和JSF页面绑定在一起,成为了JSF托管Bean(Managed Bean)。以下是home.xhtml页面中与ApplicationBean相关的内容,
<h:body>
<h:panelGroup layout="block" rendered="#{not empty applicationBean.modules}">
Modules:
<br />
<ui:repeat value="#{applicationBean.modules}" var="module">
<h:panelGrid columns="1">
<h:link outcome="#{module.moduleName}#{module.aboutPage}" value="#
{module.moduleName}" />
</h:panelGrid>
</ui:repeat>
</h:panelGroup>
<h:panelGroup layout="block" rendered="#{empty applicationBean.modules}">
No modules available.
</h:panelGroup>
</h:body>
#p#分页标题#e#
个中,#{applicationBean.modules}是JSF表达式语言,通过这个表达式,可以或许获取到ApplicationBean类实例中的modules变量的值。在设计这个页面时,我们通过<ui:repeat>标签动态地追加<h:panelGrid>,一旦有新的模块到来可能既有模块被移除,ApplicationBean类实例中的modules变量的值将产生改变,然后,当刷新欣赏器时,JSF页面将泛起出差异的内容。
那么,ApplicationBean类实例是如何跟踪到模块的注册和移除的呢?首先,让我们看一下ApplicationBean类的界说:
@Named
@ApplicationScoped
public class ApplicationBean {
@Inject
private BundleContext bc;
@Inject
private ModuleListener moduleListener;
private List<Module> modules = new ArrayList<Module>();
@Inject
public void initialize(ServiceTracker st) {
bc.addServiceListener(moduleListener);
loadServices(st);
}
public void afterAddModule(Module module) {
System.out.println("Module added.");
modules.add(module);
}
public void beforeRemoveModule(Module module) {
System.out.println("Module removed");
modules.remove(module);
}
public List<Module> getModules() {
return modules;
}
private void loadServices(ServiceTracker st) {
ServiceReference[] srs = st.getServiceReferences();
if (srs != null) {
for (ServiceReference sr : srs) {
Module m = (Module) bc.getService(sr);
modules.add(m);
}
}
}
}
以上界说中,moduleListener饰演了重要的浸染,moduleListener是org.osgi.framework.ServiceListener的一个实现,ServiceListener的浸染是用来跟踪OSGi处事的注册,更新以及卸载。afterAddModule 和beforeRemoveModule作为回调要领被moduleListener挪用,详细地,moduleListener中注入了ApplicationBean实例,一旦有新的模块到来,moduleListener就会通过ApplicationBean实例来挪用afterAddModule要领,假如既有的模块被移除,那么就挪用beforeRemoveModule要领。
在glassfish.wab.sample.web中尚有一些其他的类,因为篇幅干系,就纷歧一论述了,具体地内容请拜见: https://github.com/tangyong/GlassFishOSGiJavaEESample/tree/master
/glassfish.wab.sample/glassfish.wab.sample.web
glassfish.wab.sample.module1
模块1很简朴,只有两个类,实现Module接口的Module1类和BundleActivator的实现类Activator。我们必需要追加一个BundleActivator的实现类以便模块1在启动时可以或许将本身注册到GlassFish OSGi运行时的处事注册表中。
具体的内容请拜见: https://github.com/tangyong/GlassFishOSGiJavaEESample/tree/master
/glassfish.wab.sample/glassfish.wab.sample.module1
glassfish.wab.sample.module2
雷同于模块1,这里就省略跳过。
完整的Sample应用措施,请从https://github.com/tangyong/GlassFishOSGiJavaEESample 中下载。
利用GlassFish 4陈设OSGi WEB应用措施
一旦我们构建完Sample应用措施,就将利用GlassFish 4来陈设它。
安装和启动GlassFish
首先,你需要从以下链接下载一个GlassFish 4的安装zip包。然后解压缩这个zip包到当地的文件系统。
http://download.java.net/glassfish/4.0/release/glassfish-4.0.zip
然后,通过以下呼吁,启动GlassFish的domain,默认地,GlassFish会为你建设好一个domain。假设解压缩后的GlassFish地址的目任命$GlassFish_HOME暗示,
cd $GlassFish_HOME/glassfish4/glassfish/bin
asadmin start-domain
更多的关于GlassFish 4的文档,请参考: http://glassfish.java.net/documentation.html
陈设OSGi应用措施的方法
根基上,利用GlassFish 4陈设OSGi应用措施有三种方法,
利用asadmin deploy呼吁
在呼吁行可能Shell中,利用雷同如下的呼吁,
asadmin deploy –type=osgi XXX.jar或XXX.war
当陈设WAB时,常常容易漏掉—type=osgi,假如漏掉这个选项,那么你所做的就是在陈设一个尺度的WAR而不是WAB。
利用autodeploy的方法
这是一个很是快捷的陈设方法,你只需要将要陈设的Bundle放到$GlassFish_HOME/glassfish4/glassfish/domains/domain1/autodeploy/bundles目次下就可以了。这种方法是将Apache Felix File Install[11]集成到GlassFish中,利用这种方法甚至可以或许思量Bundle之间的依赖。具体地内容,请看一下[12]。
利用asadmin osgi呼吁
GlassFish 3答允你通过telnet登岸到GlassFish OSGi运行时的靠山,然后通过以下的方法来安装并启动一个WAB,
install webbundle:file:///tmp/mybundle.war
start <bundle_id>
#p#分页标题#e#
可是,到了GlassFish 4,这种telnet的方法已经被克制了,原因是telnet的方法并不安详,因此,GlassFish 4提供了一种新的方法去直接操纵OSGi运行时,即通过执行asadmin osgi …呼吁,譬喻,上面的呼吁等同于以下,
asadmin osgi install file:///tmp/mybundle.war
asadmin osgi start <bundle_id>
对付asadmin osgi呼吁,最常用的就是,当你陈设完一个OSGi Bundle可能想看一下某些Bundle的Id可能当前状态时,利用asadmin osgi lb呼吁可以或许罗列出OSGi运行时中所有的Bundle。
对付这三种方法,我越发倾向于利用“利用autodeploy的方法“,因为它越发简朴,更有效率。对付“利用asadmin deploy呼吁”,绝大大都场所,执行的效率也很好,可是,当你的措施利用vaadin时,陈设将会很是慢,这是GlassFish需要急需改造的一个特性,相信很快将会获得改进。
陈设并运行Sample应用措施
此刻,我们可以凭据如下的顺序陈设并运行Sample应用措施了,
陈设glassfish.wab.sample.core
执行“asadmin deploy –type=osgi glassfish.wab.sample.core.jar”
陈设glassfish.wab.sample.web.war
执行“asadmin deploy –type=osgi glassfish.wab.sample.web.war“
在欣赏器上键入“http://localhost:8080/wabsample/“,应该没有呈现任何模块,如下图所示,
陈设glassfish.wab.sample.module1和glassfish.wab.sample.module2
执行“asadmin deploy –type=osgi glassfish.wab.sample.module1.war“ 以及”asadmin deploy –type=osgi glassfish.wab.sample.module2.war“
在欣赏器上点击刷新按钮,此时,模块1和模块2都呈现了,如下图所示,
然后,再执行“asadmin osgi lb“呼吁看一下方才我们陈设的Bundle的状态,
执行以下呼吁卸载模块2
“asadmin undeploy glassfish.wab.sample.module2“
然后,在欣赏器上再次点击刷新按钮,此时,模块2已经消失了,如下图所示,
分解GlassFish OSGi/WEB容器
到这里为止,假如你仔细阅读上面的内容,我想你应该已经把握了如何开拓和陈设一个WAB,而且也应该领略了WAB和尺度OSGi Bundle以及和尺度WAR的区别。让我们再深入一下,看看GlassFish是如何实现OSGi WEB应用措施类型的。
殽杂应用措施Bundle(Hybrid Application Bundle)
从GlassFish的角度看,WAB又是殽杂应用措施Bundle的一种范例。殽杂应用措施Bundle既是一个尺度OSGi Bundle,又是一个JavaEE模块。在运行时,它既有一个OSGi Bundle上下文,又有一个JavaEE上下文。今朝,GlassFish支持两种范例的殽杂应用措施Bundle,Web应用措施Bundle和EJB应用措施Bundle。关于EJB应用措施Bundle,我将放在Part3中。
当一个殽杂应用措施Bundle被陈设到GlassFish OSGi运行时,GlassFish可以或许调查到它的生命周期,利用熟知的“Extender模式[13]“,将Bundle中的一些部门陈设或Undeploy到JavaEE容器中。殽杂应用措施Bundle的生命周期如下所示,
图6: 殽杂应用措施Bundle的生命周期
摘自: “OSGi Application Development using GlassFish Server“
假如你仔细看一下图6和图1,本质上两幅图是一样的,图6并没有在OSGi生命周期的根基状态上增加4个陈设和Undeploy相关的状态,可是,图1中的4个状态所涉及的操纵都反应到了图6中。
GlassFish OSGi Web容器的实现
GlassFish OSGi Web容器实现了OSGi Web应用措施类型。通过陈设WAB,我们可以或许清晰地领略GlassFish陈设OSGi Web应用措施的流程以及如何实现类型的。陈设流程分为两个阶段,
和陈设尺度OSGi Bundle一样,陈设WAB到OSGi运行时中。
当WAB的生命周期变为ACTIVE状态可能STARTING状态(因为有一个懒惰的激活计策)时,陈设该WAB到JavaEE运行时中。
需要留意的是,1和2是异步的,这与Undeploy进程差异,Undeploy是同步的,也就是说,一旦该WAB被遏制或卸载,将当即从JavaEE运行时中Undeploy该WAB,而且清理相应的资源。
以下,我将利用“asadmin deploy呼吁”来分解陈设的流程。
【阶段1】陈设WAB到OSGi运行时
阶段1的陈设主要包罗两个部门: a.安装WAB到OSGi运行时 b.启动该WAB使其处于ACTIVE状态可能STARTING状态。
以下是陈设WAB到OSGi运行时的时序图,
图7: 陈设WAB到OSGi运行时的时序图
#p#分页标题#e#
按照陈设的范例,ApplicationLifecycle类获取相应的AchiveHandler。因为我们正在陈设WAB,当执行“asadmin deploy“呼吁时,我们通报了“—type=osgi”,因此,陈设的范例为osgi。获取到的AchiveHandler是OSGiArchiveHandler。AchiveHandler认真处理惩罚和会见某种特定档案中的资源,这些档案包罗了WAR,JAR,RAR以及Bundle。AchiveHandler将在构建陈设ClassLoader,获取Sniffer等后续行动中被利用到。
别的,ApplicationLifecycle类是陈设的焦点类,也是陈设呼吁焦点逻辑执行的进口点,从它的地址的位置可以或许看出它的重要性,它位于GlassFish内核模块。
接下来,ApplicationLifecycle类通过SnifferManagerImpl类获取相应的Sniffer。那么,什么是Sniffer呢?自从GlassFish v3开始,按照陈设的请求,Sniffer被用来阐明和选择符合的容器来处理惩罚应用措施的范例。阐明和选择的进程大概简朴,也大概巨大。譬喻,通过查找WEB-INF/web.xml可能是否以.war末了来阐明是否需要WEB容器,也大概通过注解(Annotation)扫描来判定是否需要EJB容器。对付WAB的景象,SnifferManagerImpl返回了OSGiSniffer。进一步地,Sniffer接口有个重要的要领叫“getContainersNames”,对付OSGiSniffer,这个要领返回“osgi”。这个要领将被用来获取相应的容器。
有了详细的Sniffer之后,ApplicationLifecycle类通过ContainerRegistry类的getContainer(String containerType)要领来获取相应的容器,个中,containerType就是2)中提到的“getContainersNames”的返回值。进一步地,getContainer(String containerType)要领返回了一个EngineInfo工具,这个工具拥有特定容器的信息。对付WAB景象,这个特定的容器是OSGiContainer。以下是一个调试的信息,给出了EngineInfo工具中的内容。
个中,你可以发明container的范例是一个ServiceHandleImp,这是一个HK2相关的类,以下是OSGiContainer的代码,
@Service(name = OSGiSniffer.CONTAINER_NAME)
@Singleton
public class OSGiContainer implements Container {
public Class<? extends Deployer> getDeployer() {
return OSGiDeployer.class;
}
public String getName() {
return OSGiSniffer.CONTAINER_NAME; // used for reporting
purpose,so any string is fine actually
}
}
关于HK2的内容,我将在Part7中具体叙述。这里简朴地说一下,首先,HK2是一个JSR330的实现。其次,OSGiContainer利用@Service来标注这个类是一个HK2的处事,而且用name属性来利便HK2举办依赖注入。别的,利用@Singleton来标注当利用HK2获取OSGiContainer实例时,利用Singleton的方法。再者,这个类中最为重要的要领是getDeployer,该要领返回了OSGiDeployer,用于后续的OSGi陈设。
从以上的界说可以或许看出,OSGiContainer的实例由HK2认真建设并不是通过new出来的,因此,EngineInfo工具中的内容很自然地酿成了ServiceHandleImp。
接下来就是通过EngineInfo工具获取相应的Deployer了,Deployer真正认真陈设{3)中我们已经知道对付WAB景象,EngineInfo将返回OSGiDeployer。
然后,ApplicationLifecycle类委托OSGiDeployer类来安装WAB到OSGi运行时中,OSGiDeployer进而利用BundleContext来安装该WAB。安装乐成后,该WAB的状态将变为INSTALLED。
当安装乐成后,ApplicationLifecycle类开始委托OSGiDeployedBundle类来启动该WAB,虽然,在启动之前,需要首先判定该Bundle不是一个Fragment,然后再通过Bundle.start要领来启动该WAB。
上面提到的Sniffer等观念,在GlassFish Wiki[14]中有更为具体地说明。
【阶段2】陈设WAB到JavaEE运行时
在叙述阶段2之前,需要先回到GlassFish Domain的启动,这部门内容将在Part8中具体地说明。也许你会问,为什么要回到GlassFish Domain的启动?
原因在于从阶段1过渡到阶段2,需要做一些须要的事情,譬喻: 在“WAB生命周期”一章中,提到过为了陈设WAB到JavaEE运行时,前提条件是期待WAB处于ACTIVE状态或STARTING状态,那么如何期待?在OSGi开拓中,一个常见的模式是利用BundleTracker类来跟踪已被安装的Bundle的生命周期变革。凡是,打开BundleTracker的操纵是由OSGi Activator完成的,而OSGi Activator(假如有的话)是启动OSGi Bundle最先执行的要领,因此,必需有一个Bundle做这样的BootStrap行动。GlassFish OSGi-JavaEE遵循了这一设计模式,所以,为了搞清楚哪些Bundle在完成这些BootStrap行动,我们必需回到GlassFish Domain的启动。
GlassFish安装目次下有个目次叫glassfish4/glassfish/modules/autostart,这里安排了一些Bundle,个中,有两个Bundle与本文密切相关: 1) osgi-javaee-base.jar 2) osgi-web-container.jar。
首先,看一下它们的浸染,osgi-javaee-base是GlassFish OSGi-JavaEE实现的基类,主要利用了Extender模式来构建整个OSGi-JavaEE的框架,是GlassFish OSGi-JavaEE实现的魂灵。osgi-web-container实现了OSGi Web类型,也是本文重点要分解的工具。
#p#分页标题#e#
其次,osgi-javaee-base和osgi-web-container都界说了Activator,当启动GlassFish Domain后,osgi-javaee-base.jar和osgi-web-container.jar被陈设到GlassFish OSGi运行时中,且这两个Bundle都被激活处于Active状态,在达到Active状态之前,各自的Activator都被挪用。让我们来看看它们的Activator都做了什么。
osgi-javaee-base的Activator
osgi-javaee-base的Activator叫“OSGiJavaEEActivator”,它的start要领中焦点的逻辑是启动ExtenderManager,以及注册并启动JavaEEExtender。ExtenderManager的浸染是认真启动任何已经被注册的Extender处事。以下是相应的代码,
private synchronized void startExtenders() {
//Because of a race condition,we can be started multiple times, so
check if already started
if (extenderTracker != null) return;
// open will call addingService for each existing extender
// and there by we will start each extender.
extenderTracker = new ExtenderTracker(context);
extenderTracker.open();
}
可以清楚地看到,启动的逻辑主要在ExtenderTracker中,让我们看一下
private class ExtenderTracker extends ServiceTracker {
ExtenderTracker(BundleContext context)
{
super(context, Extender.class.getName(), null);
}
@Override
public Object addingService(ServiceReference reference)
{
Extender e = Extender.class.cast(context.getService
(reference));
logger.logp(Level.FINE, "ExtenderManager$ExtenderTracker","
addingService",
"Starting extender called {0}", new Object[]{e});
e.start();
return e;
}
…
ExtenderTracker是一个ServiceTracker,在OSGi开拓中,利用ServiceTracker来跟踪注册的OSGi处事已经成为了经典的模式。这里,ExtenderTracker跟踪的处事范例是Extender接口。一旦某个Extender被注册,那么ExtenderTracker将挪用addingService要领然后启动这个Extender。
前面提到,除了启动ExtenderManager,osgi-javaee-base也注册并启动JavaEEExtender,这个JavaEEExtender很是重要,它的浸染就是认真侦听和陈设殽杂应用措施Bundle。看一下它的start要领,
public synchronized void start() {
executorService = Executors.newSingleThreadExecutor();
c = new OSGiContainer(context);
c.init();
reg = context.registerService(OSGiContainer.class.getName(),
c, null);
tracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.
STARTING, new HybridBundleTrackerCustomizer());
tracker.open();
}
个中,最重要的是初期化并注册OSGiContainer以及打开一个BundleTracker来跟踪殽杂应用措施Bundle是否处于Active或Starting状态。对付OSGiContainer,它详细认真了陈设的进程,搭建了陈设的骨架。对付BundleTracker来说,它答复了早期提到的“如何期待WAB处于ACTIVE状态或STARTING状态”的问题。对付HybridBundleTrackerCustomizer类,个中的addingBundle要领值得我们看一下,
public Object addingBundle(final Bundle bundle, BundleEvent event) {
if (!isStarted()) return null;
final int state = bundle.getState();
if (isReady(event, state)) {
Future<OSGiApplicationInfo> future = executorService.
submit(new Callable<OSGiApplicationInfo>() {
@Override
public OSGiApplicationInfo call()throws Exception{
return deploy(bundle);
}
});
deploymentTasks.put(bundle.getBundleId(), future);
return bundle;
}
return null;
}
可以清晰地看到,一旦殽杂应用措施Bundle处于Active或Starting状态,那么,立即启动一个线程举办陈设。
osgi-web-container的Activator
osgi-web-container的Activator是OSGiWebContainerActivator,这个类的start要领很简朴,注册WebExtender作为OSGi处事。可以看出,osgi-web-container遵循了Extender模式,一旦注册乐成,osgi-javaee-base中的ExtenderTracker将跟踪到它并挪用它的start要领。下图是WebExtender的主要处理惩罚逻辑,
阶段2的前传已经讲完,接下来,回到阶段2的陈设上来,以下是阶段2中主要的陈设时序图,
图9: 阶段2中主要的陈设时序图
下面,具体地说明一下图9中的各个时序行动,
当JavaEEExtender中的HybridBundleTrackerCustomizer跟踪到WAB处于Active或Starting状态时,开始挪用OSGiContainer的deploy要领,这里的OSGiContainer来自osgi-javaee-base模块并不是阶段1中提到的OSGiContainer,请留意区分。
#p#分页标题#e#
OSGiContainer的deploy要领首先选择正确的Deployer,要领是通过遍历所有已经注册的OSGiDeployer处事,然后逐个挪用这些OSGiDeployer处事的handles要领来选择正确的Deployer。对付WAB景象,正确的Deployer是OSGiWebDeployer,它的handles要领如下:
final Dictionary headers = b.getHeaders();
return headers.get(Constants.WEB_CONTEXT_PATH) != null &&
headers.get(org.osgi.framework.Constants.FRAGMENT_HOST)
== null;
很清晰地看到,假如当前Bundle的元数据中包括了Web-ContextPath且不包括 Fragment-Host,那么该Bundle是一个WAB,且OSGiWebDeployer可以或许处理惩罚这种范例的殽杂应用措施Bundle。
选择完正确的Deployer后,OSGiContainer委托OSGiWebDeployer执行详细的陈设。OSGiWebDeployer的deploy要领首先建设OSGi陈设请求(OSGiWebDeploymentRequest),然后挪用OSGiWebDeploymentRequest的execute要领举办陈设,在execute要领中,主要执行预处理惩罚(preDeploy),陈设的筹备事情(prepare),实际的陈设(deploy),以及后处理惩罚(postDeploy)。
预处理惩罚的焦点逻辑是利用ContextPathCollisionDetector检测Web上下文路径斗嘴,这是OSGi Web类型必需的。
陈设的筹备事情中最重要的是建设一个OSGiWebDeploymentContext,OSGiWebDeploymentContext是GlassFish WAB支持的心脏,它认真为WAB建设一个类加载器(class loader)以便当有Web请求时,Web容器可以或许正确地加载到WAB中相关的静态资源和动态资源。这个类加载器为WABClassLoader,这个类加载器担任了org.glassfish.web.loader.WebappClassLoader,尔后者专门是GlassFish Web容器用来实现资源加载的。为了建设这个类加载器,需要重载OSGiDeploymentContext.setupClassLoader要领,如下所示:
protected void setupClassLoader() throws Exception {
finalClassLoader = new WABClassLoader(null);
shareableTempClassLoader = finalClassLoader;
WebappClassLoader.class.cast(finalClassLoader).start();
}
筹备事情做完后,开始执行实际的陈设,你大概已经发明,实际的陈设再次委托到了阶段1中提到的ApplicationLifecycle,是的,我们不需要再次发现轮子,因为ApplicationLifecycle在哪里,通过它,将这个WAB展开到glassfish4/glassfish/domains/domain1/applications中,在domain.xml中写入陈设的信息等,总之,像陈设尺度WAR一样去陈设它。
最后,照旧要执行一下后处理惩罚事情,因为,一旦前面的行动陈设失败了,老是需要举办回滚使系统的状态规复到之前。
至此,WAB的陈设以及相关的实现逻辑已经写完了,具体的代码可以利用SVN下载GlassFish FighterFish子工程(https://svn.java.net/svn/glassfish~svn/trunk/fighterfish)来研究一下。
最后,想简朴地说一下对付将来的一些思考。
思考
建造新的Maven Archetype
在Part1写完后,我看到有伴侣在评论部门提到了OSGi中看不顶用,从本文的WAB实例的构建看,确实也有未便之处。对付一些工钱需要手动举办设置的部门(如pom文件),最好可以或许尽大概的自动化。这项事情已经开始了!我和我的同事程晓明(@程_晓明)以及GlassFish FighterFish子工程的leader(Sahoo)正在建造新的Maven Archetype以便自动化一些繁琐的构建事情,应该很快就谋面世。
关于Sample的实用性
本文的Sample只是作为演示用,间隔真正的实用性尚有不小的差距,尤其是需要办理一个Bundle之间共享CDI BeanManager的问题,譬喻,让模块1中也可以或许利用JSF托管的Bean,然后这并不是一件容易的工作,这需要在Web模块和模块1中架起一座桥梁,以便Web模块中的BeanManager可以或许发明模块1中的CDI Bean。这个问题今朝正在和JSF以及CDI的leader举办接头,等候可以或许尽快办理。