导出和导入Package

Export-Package用于声明Bundle要导出哪些Package,Import-Package用于声明Bundle需要导入哪些Package。这两个标记最简单的方式是直接跟随导入或导出的Package名称,如果导入或导出多个Package,则使用逗号分隔,如下所示:

Export-Package: org.osgi.service.io, org.osgi.service.web

Import-Package: org.osgi.service.io, org.osgi.service.web

这种写法是OSGi中导入导出Package的基本用法,也是最常见用法,已经能满足相当多的应用场景,但是在开发过程中总会遇见各种不同的需求,要根据特定规则去选择适合的Package。下面对OSGi规范中定义的几种对导入导出进行筛选的过滤方式进行介绍。

1.根据类名过滤

如果仅在Package层次上控制导出的粒度不够精细,无法满足应用需求,那么可以使用附加参数include和exclude进一步描述要导出Package中哪一部分内容。比如,只希望导出org.osgi.service.io包中命名以“IO”开头的接口,不导出实现类(假设实现类都以Impl结尾),那么可以这样写:

Export-Package: org.osgi.service.io;include="IO*"; exclude:="*Impl"

include和exclude参数的具体使用方法如下:

附加参数include用于列举需要导出Package中哪些类,类名使用逗号分隔,可以使用通配符。如果加入这个参数,那么只有符合规则的类才会被导出。

附加参数exclude用于列举禁止导出Package中哪些类,类名使用逗号分隔,可以使用通配符。如果加入这个参数,那么只要符合规则的类就不会被导出。

include和exclude的限制是在导出时处理的,导入时无需对应用做任何特殊声明,Import-Package标记也无法与这两个参数搭配使用。例如与前面配对的导入声明依然为:

Import-Package: org.osgi.service.io

2.根据版本过滤

在OSGi系统中,同一个名称的Package可能存在多个不同版本。假设Bundle C开发时引入了Spring 2.0版的Package,并且使用了某些只在这个版本私有的特征,而Bundle D开发时使用的是Spring 3.0版的Package,那么从这个系统中导出Spring的Bundle就必须明确指明Spring的版本号,以便导入时区分。示例如下:

Bundle A(导出Spring 2.0):

Export-Package: org.springframework.core; version="2.0.0"

Bundle B(导出Spring 3.0):

Export-Package: org.springframework.core; version="3.0.0"

对应的,导入时也要指明版本,准确地说应指明某个版本范围,例如:

Bundle C(导入Spring 2.x版):

Import-Package: org.springframework.core; version="[2.0.0,2.9.9]"

Bundle D(导入Spring 3.0以上版本):

Import-Package: org.springframework.core; version="3.0.0"

这里要注意我们导入的是“版本范围”而不是某个具体的“版本”,例如示例中Bundle D的Import-Package写法,只声明了version="3.0.0"的含义并不是“只导入”3.0.0版本的Package,而是导入3.0.0或以上版本的Package,因为这更符合一般的开发使用场景。如果需要指定只导入3.0.0版的Package,需要这样写:

Bundle E(只导入Spring 3.0版本):

Import-Package: org.springframework.core; version="[3.0.0,3.0.0]"

version参数的具体使用方法是:在导出Package时,此参数声明导出Package的版本号,如果不设置,默认值为0.0.0;在导入Package时,此参数声明要导入Package的版本范围,如果不设置,默认值为[0.0.0, ∞)。在声明版本范围时,方括号“[”和“]”表示“范围包含此数值”,圆括号“(”和“)”表示“范围不含此数值”。

在OSGi R3.x及之前的版本中,version标记原本叫做specification-version,在R4.x规范中新增了version标记,但依然保留了specification-version这个别名,但是已将它声明为Deprecated,如果同时设置了version和specification-version的值,那么这两个值必须相等。

3.根据提供者过滤

OSGi还允许开发人员根据Bundle名称、版本等信息来过滤Package,这种过滤方式在规范中被称为“选择提供者(Provider Selection)”。由开发人员明确指明导入的Package必须来自某个提供者的做法在实际开发之中是很少见的,它会增加系统的兼容性风险和人为的不确定因素。就如同我们组装电脑选择显示卡一样,理性的选择方式是根据性能需求确定显示芯片、显存大小和位宽等参数,再来选择合适的产品,而不是明确要求必须选择某公司出产的某型号产品。

根据提供者过滤一般使用在测试Bundle的时候,Import-Package标记提供了两个附加参数bundle-symbolic-name和bundle-version来完成这项功能,它们分别用于对Bundle的名称和版本进行过滤,示例如下:

Bundle A:

Bundle-SymbolicName: BundleA

