no manifiest section for signature file entry OSGI-OPT/src/org/osgi/service/device/DriverLocator.java

在使用maven-bundle-plugin在进行osgi bundle开发时,有时mvn install会碰到如下的报错:

错误:构造处理程序对象 no manifiest section for signature file entry OSGI-OPT/src/org/osgi/service/device/DriverLocator.java 时抛出异常

我碰到这个问百思不得其解,由于现在玩osgi的少,在google、stackoverflow上的资料偏少,很难找到解决办法,后来反复尝试,发现只要在pom配置时,保证配置的<Export-Package>其有一个真实存在包路径即可,不能写成*,否则就会报上面的错误;如果这个bundle确实没有package需要被export出去,那么就要写<Private-Package>,且里面要有具体的真实存在包路径或者包路径的通配符。

我用的maven-bundle-plugin是1.4.3版的,不知道后来的版本是否对这方面有改进。

还有类似原因的问题,比如在mvn install的时候出现如下的错误提示:

[WARNING] Warning building bundle ipojo.examples:hello.service:bundle:1.5.0-SNAPSHOT : Instructions for Export-Package that are never used: hello\.service\..*|hello\.service
[WARNING] Warning building bundle ipojo.examples:hello.service:bundle:1.5.0-SNAPSHOT : Superfluous export-package instructions: [hello.service.*]
[WARNING] Warning building bundle ipojo.examples:hello.service:bundle:1.5.0-SNAPSHOT : Did not find matching referal for *
[ERROR] Error building bundle ipojo.examples:hello.service:bundle:1.5.0-SNAPSHOT : The JAR is empty

这个解决方法,也和上面的相同,在maven-bundle-plugin中添加<Export-Package>或者<Private-Package>的配置即可。

 

源码解析之访问osgi felix bundle中的文件和资源

Apache Felix
Apache Felix

根据osgi规范中api的定义目前访问bundle里面的文件和资源的方法有三种。本文以felix框架为例,解析一下osgi中访问bundle里面的文件和资源的方法,以及各方法的区别。

一. Bundle.getEntry(String name)

该方法只能从当前的bundle jar中获得文件和资源(包括jar中的所有文件,比如*.class、META-INF\MANIFEST.MF等)。该方法的返回值为java.net.URL,在使用这个url的时候不能将其转成File类型来读取文件内容,因为这个url不是一个普通的文件操作系统中的url,而是osgi容器中的一个抽象的url,其文件读取操作必须交由创建这个url时设置的URLHandlersBundleStreamHandler来处理。所以如下的代码是不能获取到文件和资源的:

可以看到,从bundle中获取的entry的文件路径不是一个真实意义上的路径,它只是一个osgi容器才认识的url,要对这个url处理就需要用如下的方式,见示例代码:

接下来,从felix的源码中看看为何只能用下面的方式,这要从这个url是如何创建的来看了,在BundleImpl.getEntry(String name)方法中进行追踪,可以看到这个url是由BundleRevisionImpl.createURL(int port, String path)方法产生的,其代码如下:

在追踪下这个BundleStreamHandler,可以发现它是在Felix框架初始化的时候给默认设置的。如此就明了了。这个url对应的资源其实不能算是一个具体的文件,至少在Felix框架中不是,Equinox可能可以通过其他的方式来用文件方式来读取bundle中的资源,详见stackoverflow的一个question《No access to Bundle Resource/File (OSGi)》

二. Bundle.getResource(String name)

该方法的返回值也是URL类型的,其使用方法和Bundle.getEntry(String name)方法相同,也不能把url.getFile()当做文件名来创建File对象,只能用openConnection().getInputStream()来读取文件和资源的内容。

这个方法查找资源的方式和之前一篇博客《osgi类加载顺序源码解析》相似,在bundle没有解析时(即不处于resolved状态时),直接从当前bundle中查找资源;如果已经解析,就按照类加载的优先级,优先从其他bundle的classpath下查找这个文件或资源,如果有就返回;如果没有,才从本地bundle中查找并返回。(当然如果要共享某个bundle中的资源文件,就需要export这个资源文件所在的包,而且要使用这个资源的bundle也需要import这个包才行)。详见源码:

利用这一点,可以和getEntry方法搭配使用,达到游刃有余的控制资源共享的效果。

三. Bundle.getDataFile(String filename)

