(文章转自CSDN)
3. 使用XPath语法来查询对象和集合
Commons JXPath是一种让人很吃惊地(非标准的)对XML标准的使用。XPath一段时间以来一直是作为在一个XSL样式表中选择结点或结点集的一种方法。如果你用过XML,你会很熟悉用这样的语法/foo/bar来从foo文档元素中选择bar子元素。
Jakarta Commons JXPath增加了一种有趣的手法:你可以用JXPath来从bean和集合中选择对象,其中如servlet上下文和DOM文档对象。考虑一个包含了Person对象的列表。每一个Person对象有一个属性的类型为Job,每一个Job对象有一个salary(薪水)属性,类型为int。Person对象也有一个coountry属性,它是两个字符的国家代码。使用JXPath,你可以很容易地选出所有国家为美国,薪水超过一百万美元的Person对象。下面是设置一个由JXPath过滤地bean的List的代码:
// Person的构造器设置姓和国家代码
Person person1 = new Person( "Tim", "US" );
Person person2 = new Person( "John", "US" );
Person person3 = new Person( "Al", "US" );
Person person4 = new Person( "Tony", "GB" );
// Job的构造器设工作名称和薪水
person1.setJob( new Job( "Developer", 40000 ) );
person2.setJob( new Job( "Senator", 150000 ) );
person3.setJob( new Job( "Comedian", 3400302 ) );
person4.setJob( new Job( "Minister", 2000000 ) );
Person[] personArr =
new Person[] { person1, person2,
person3, person4 };
List people = Arrays.asList( personArr );
people List包含了四个bean: Tim, John, Al, 和George。Tim是一个挣4万美元的开发者,John是一个挣15万美元的参议员,Al是一个挣340万美元的喜剧演员,Tony是一个挣200万欧元的部长。我们的任务很简单:遍历这个List,打印出每一个挣钱超过100百万美元的美国公民的名字。记住people是一个由Person对象构成的ArrayList,让我们先看一下没有利用JXPath便利的解决方案:
Iterator peopleIter = people.getIterator();
while( peopleIter.hasNext() ) {
Person person = (Person) peopleIter.next();
if( person.getCountry() != null &&
person.getCountry().equals( "US" ) &&
person.getJob() != null &&
person.getJob().getSalary() > 1000000 ) {
print( person.getFirstName() + " "
person.getLastName() );
}
}
}
}
上面的例子是繁重的,并有些容易犯错。为了发现合适的Person对象,你必须首先遍历每一个Person对象并且检查conuntry的属性。如果country属性不为空并且符合要求,那么你就要检查job属性并看一下它是否不为空并且salary属性的值大于100万。上面的例子的代码行数可以被Java 1.5的语法大大减少,但是,哪怕是Java 1.5,你仍旧需要在两层上作两次比较。
如果你想对内存中的一组Person对象也做一些这样的查询呢?如果你的应用想显示所有在英格兰的名叫Tony的人呢?喔,如果你打印出每一个薪水少于2万的工作的名称呢?
如果你将这些对象存储到关系数据库中,你可以用一个SQL查询来解决问题,但你正在处理的是内存中的对象,你可以不必那么奢侈。虽然XPath主要是用在XML上面,但你可以用它来写一个针对对象集合的“查询”,将对象作为元素和,把bean属性作为子元素。是的,这是一种对XPath奇怪的应用,但请先看一下下面的例子如何在people上,一个由Person对象构成的ArrayList,实现这三种查询:
import org.apache.commons.jxpath.JXPathContext;
public List queryCollection(String xpath,
Collection col) {
List results = new ArrayList();
JXPathContext context =
JXPathContext.newContext( col );
Iterator matching =
context.iterate( xpath );
while( matching.hasNext() ) {
results.add( matching.getNext() );
}
return results;
}
String query1 =
".[@country = 'US']/job[@salary > 1000000]/..";
String query2 =
".[@country = 'GB' and @name = 'Tony']";
String query3 =
"./job/name";
List richUsPeople =
queryCollection( query1, people );
List britishTony =
queryCollection( query2, people );
List jobNames =
queryCollection( query3, people );
queryCollection()方法使用了一个XPath表达式,将它应用到一个集合上。XPath表达式被JXPathContext求值, JXPathContext由JXPathContext.newContext()调用创建,并将它传入要执行查询的集合中。凋用context.iterate()来在集合中的每一个元素上应用XPath表达式,返回包含所有符合条件的“节点”(这里是“对象”)的Iterator。上例中执行的第一个查询,query1,执行了和不使用JXPath的例子相同的查询。query2选择所有国家为GB并且名字属性为Tony的Person对象,query3返回了一个String对象的List,包含了所有Job对象的name属性。
当我第一次看到Commons JXPath, 它是一个坏思想的想法触动了我。为什么要把XPath表达式应用到对象上?有点感觉不对。把XPath作为一个bean的集合的查询语言的这种意想不到的用法,在过去几年中已经好多次给我带来了便利。如果你发现你在list中循环来查找符合条件的元素,请考虑一下JXPath。更多的信息,请参考Jakarta Commons Cookbook的第12章,“查找和过滤”,它讨论了Commons JXPath和与Commons Digester配对的Jakarta Lucene。
还有更多
对Jakarta Commons纵深地探索仍然在调试中。在这一系列的下面几部分中,我会介绍一些相关的工具和功能。在Commons Collections中设置操作,在collection中使用Predicate对象,使用Commons Configuration来配置一个应用和使用Commons Betwixt来读写XML。能从Jakarta Commons得到的东西还有很多,不能在几千字中表达,所以我建议你看一下Jakarta Commons Cookbook。许多功能可能会,一眼看上去,有点普通,但Jakarta Commons的能量就蕴藏在这些工具的相互组合和与你的系统的集成当中。
Timothy M. O'Brien是一个专业的独立的开发者,在Chicago地区工作和生活。
资源
·onjava.com:onjava.com
·Matrix-Java开发者社区:http://www.matrix.org.cn/
·APACHE:APACHE.org