Import-Package: com.acme.foo; bundle-symbolic-name="BundleB";

bundle-version="[1.4.1,2.0.0)"

Bundle B

Bundle-SymbolicName: BundleB

Bundle-Version: 1.4.1

Export-Package: com.acme.foo

上面配置指明一定要是来自于名称为“BundleB”并且版本在1.4.1至2.0.0之间的com.acme.foo包才会被选择。

bundle-symbolic-name和bundle-version参数的具体使用方法如下。

附加参数bundle-symbolic-name:参数值为某个Bundle的名称,只有符合该名称的Bundle所导出的Package才会被导入。

附加参数bundle-version:参数值为导入Bundle(注意不是Package)的版本范围,只有版本在该范围之内的Bundle所导出的Package才会被导入。

4.根据属性过滤

导入和导出的Package除了使用include、exclude对类名进行过滤,使用version对版本进行过滤和使用bundle-symbolic-name、bundle-version对提供者信息进行过滤外,还有第四种方式:使用自定义的扩展属性进行过滤。包名、类名和版本这几项信息都是很客观的数据,在代码开发完成那一刻就已确定下来,不可能随意改变。而自定义的扩展属性可以满足某些根据开发人员自己加入的属性进行过滤的需求。示例如下:

Export-Package: org.osgi.simple;filter="true";

在导出org.osgi.simple时加入了自定义属性filter,它的值为“true”(属性值按照字符串处理),那下面三句Import-Package中,只有第Bundle B、C可以成功导入前面发布的org.osgi.simple包,而Bundle A中由于自定义属性冲突导致而导致匹配失败,示例如下:
Bundle A:

Import-Package: org.osgi.simple; filter="false"

Bundle B:

Import-Package: org.osgi.simple; filter="true"

Bundle C

Import-Package: org.osgi.simple;

注意,Bundle C虽然没有声明自定义属性filter,但在默认情况下这并不会产生匹配冲突。如果要改变这种情况,可以使用mandatory附加参数,强制要求必须存在扩展属性才能成功匹配,示例如下:

Export-Package: org.osgi.simple; filter="true";mandatory:="filter"

这样,在下面两句Import-Package中,只有第一句能成功导入前面发布的“org.osgi.simple”包,因为它发布时filter属性被声明为“必须”的,示例如下:

Bundle A:

Import-Package: org.osgi.simple; filter="true"

Bundle B:

Import-Package: org.osgi.simple

如果没有在mandatory中指定属性名称,那这种属性被默认为“optional”,即可选的,在导入时没有声明这个属性也不影响正常导入。

mandatory参数的具体使用方法:只适用于Export-Package,用于声明哪些属性是必选的,只有导入Package时提供了必选的属性,才能正确匹配到导出的Package。多个属性用逗号分隔,用双引号包裹。

5.可选导入与动态导入

在大多数情况下,导入某个Package,就说明当前这个Bundle的正常运行是必须依赖导入的Package的,比如一个基于Spring开发的程序,没有导入Spring的Package就肯定无法运行。但还有另外一些场景,导入某个Package是为了实现一些不影响Bundle正常运行的附加功能,比如为某个英文软件开发了一个实现中文语言支持的插件,不导入这样的Package也不应当影响整个系统正常运行,只不过软件仍以英文形式显示而已。

Import-Package标记也可以很好地支持上述需求,它有一个名为resolution的附加参数,用于说明一个依赖的Package是否是必需的。示例如下:

Bundle A:

Import-Package: org.osgi.simple;resolution:="mandatory"

Bundle B:

Import-Package: org.osgi.simple

Bundle C:

Import-Package: org.osgi.simple; resolution:="optional"

在上面例子中,Bundle A和Bundle B的Import-Package是等价的,因为resolution的默认值就是mandatory。在这个设置下,如果没有任何Bundle导出org.osgi.simple包,那么Bundle A 和Bundle B将会解析失败。而Bundle C不会,因为它明确指出了这个Package的导入是可选的(resolution:="optional")。

resolution参数的具体使用方法是这样的:附加参数resolution只适用于Import-Package标记,用于确定某个Package是可选的还是必须的。

还有一种场景,可能某个被导入的Package确实是必需的,但是提供这个Package的Bundle并不会在系统启动时就被安装。在这种情况下,只有真正使用到这个Package的类时,才会去导入这个Package,而不是在启动时就查找是否有提供Package的Bundle存在。

DynamicImport-Package标记可以处理这样的需求,这个标记的语义和Import-Package标记很类似,不同点在于DynamicImport-Package标记不在Bundle的解析阶段进行处理,无论它要求导入的Package是否存在,都不会影响Bundle的解析,示例如下:

Bundle A:

Import-Package: org.osgi.simple

Bundle B:

DynamicImport-Package: org.osgi.simple

如果没有任何Bundle提供org.osgi.simple包,那么Bundle A将无法解析,而Bundle B不受影响;但是如果真正使用到org.osgi.simple中的类时还是没有任何Bundle可提供,那么Bundle B依然要抛出ClassNotFoundException异常。

DynamicImport-Package也可是使用version附加参数来过滤导入Package的版本,在导入Package时声明属性,以便符合Package导出时mandatory参数中声明必须存在属性。

与Import-Package有所不同的是,DynamicImport-Package可以使用通配符,举一个极端的例子:

DynamicImport-Package: *

如果某个Bundle的元数据信息中有上面这行定义,那么它就成了一个“管理员级别”的Bundle,它可以访问整个系统中所有被导出过的Package。这样做很方便,却违背了OSGi中提倡的封装与按需使用的原则,会带来许多遗患,因此这并不是一种好的使用方式。

动态导入和可选导入实现的功能有些类似,它们的共同特征是在Bundle解析期间即使找不到要导入的依赖,也不会导致解析失败。它们的区别是,动态导入每次加载包中的类都会尝试去查找动态导入包,而可选导入包只有在Bundle解析时才进行连接尝试。

6.导出Package的依赖限制

如果导出Package的全部依赖都集中在一个Bundle之中,那么这个Package的导出是完全不受限制的。但是如果要导出的Package中有某些类还依赖于其他Bundle所提供的类,比如从其他Bundle所导出的Package中的类继承,或者其他Bundle的类出现在方法的声明中,在这种情况下,要导出这个Package就会受到依赖限制,必须先保证依赖的Package是可用的,才能保证导出的Package是可用的。

这种Package之间的关系可以通过在Export-Package标记中的uses附加参数来描述。例如:包org.osgi.service.http使用了包javax.servlet.http中的API,因此这两个包存在依赖关系。在导出org.osgi.service.http的Export-Package标记中就应当包含值为javax.servlet的uses参数,如下所示:

Export-Package: org.osgi.service.http;uses:="javax.servlet.http"

当一个系统中同时存在不同版本的Package时,uses参数对于协调依赖关系很有用。比如上面例子中的javax.servlet.http包同时存在2.4和2.1两个版本,而Bundle A导入的是2.4版,如下所示:

Bundle A:

Export-Package: org.osgi.service.http;uses:="javax.servlet.http"

Import-Package:javax.servlet.http; version="2.4"

对于任意一个Bundle,只要导入Bunlde A中发布org.osgi.service.http包,同时又导入了javax.servlet.http包,即使在导入的时候不明确指明其版本,OSGi实现也必须保证它只会使用2.4版的javax.servlet.http,如下所示:

Bundle B:

Import-Package:org.osgi.service.http  // 由Bundle A提供

,javax.servlet.http // 这里默认会导入2.4版的Package

uses参数的具体使用方法是:附加参数uses只适用于Export-Package标记,用于说明导出Package的依赖关系。如果同时依赖多个Package,使用逗号分隔,并用双引号包裹。

当OSGi容器中的Bundle不断增加,依赖关系逐渐变得复杂时,uses参数就是协调依赖的必要手段,也是配置OSGi依赖的难点之一。在2.4.2节中我们还会对如何使用uses参数协调Package依赖约束进行更详细介绍。

7.导入整个Bundle

前面几项介绍的都是Package级别的导入、导出和依赖,OSGi还可以支持Bundle级别的依赖关系。我们可以使用Require-Bundle标记来声明要导入这个Bundle,如果成功导入了这个Bundle,那就意味着导入了这个Bundle中所有声明为导出的Package。

Require-Bundle标记后面应跟随一个或多个其他Bundle的名称(由Bundle-SymbolicName定义的名称),多个名称之间使用逗号分隔,如下所示:

Require-Bundle:BundleA、BundleB、BundleC

如果导入了某个Bundle,就可以重复导出该Bundle中已导出过的Package,就如同导出自己的Package一样,例子如下所示:

Bundle A:

Require-Bundle: BundleB

Export-Package: p

Bundle B:

Export-Package: p;partial=true;mandatory:=partial

这时如果有Bundle C要导入Package p,而又没有声明属性partial,那它将会从Bundle A中导入Package p。实际上Bundle A只承担了转发的工作,真正的Package p在Bundle B之中。这种情况属于拆分包的一个特例,应尽可能避免。

注意 拆分包(Split Packages)是指OSGi容器中有两个或两个以上的Bundle导出了相同名称的Package。容器存在拆分包会令包导入过程变得复杂,因为只带有包名的Import-Package标识无法保证能正确导入到所需的Package。必须通过过滤或者使用Require-Bundle才能导入正确的Package。

与Import-Package类似,Require-Bundle标记也有附加参数bundle-version和resolution。bundle-version用于过滤导入Bundle的版本,默认值为[0.0.0,∞),即不限制版本范围。resolution用于确定导入的Bundle是否是必需的,可选值为mandatory和optional,默认值为mandatory,含义与Import-Package标记的resolution参数相同,这里就不再详细举例介绍了。

在默认设置下,导入了某个Bundle,仅表示在本Bundle中可以访问被导入Bundle中声明导出的Package,除非明确用Export-Package声明过,否则这些Package在本Bundle中默认不会再次被导出。如果有必要,可以使用Import-Package标记的visibility附加参数来改变这种行为,示例如下:

Bundle A

Require-Bundle: BundleB;visibility:=reexport

Bundle B

Export-Package: org.osgi.service.http

在上面例子中由于明确设置了visibility的值为reexport,Bundle A中就会重复导出Bundle B的声明导出的包,即org.osgi.service.http。

visibility参数的具体使用方法是:附加参数visibility仅用于Require-Bundle标记,描述来自被导入Bundle中的Package是否要在导入Bundle中重新导出。默认值为private,代表不会在导入Bundle中重新导出。如果值为reexport,则说明需要重新导出。

在元数据信息中可以同时使用Import-Package和Require-Bundle标记来获取Bundle所需的依赖包,但是如果某个依赖的Package同时在Import-Package列表和Require-Bundle的Bundle中存在,那么OSGi实现框架必须保证要以Import-Package列表中的包优先。

使用Require-Bundle有时候确实会获得一些便利,但从长远来看,依赖的粒度越小越好。依靠Require-Bundle来处理依赖关系并非一种好的开发实践,甚至可能会带来一些令人头痛的问题,我们将在2.4.2节中通过实际例子来介绍Require-Bundle的缺陷。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在VS2019中,我们可以使用一个叫做“EPPlus”的第三方库来导入导出Excel文件。 首先,我们要在项目中添加EPPlus库。我们可以通过NuGet来添加这个库,也可以手动下载并在项目中引用。在添加完EPPlus后,我们需要在代码中添加如下语句来引用该库: using OfficeOpenXml; //Excel处理库 然后我们需要编写代码来实现Excel文件的导入导出。 1. 导出Excel文件 我们需要定义一个方法来导出Excel文件,示例代码如下: private void ExportExcel() { using (ExcelPackage package = new ExcelPackage()) { ExcelWorksheet sheet = package.Workbook.Worksheets.Add("Sheet1"); //创建工作表 sheet.Cells[1, 1].Value = "姓名"; sheet.Cells[1, 2].Value = "年龄"; sheet.Cells[2, 1].Value = "张三"; sheet.Cells[2, 2].Value = 18; sheet.Cells[3, 1].Value = "李四"; sheet.Cells[3, 2].Value = 25; Response.Clear(); Response.BufferOutput = false; Response.AddHeader("content-disposition", "attachment; filename=Export.xlsx"); //指定下载的文件名 Response.ContentType = "application/octet-stream"; Response.BinaryWrite(package.GetAsByteArray()); Response.End(); } } 在这个方法中,我们创建了一个ExcelPackage对象,并添加了一个名为“Sheet1”的工作表。在工作表中,我们添加了表头和数据。最后,我们把文件转换成二进制数组并通过Response.BinaryWrite方法输出到页面。在页面中,我们会看到浏览器自动下载了一个名为“Export.xlsx”的Excel文件。 2. 导入Excel文件 我们需要定义一个方法来实现Excel文件的导入,示例代码如下: private void ImportExcel(HttpPostedFileBase file) { using (ExcelPackage package = new ExcelPackage(file.InputStream)) { ExcelWorksheet worksheet = package.Workbook.Worksheets[0]; //获取第一个工作表 int rowCount = worksheet.Dimension.Rows; //获取行数 int colCount = worksheet.Dimension.Columns; //获取列数 for (int row = 1; row <= rowCount; row++) { for (int col = 1; col <= colCount; col++) { string value = worksheet.Cells[row, col].Value?.ToString().Trim(); //获取单元格的值 } } } } 在这个方法中,我们首先创建了一个ExcelPackage对象,并从传入的文件流创建一个ExcelWorksheet对象。然后,我们获取工作表的行数和列数,遍历每个单元格并获取单元格中的值。我们可以将这些值存储到数据库中,或者进行其他的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值