最近学习了下Apache Commons项目下的一个子项目JXpath的使用,结合官方文档与网络上的一些入门教程小试用了一把。下面把学习内容整理了下,以便日后查看。
1 简介
JXPath定义了一个简单的XPath解释器,可用于定位各种对象——JavaBeans, Maps, Servlet contexts, DOM——以及它们的混合。
JXPath的核心类是JXPathContext,所以应用几乎总是会用到该类。
2 应用及示例
下面我们用一个示例来说明其应用,示例使用 Company --> Department --> Employee的结构来展示JXPath的应用。
Company |
Department |
Employee |
String name; List<Department> departments; |
String name; Set<Employee> employees; |
String name; int age; Map<String, String> contacts |
Company[“MS”]: Department[“Development”] Department[“Administrative”] |
Department[Development]: Employee[“Eric”] Employee[“Andy”] Department[Administrative]: Employee[“Lily”] Employee[“Lucy”] |
Employee[“Eric”]: age: 20 contacts: <home, “1234567”> <office, “7654321”> Employee[“Andy”]: age: 28 Employee[“Lily”] age: 22 Employee[“Lucy”] age: 25 |
我们共用一个JXPathContext,如下:
JXPathContext context = JXPathContext.newContext( company ); |
2.1 选取当前节点
Company c = (Company) context.getValue( "." ); |
由于我们使用的company创建的JXPathContext,所以,company是根节点,也是当前节点。使用“.”选择器就可以选取当前节点,即company。表达式 c == company 也会返回true。
2.2 访问属性
String companyName = (String) context.getValue( "name" ); |
当前节点company有name属性,所以可得company name—— MS。
2.3 谓语
Employee lily = (Employee) context.getValue( "/departments/employees[name='Lily']" ); |
谓语类似于SQL中的WHERE语句,有筛选作用,谓语被嵌在方括号中。以上代码可以选出所有部门所有员工中叫“Lily”的员工。
谓语中可以使用运算符。
context.getValue( "/departments/employees[name='Lily1' or age=22]" ); context.getValue( "/departments/employees[name='Lily' and age=22]" );
|
2.4 变量
context.getVariables().declareVariable( "name", "Eric" ); Employee eric = (Employee) context.getValue( "/departments/employees[name=$name]" ); |
查询中是可以使用变量的,只需要取得context关联的变量池,声明变量即可。
引用变量时,只需要在变量名前加前导符“$”即可。
声明的变量不必是字符串,可以是任意对象。
2.5 宽松模式
context.setLenient( true ); Employee clark = (Employee) context.getValue( "/departments/employees[name='clark']" ); |
如果指定的xpath不能映射到任何已存在的节点,那么将抛出异常。但是,这个限制在调用context.setLenient( true );后将被放宽,仅返回null,而不抛出异常。
2.6 访问Map
context.getValue( "/departments/employees[name='Eric']/contacts/home" ); context.getValue( "/departments/employees[name='Eric']/contacts[@name='office']" ); |
我们知道Employee类中有contacts属性,为Map类型。名为Eric的员工存储了两种联系方式:home和office。上面的代码分别用两种语法选取了两种联系方式。
JXPath只支持key为字符串的Map。
2.7 访问集合
2.7.1 迭代单个集合
for ( Iterator<?> iter = context.iterate( "/departments" ); iter.hasNext(); ) { Department dept = (Department) iter.next(); System.out.println(dept.getName()); } |
上面的代码会得到company下的departments的迭代器,即可遍历所有部门。
2.7.2 迭代多个集合
for ( Iterator<?> iter = context.iterate( "/departments/employees" ); iter.hasNext(); ) { Employee emp = (Employee) iter.next(); System.out.println(emp.getName()); } |
与迭代单个集合不同,上面的代码选取的节点不只是一个集合,它选取了所有部门下的所有员工集合。但是返回的迭代器可以一次性迭代遍历所有员工,尽管他们在不同部门的员工集合中。
2.7.3 下标
Employee emp2 = (Employee) context.getValue( "/departments/employees[2]" ); |
对于数组或集合形式的节点,我们可以使用下标的方式进行访问。
对Set这种无序的集合而言,它的元素顺序是其迭代顺序,由于迭代顺序是不可预测的,所以通常不会使用下标访问Set。
注:下标是从1开始,而不是0。
下标访问时,根据迭代多个集合的思维来预测结果会有一些偏差。
对上面代码而言,并没有把所有部门的员工合并,然后选取第2个员工。相反,程序将进行路径尝试。首先,尝试 /departments[1]/employees[2]能否选取节点,如果可以返回该节点;如果不能则尝试 /departments[2]/employees[2]能否选取节点……以此类推。所以,上面代码返回的结果应是查找到的第1个至少有2个员工的部门的第2个员工。
以上说明的是getValue()的行为,但是对于iterate()又有点不同。
for ( Iterator<?> iter = context.iterate( "/departments/employees[2]" ); iter.hasNext(); ) { Employee emp = (Employee) iter.next(); System.out.println(emp.getName()); } |
相同的是,iterate()也会进行路径尝试,但不同在于它会返回所有尝试通过的节点的集合的迭代器。对上面的代码而言,返回的迭代器对应的集合应该是所有至少拥有2个员工的部门下的第2个员工的集合。本例中,Development部门有Andy、Eric;Administrative部门有Lucy、Lily——员工按顺序列出的——那么迭代出的员工应是Eric和Lily。
【参考】
http://www.javaworld.com/article/2077700/data-storage/java-object-queries-using-jxpath.html