该方法是从felix cache的当前bundle的data文件夹下获取得文件和资源。这个data文件夹与felix cache的目录结构有关。默认情况下felix cache文件夹下会为每个bundle创建一个文件夹,文件夹的名称为bundle0、bundle1、bundle2。。。。bundleN,其中bundle0是系统bundle。每个bundle文件夹下又会为每个bundle的revision创建一个文件夹,文件夹名称的前缀为version(比如:version0.0),另外bundle文件夹下还有一个bundle.info的文件,描述了bunlde的信息(如果bundleId、被安装的bundle的jar文件路径、时间戳等)。如下图所示:

felix-cache目录结构
felix-cache目录结构

 

默认情况下,每个bundle目录下都没有data文件夹,getDataFile方法一旦被调用,就会在felix cache的当前bundle文件夹下,创建名为data的文件夹(如果没创建则创建之,如果已创建过,则跳过),该方法的参数是文件的相对路径,这个相对路径都是相对于当前bundle的data文件夹的;该方法的返回值是File类型,与上两个api不同,这个file是一个真实存在的文件,是可以通过file.toString()得到的url直接定位的。可以这样使用这个api:

这个方法相当于是提供了一个便捷的方式来存储和获取每个bundle下的数据。其源码其实最终的调用的BundleArchive类的getDataFile方法,源码如下:

 

综合使用上述的三个api就可以比较方便的读取bundle的资源了。另外还有getEntryPaths(String path)、findEntries(String path, String filePattern, boolean recurse)、getResources(String name)等一次性获取多个资源的api,还有便于读取menifest.mf文件的api:getHeaders()、getHeaders(String locale)等。

osgi类加载顺序源码解析

osgi容器中有三种classloader:各个bundle都有自己的classLoader,osgi容器也有一个框架级的classloader,这些classloader也不同于启动osgi容器的classloader(一般是app classloader)。如果需要加载一个类,那么就需要在这三个classloader之间挑出一个合适的classloader来加载类。本文将根据equinox和felix源码肤浅的描述一下osgi类加载顺序。

osgi规范中类加载顺序的定义

首先osgi类加载顺序不是开发osgi框架的人定的,而是osgi规范定的,但是每个框架的实现可能有所不同(规范详见osgi R4.3中的第3.9.4节 《Overall Search Order》):

在类和资源加载过程中,框架必须遵循以下规则。当请求一个bundle类加载器进行类加载或者资源的查找,查找必须按照以下顺序执行:

  1. 如果类或者资源是在包java.*中,那么交由父级类加载器代理完成,否则,搜索过程进入第二步。如果父类级类加载器加载失败,那么查找过程结束,加载失败。
  2. 如果类或者资源在启动代理序列(org.osgi.framework.bootdelegation)中定义,那么交由父级代理完成,此时的父级代理有启动参数org.osgi.framework.bundle.parent指定,默认是引导类加载器(bootstrap class loader),如果找到了类或者资源,那么查找过程结束。
  3. 如果类或者资源所在的包是在Import-Package中指定的,或者是在此之前通过动态导入加载的了,那么将请求转发到导出bundle的类加载器,否则搜索继续进行下一步;如果该包在启动参数org.osgi.framework.system.packages.extra中,则将请求转发给osgi容器外部的类加载器(通常是系统类加载器)。如果将请求交由导出类加载器代理,而类或者资源又没有找到,那么查找过程中止,同时请求失败。
  4. 如果包中类或者和资源所在的包由其他bundle通过是使用Require-Bundle从一个或多个其他bundle进行导入的了,那么请求交由其他那些bundle的类加载器完成,按照根据在bundle的manifest中指定的顺序进行查找进行查找。如果没有找到类或者资源,搜索继续进行。
  5. 使用bundle本身的内部bundle类路径查找完毕之后,。如果类或者资源还没有找到,搜索继续到下一步。
  6. 查找每一个附加的fragment的内部类路径,fragment的查找根据bundle ID顺序升序查找。如果没有找到类或者资源的,查找过程继续下一步。
  7. 如果包中类或者资源所在的包由bundle导出,或者包由bundle导入(使用Import-Package或者Require-Bundle),查找结束,即类或者资源没有找到。
  8. 否则,如果类或者资源所在的包是通过使用DynamicImport-Package进行导入,那么试图进行包的动态导入。导出者exporter必须符合包约束。如果找到了合适的导出者exporter,然后建立连接,以后的包导入就可以通过步骤三进行。如果连接建立失败,那么请求失败。
  9. 如果动态导入建立了,请求交由导出bundle的类加载器代理。如果代理查找失败,那么查找过程中止,请求失败。

