通过使用com技术,我们用微软office应用程序能够建立很多应用程序扩展,但是java开发人员却无法享受它带来的便利--除非他们拥有方便的java访问com的途径(java-to-com桥)。使用jcom的时候,你可以在java中控制几乎所有的com对象,而且它还带有一些用于excel的强大的辅助类。
在你每次编写用html表格样式或java表格对象显示数据的应用程序的时候,通常都需要带有"导出到excel"功能。那么头疼的问题就出现了。怎么样实现这种功能呢?在html中显示的可以在office 2003中处理吗?没有这么好!你还必须支持office 97!
你只能去找一个符合当前需求的工具了,但是接着收到更多的要求了。"这能在word中做到吗?powerpoint能做到吗?能不能用调制解调器拨号到远程服务器上并发布数据?java无法实现这些功能是什么意思啊?java可以实现任何功能。"
感谢作为java和com桥梁的框架组件,它使你在遇到这些情况的时候都可以回答"yes"。java-com桥梁使你能够根据自己的需要操作windows组件--以前这是vb、c++和.net开发人员的领地。你通过实现一个与dcom后端(back end)对话的java前端(front end),可以远离端对端(end-to-end)的com系统。在本文的末尾,你可以使用其中一个java-to-com桥:它可以被命名为jcom。
excel基础知识
开始之前,你需要首先从sourceforge网站下载api。它包含了jcom所使用的java类的所有源代码、c++代码和jcom用于配置java和com的编译好的dll。把这个dll放到你的java主目录的/bin/目录下面,否则会出现问题。同时,为了不出现问题,还要正确地设置java_home环境变量。jcom的大多数文档目前都是用日语写的,但是翻译工作正在进行中,因此以后会有些改进的。
下载和安装过程完成以后,用列表1中的代码试一试。这段代码会建立到excel的jcom接口,并把"hello world"写入第一个单元格中。你可以看到如图1所示的结果。尽管jcom是一个通用的com类库,但是还是带有很多用于excel的辅助类,这是因为excel可能是最常用的自动化com应用程序。这些辅助类可以为我们节省很多时间,它们使jcom成为一个更好的类库了。
列表1:开始使用jcom和excel
import jp.ne.so_net.ga2.no_ji.jcom.excel8.*;
import jp.ne.so_net.ga2.no_ji.jcom.*;
public class testsimple
{
public static void main(string[] args) throws exception {
releasemanager rm = new releasemanager();
try {
system.out.println("excel is starting...");
excelapplication excel = new excelapplication(rm);
excel.visible(true);
excelworkbooks xlbooks = excel.workbooks();
excelworkbook xlbook = xlbooks.add();
excelworksheets xlsheets = xlbook.worksheets();
excelworksheet xlsheet = xlsheets.item(1);
excelrange xlrange = xlsheet.cells();
xlrange.item(1,1).value("hello, world!" );
}
catch(exception e) { e.printstacktrace(); }
finally { rm.release(); }
}
}
图1:java中使用excel的第一个com自动化
如果曾经使用vb或vba来自动化excel,那么你应该很熟悉列表1中的代码了。excelapplication类暴露了workbooks()属性,它列举出excel中当前打开的工作薄(workbook),并允许你向运行的excel副本增加或删除工作薄。使用.add()方法增加工作薄,这个方法会返回一个工作薄的引用。
在excel中工作薄用于保存输入的数据。工作薄是xls文件的基础。一个工作薄由多个工作表(worksheet,在图1中有三个工作表,分别叫做sheet1、sheet2和sheet3)。工作表通过item属性来枚举。你可以使用这个属性访问特定的工作表。在图1中,sheet 1是第一个工作表,因此你可以使用xlsheets.item(1)来获取对它的引用。
有了工作表之后,你就可以使用range(范围)来操作它上面的数据。range是一个单元格或多个单元格。例如,单元格a1可以使用range (1,1)来引用,接着可以使用value属性把数据载入单元格中。
更有意义的例子
假设你希望把excel作为数据库中某些数据的表现层。在java中你希望通过jdbc获取数据,并把数据显示在excel前端。这种假设是很好的,因为复杂业务逻辑中的用例(use case)已经用excel前端显示了,更不用说工作流中的其它的数据项的显示和交互操作了。现在你不需要了解业务逻辑或分析,就可以使用原始的电子表格,并使用java中的自动化来"填充它们之间的裂痕"。
我将给出一个演示这种操作的简单示例:本文下载中所包含的内容是建立比较销售行为的一个简单的mysql数据库的。它有三个表:
? sales是销售的细节信息,包括销售项、数量、销售价格、是谁销售的以及销售地区。
? people包含销售人员的姓名和佣金。
? districts包含了销售地区的名称和税率。
生成报表的时候,你需要使用下面的业务逻辑来算出真正的数值:
? 毛销售额等于销售项乘以销售价格加上地税金额。
? 纯销售额等于毛销售额减去销售人员提取的税前佣金。
这都是一些在java中可以实现的简单直接的计算过程,但是我在本文中使用它们的目的是演示如何把这些数据写入excel电子表格并让excel自动计算。对于更复杂的情况(使用了更复杂的excel公式),原理也是一样的。
此外,下载的内容中还包含了如图2所示的电子表格。它是作为"模板"供你填充适当的数据的电子表格。
图2:用于前端填充数据的excel模板
使用jcom的时候,查询数据库中所有销售数据需要使用下面的sql:select sales.id, sales.description, sales.quantity, sales.price, districts.districtname,districts.salestax,people.name,people.commission
from 'sales','districts','people'
where (sales.district = districts.id) and (sales.salesperson = people.id)
接着使用自动化(automation)把每条记录都写入excel电子表格的适当的列中。下载的内容中包含了完整的代码(列表2所示)。在下面的部分,我将为你解释代码中使用jcom的自动化部分。
列表2:从数据库中获取数据并载入excel中的代码// 首先建立excel的引用
excelapplication excel = new excelapplication(rm);
// 使它可视
excel.visible(true);
// 接着打开我们将使用的模板工作薄
excelworkbooks xlbooks = excel.workbooks();
excelworkbook xlbook = xlbooks.open("c://sales.xls");
// 接着获取我们将修改的范围的引用
excelworksheets xlsheets = xlbook.worksheets();
excelworksheet xlsheet = xlsheets.item(1);
excelrange xlrange = xlsheet.cells();
…
// 此处放置数据库链接和查询建立代码
…
if (stmt.execute(strsql))
{
rs = stmt.getresultset();
}
int ncolcount = rs.getmetadata().getcolumncount();
int nrow=1;
int ncol=0;
// 在记录集中循环
while(rs.next())
{
// 记录集中的每行都是电子表格中的一行
nrow++;
for(int i=1;i<=ncolcount;i++)
{
// 匹配db列和电子表格列
ncol = getexcelcolumn(i);
// 查找于行和列对应的单元格,并把它设置为适当的记录集字段
xlrange.item(nrow,ncol).value(rs.getobject(i).tostring());
}
// 填入公式
xlrange.item(nrow,7).value("=rc[-2]*rc[-1]");
xlrange.item(nrow,9).value("=rc[-2]*rc[-1]/100");
xlrange.item(nrow,10).value("=rc[-3]+rc[-1]");
xlrange.item(nrow,11).value("=rc[-4]*((100-rc[2])/100)");
}
在列表2的第一部分中,其目标是获取希望修改的单元格的控制权。这会花费了一定的开销。
? 首先你必须获取表现excel本身的对象,有了jcom辅助类的帮助以后,这一步操作相当直接。excelapplication excel = new excelapplication(rm);
? 下一步,你希望获取对工作薄集合的访问权。你希望打开自己的模板工作薄(本示例中这个模板在c:/sales.xls中),在工作薄集合中打开它。excelworkbooks xlbooks = excel.workbooks();
? 下一步,你希望打开自己的工作薄并获取该工作薄集合的引用。excelworkbook xlbook = xlbooks.open("c://sales.xls");
excelworksheets xlsheets = xlbook.worksheets();
? 最后,你希望获取集合中的第一个工作表,并把工作范围定义为整个工作表。excelworksheet xlsheet = xlsheets.item(1);
excelrange xlrange = xlsheet.cells();
完成这些工作以后,你的xlrange对象将允许你在单元中放入值和公式了。通过在前面的记录集(从提交给数据库的sql命令中返回的)中循环,使用xlrange.item(nrow,ncol).value("whatever")语法,逐行逐列地把记录集中的值插入到电子表格中(语法中的whatever来自于记录集)。通过使用rs.getobject(i).tostring可以得到当前行中i列的值。通过使用rs.movenext(),当前行一直向后移动,直到记录集的末尾。
你在列表2中可能会注意到一个奇怪的函数调用:ncol = getexcelcolumn(i);
这是一个简单的辅助函数,它把记录集中的列编号与excel中存放数据的位置进行匹配。例如,如果你再次查看上面的sql语句,佣金费率将返回到第8列。在电子表格中,存放它的列是第13列。这个函数用于处理两者之间的转换。更复杂的应用程序可以使用excel中的命名(named)范围来匹配列,但是这超出了本文的范围。
最后,应用程序把公式输入适当的单元中。它是使用excel的r1c1符号来实现的,因此相应的字符串是用于描述公式的。在excel中,假设你希望把第1行中a列的值与b列的值相加,然后放到c列中,那么你就需要在c列中输入公式"=a1+b1"。如果在第二行你也希望这样操作,那么可以复制和粘贴这个公式,它会自动地更新为"=a2+b2"。
从示例程序中得到这些值需要一些字符串操作,但是如果使用"r1c1"相关的转换就非常直接了。在本文的例子中,你在c1中输入公式的时候,不需要给出"=a1+b1"引用,而是给出"=rc[-2]+rc[-1]"引用,它的意思是把向前两列的值与向前一列的值相加。
当你移动了其它行的时候,这个公式的值不会发生改变,因此它使我们生成程序所需要的值非常简单了。这也是我用于计算总销售额、销售税金、毛销售额和纯销售额的方法,如下所示:xlrange.item(nrow,7).value("=rc[-2]*rc[-1]");
xlrange.item(nrow,9).value("=rc[-2]*rc[-1]/100");
xlrange.item(nrow,10).value("=rc[-3]+rc[-1]");
xlrange.item(nrow,11).value("=rc[-4]*((100-rc[2])/100)");
运行这段代码将得到图3所示的电子表格。
图3:公式:excel显示了数据库查询的输出信息
请注意,这是一种比较好的工程学经验,即不要在应用程序中包含公式,因为公式可能随时改变,而你肯定不希望在改变公式的时候就去改变自己的代码。这种办法使每个新数据行成为包含公式的已有数据行的副本。模板电子表格(sales.xls)包含了假数据,并在第二列中包含了公式(第一列包含了列标题)。因此,当你填充每行的时候,在你填入数据库值的之前线复制这一行的内容。通过这种办法,公式通过复制进入了新数据行,并且你不需要在java代码中包含任何业务逻辑。
我们看一看salesreport2.java文件(也在下载内容中)中的内容,你可以发现,我们并没有使用前面所说的公式,而是在while循环顶部使用了下面的代码:if(nrow>1)
{
string strdest="a"+(nrow+1);
excelrange xlrange2copy = xlsheet.range("a2:m2");
xlrange2copy.copy(xlsheet.range(strdest));
}
除了第一行之外(第一行不需要处理,这样的信息已经包含在sales.xls中了),这段代码将处理a2到m2之间的单元,并把它们复制到nrow+1标识的数据行中(nrow是当前行的编号)。加上1的原因是计算了列标题(列标题在第一行)。
超越excel
请记住jcom是用于com的而不仅仅是用于excel的。你可以使用这种工具在java中控制几乎所有的com对象。excel是最常见的被控制对象,因为有很多很好的辅助类可以让它更简单,但是所有的com组件都是可以使用的。因此控制其它的应用程序(例如word和powerpoint)也是直接的。
下面是使用word的一个例子:idispatch wdapp = new idispatch(rm, "word.application");
wdapp.put("visible", new boolean(true));
由于没有用于word的直接的辅助类,每个部分都必须通过使用idispatch对象来实现(用于excel的下层辅助类也是这样实现的)。上面的代码将载入微软word副本,并通过wdapp对象使它可以自动化操作。接着你可以使用类似"put"(设置属性)或"method"(调用方法)等方法来使用word。例如,上面的代码把visible属性设置为true,使得该word应用程序可视。
如果你一定要使用windows,那么有大量的com组件可以扩充你的视野,它的数量比java目前提供的多很多。通过串行和并行口进行硬件控制也可以在java中实现了,而这仅仅是个开始。在你的工具包中有了jcom之后,就不受任何限制了。
闽公网安备 35060202000074号