下面的非标准流程图展示了查找的过程:

osgi类加载流程图
osgi类加载流程图

从规范可见,类加载的优先级顺序基本按照如下的原则:父容器classloader(通常是app classloader) –> 其他bundle的classloader –> 当前bundle的classloader –> 动态导入的包所在bundle的classloader。这个原则既可以使相同的类(包名也相同)尽可能只被加载一次,减少虚拟机perm区大小,也正因为如此,不同bundle中的相同的类,委托给同一个classloader加载,才能做到他们的对象和引用可以相互转换。(要知道一个类如果由不同的classloader加载后,其中一个classloader加载的类的对象是不能赋值给另一个classloader加载的类的引用的。)

osgi类加载顺序的源码解析

目前最流行的osgi框架实现有两个:equinoxfelix,下面分别列出equinox和felix在类加载顺序上的实现代码,如果大家在开发过程中发现一些类加载方面的奇怪现象,就可以通过找到如下的代码位置进行调试,从而跟踪出问题的根源,尤其是需要osgi容器内外共享类的时候。

equinox的osgi类加载顺序实现代码

在org.eclipse.osgi.internal.loader.BundleLoader.java的findClass(String name, boolean checkParent)和findClassInternal(String name, boolean checkParent, ClassLoader parentCL)方法中:

felix的osgi类加载顺序实现代码

在org.apache.felix.framework.BundleWiringImpl.java类的findClassOrResourceByDelegation(String name, boolean isClass) 方法中:

Reference

深入分析Java ClassLoader原理

 

[破除迷信]java.util.ArrayList在foreach循环遍历时可以删除元素

ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况。这时候大家都不会使用foreach循环的方式来遍历List,因为它会抛java.util.ConcurrentModificationException异常。比如下面的代码就会抛这个异常:

那是不是在foreach循环时删除元素一定会抛这个异常呢?答案是否定的。

见这个代码:

这段代码和上面的代码只是把要删除的元素的索引换成了4,这个代码就不会抛异常。为什么呢?

接下来先就这个代码做几个实验,把要删除的元素的索引号依次从1到5都试一遍,发现,除了删除4之外,删除其他元素都会抛异常。接着把list的元素个数增加到7试试,这时候可以发现规律是,只有删除倒数第二个元素的时候不会抛出异常,删除其他元素都会抛出异常。

好吧,规律知道了,可以从代码的角度来揭开谜底了。

首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常

其实,每次foreach迭代的时候都有两部操作:

  1. iterator.hasNext()  //判断是否有下个元素
  2. item = iterator.next()  //下个元素是什么,并赋值给上面例子中的item变量

hasNext()方法的代码如下:

这时候你会发现这个异常是在next方法的checkForComodification中抛出的,抛出原因是modCount != expectedModCount

  • modCount是指这个list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会自动增减;
  • expectedModCount是指Iterator现在期望这个list被修改的次数是多少次。

iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。

如果想让其不抛出异常,一个办法是让iterator在调用hasNext()方法的时候返回false,这样就不会进到next()方法里了。这里cursor是指当前遍历时下一个元素的索引号。比如删除倒数第二个元素的时候,cursor指向最后一个元素的,而此时删掉了倒数第二个元素后,cursor和size()正好相等了,所以hasNext()返回false,遍历结束,这样就成功的删除了倒数第二个元素了。

破除迷信,foreach循环遍历的时候不能删除元素不是绝对,倒数第二个元素是可以安全删除的~~(当然以上的思路都是建立在list没有被多线程共享的情况下)

webx学习及相关资料

webx是一套基于Java Servlet API的通用Web框架。它在Alibaba集团内部被广泛使用。从2010年底,向社会开放源码。
  1. webx的官方开源网址:http://www.openwebx.org/
  2. Webx框架指南:http://openwebx.org/docs/index.html
  3. 手把手创建第一个Webx应用:http://openwebx.org/docs/firstapp.html
  4. Webx表单验证服务指南:http://openwebx.org/docs/form.html
  5. Webx日志系统的配置:http://openwebx.org/docs/logging.html
  6. Webx辅助工具: