Apache Digester XML 解析器注释和复合模型
Apache Digester XML parser annotations and composite model
我有以下 XML 文档,我将使用 Apache Digester 解析器(通过 Digester 注释)将其解析为对象模型:
<?xml version="1.0" encoding="UTF-8"?>
<Decision>
<Name>Antivirus software for Windows</Name>
<Description>Description 1</Description>
<Url>http://yahoo.com</Url>
<ImageUrl>http://yahoo.com/img.jpg</ImageUrl>
<CriterionGroups>
<CriterionGroup>
<Name>Windows</Name>
<Description>Description 1</Description>
<Criteria>
<Criterion>
<Name>Heuristics</Name>
<Description>Description 1</Description>
</Criterion>
</Criteria>
</CriterionGroup>
</CriterionGroups>
<Criteria>
<Criterion>
<Name>On-demand scan</Name>
<Description>Description 1</Description>
</Criterion>
</Criteria>
<CharacteristicGroups>
<CharacteristicGroup>
<Name>Windows</Name>
<Description>Description 1</Description>
<Characteristics>
<Characteristic>
<Name>Country of origin</Name>
<Description>Description 1</Description>
<ValueType>String</ValueType>
<VisualMode>SelectBox</VisualMode>
<Sortable>true</Sortable>
<Options>
<Option>
<Value>Shareware</Value>
<Description>Description 1</Description>
</Option>
</Options>
</Characteristic>
</Characteristics>
</CharacteristicGroup>
</CharacteristicGroups>
<Characteristics>
<Characteristic>
<Name>License</Name>
<Description>Description 1</Description>
<ValueType>Integer</ValueType>
<VisualMode>Slider</VisualMode>
<Sortable>false</Sortable>
</Characteristic>
</Characteristics>
<Decisions>
<Decision>
<Name>Avast Free Antivirus</Name>
<Description>Description 1</Description>
<Url>http://google.com</Url>
<ImageUrl>http://google.com/img.jpg</ImageUrl>
<Votes>
<Vote>
<CriterionName>On-demand scan</CriterionName>
<Weight>4.3</Weight>
</Vote>
<Vote>
<CriterionName>Heuristics</CriterionName>
<CriterionName>On-demand scan</CriterionName>
<Weight>4.3</Weight>
<Description>Description 1</Description>
</Vote>
</Votes>
<Values>
<Value>
<CharacteristicName>License</CharacteristicName>
<Value>Proprietary</Value>
<Description>Description 1</Description>
</Value>
</Values>
</Decision>
</Decisions>
</Decision>
从这个XML可以看出,有两个 Criterion
节点通过两条不同的路径:
- Decision/Criteria/Criterion
- Decision/CriterionGroups/CriterionGroup/Criteria/Criterion
这是我的对象模型:
@ObjectCreate(pattern = "Decision")
public class DecisionNode {
@BeanPropertySetter(pattern = "Decision/Name")
private String name;
@BeanPropertySetter(pattern = "Decision/Description")
private String description;
@BeanPropertySetter(pattern = "Decision/Url")
private String url;
@BeanPropertySetter(pattern = "Decision/ImageUrl")
private String imageUrl;
private List<CriterionGroupNode> criterionGroupNodes = new ArrayList<>();
private List<CriterionNode> criterionNodes = new ArrayList<>();
private List<CharacteristicGroupNode> characteristicGroupNodes = new ArrayList<>();
private List<CharacteristicNode> characteristicNodes = new ArrayList<>();
private List<DecisionNode> decisionNodes = new ArrayList<>();
private List<VoteNode> voteNodes = new ArrayList<>();
private List<ValueNode> valueNodes = new ArrayList<>();
....
@SetNext
public boolean addCriterionGroupNode(CriterionGroupNode criterionGroupNode) {
return criterionGroupNodes.add(criterionGroupNode);
}
....
}
@ObjectCreate(pattern = "Decision/CriterionGroups/CriterionGroup")
public class CriterionGroupNode {
@BeanPropertySetter(pattern = "Decision/CriterionGroups/CriterionGroup/Name")
private String name;
@BeanPropertySetter(pattern = "Decision/CriterionGroups/CriterionGroup/Description")
private String description;
private List<CriterionNode> criterionNodes = new ArrayList<>();
....
@SetNext
public boolean addCriterionNode(CriterionNode criterionNode) {
return criterionNodes.add(criterionNode);
}
....
}
@ObjectCreate(pattern = "Decision/Criteria/Criterion")
public class CriterionNode {
@BeanPropertySetter(pattern = "Decision/Criteria/Criterion/Name")
private String name;
@BeanPropertySetter(pattern = "Decision/Criteria/Criterion/Description")
private String description;
public CriterionNode() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
现在我只能解析 Decision/Criteria/Criterion
,但 Decision/CriterionGroups/CriterionGroup/Criteria/Criterion
仍然是 NULL
。如何配置我的模型并更改注释以便能够用两个不同的位置解析 CriterionNode
?
此外,我不明白为什么解析器通过 Decision/Criteria/Criterion
:
找到两个 Criterion
节点而不是一个节点
我能看到两个问题:
首先,您发布的代码仅匹配作为决策的直接子项的标准。也就是说,您匹配了 "Decision/Criteria/Criterion",但没有匹配 "Decision/CriterionGroups/CriterionGroup/Criteria/Criterion",因此永远不会创建更深层次的元素。最简单的解决方案就是使用通配符:
@ObjectCreate(pattern = "*/Criteria/Criterion")
public static class CriterionNode {
@BeanPropertySetter(pattern = "*/Criteria/Criterion/Name")
private String name;
@BeanPropertySetter(pattern = "*/Criteria/Criterion/Description")
private String description;
第二个问题是 CriterionNode
的 SetNext
规则,这个有点棘手。切入正题,我认为这段代码应该适合你:
@ObjectCreate(pattern = "Decision")
public class DecisionNode {
...
@SetNext
public boolean addCriterionNode(CriterionNode criterionNode) {
return criterionNodes.add(criterionNode);
}
}
@ObjectCreate(pattern = "Decision/CriterionGroups/CriterionGroup")
public class CriterionGroupNode {
...
// no SetNext rule on this method
public boolean addCriterionNode(CriterionNode criterionNode) {
return criterionNodes.add(criterionNode);
}
}
这样做的原因是注释构建下一个规则集的方式。
一个set next rule需要三样东西:
- 一个模式。
- 方法名称。
- 一个参数类型。
所以这个注解试图达到的效果相当于:
digester.addSetNext("*/Criteria/Criterion", "addCriterionNode", "CriterionNode")
请注意,此规则中的任何地方都没有提及所有者 DecisionNode
和 CriterionGroupNode
。
方法名称和参数类型很简单 - 它们直接来自带注释的方法 - 但模式不太清楚。注释处理会查看与参数匹配的注释来推断模式,因此在这种情况下,参数是 CriterionNode
,并且具有与“*/Criteria/Criterion”匹配的 ObjectCreate
注释,所以它创建了所需的规则。
在 CriterionGroupNode
class 中不需要第二个 SetNextRule
的原因是它会复制完全相同的处理,因此会添加重复的规则。
关于 Digester 注释的注释
我将在此添加关于 Digester 注释的标准免责声明:我个人不喜欢它们并且从不使用它们,原因有二:
- 一个更笼统:我认为注解应该只用在什么地方
他们说的是 class 本身,而不是它的使用方式,但这只是我个人对过度使用注释的看法。
- 特别是 Digester 注释:它们只能满足极其简单的情况,甚至像这样简单的事情也会导致上述问题。
我发现基于规则的配置对于快速映射来说最简单,如果需要扩展它也是最强大的。在这种情况下,类似于:
RulesModule rules = new AbstractRulesModule() {
@Override
public void configure() {
forPattern("Decision")
.createObject().ofType(DecisionNode.class);
forPattern("Decision/Name").addRule(new BeanPropertySetterRule("name"));
forPattern("Decision/Description").addRule(new BeanPropertySetterRule("description"));
forPattern("Decision/Url").addRule(new BeanPropertySetterRule("url"));
forPattern("Decision/ImageUrl").addRule(new BeanPropertySetterRule("imageUrl"));
forPattern("Decision/CriterionGroups/CriterionGroup")
.createObject().ofType(CriterionGroupNode.class)
.then().setNext("addCriterionGroupNode");
forPattern("Decision/CriterionGroups/CriterionGroup/Name").addRule(new BeanPropertySetterRule("name"));
forPattern("Decision/CriterionGroups/CriterionGroup/Description").addRule(new BeanPropertySetterRule("description"));
forPattern("*/Criterion")
.createObject().ofType(CriterionNode.class)
.then().setNext("addCriterionNode");
forPattern("*/Criterion/Name").addRule(new BeanPropertySetterRule("name"));
forPattern("*/Criterion/Description").addRule(new BeanPropertySetterRule("description"));
}
};
DigesterLoader loader = DigesterLoader.newLoader(rules);
Digester digester = loader.newDigester();
DecisionNode dn = digester.parse(...);
请注意,扩展版本的 BeanPropertySetterRule 是必需的,因为您的 XML 实体不遵循 Java Bean 约定(属性 必须是 lower-camel - 所以 getName 和setName 定义 属性 "name" 而不是 "Name")。因此,如果您的 XML 使用小写实体,例如 "name" 和 "description",那么您可以使用更短的:
forPattern("*/Criterion/Name").setBeanProperty();
forPattern("*/Criterion/Description").setBeanProperty();
绝对没有理由让您的 XML 遵循 Java Bean 约定 - 如果您想知道为什么需要扩展版本,我只是指出这一点。
干杯
我有以下 XML 文档,我将使用 Apache Digester 解析器(通过 Digester 注释)将其解析为对象模型:
<?xml version="1.0" encoding="UTF-8"?>
<Decision>
<Name>Antivirus software for Windows</Name>
<Description>Description 1</Description>
<Url>http://yahoo.com</Url>
<ImageUrl>http://yahoo.com/img.jpg</ImageUrl>
<CriterionGroups>
<CriterionGroup>
<Name>Windows</Name>
<Description>Description 1</Description>
<Criteria>
<Criterion>
<Name>Heuristics</Name>
<Description>Description 1</Description>
</Criterion>
</Criteria>
</CriterionGroup>
</CriterionGroups>
<Criteria>
<Criterion>
<Name>On-demand scan</Name>
<Description>Description 1</Description>
</Criterion>
</Criteria>
<CharacteristicGroups>
<CharacteristicGroup>
<Name>Windows</Name>
<Description>Description 1</Description>
<Characteristics>
<Characteristic>
<Name>Country of origin</Name>
<Description>Description 1</Description>
<ValueType>String</ValueType>
<VisualMode>SelectBox</VisualMode>
<Sortable>true</Sortable>
<Options>
<Option>
<Value>Shareware</Value>
<Description>Description 1</Description>
</Option>
</Options>
</Characteristic>
</Characteristics>
</CharacteristicGroup>
</CharacteristicGroups>
<Characteristics>
<Characteristic>
<Name>License</Name>
<Description>Description 1</Description>
<ValueType>Integer</ValueType>
<VisualMode>Slider</VisualMode>
<Sortable>false</Sortable>
</Characteristic>
</Characteristics>
<Decisions>
<Decision>
<Name>Avast Free Antivirus</Name>
<Description>Description 1</Description>
<Url>http://google.com</Url>
<ImageUrl>http://google.com/img.jpg</ImageUrl>
<Votes>
<Vote>
<CriterionName>On-demand scan</CriterionName>
<Weight>4.3</Weight>
</Vote>
<Vote>
<CriterionName>Heuristics</CriterionName>
<CriterionName>On-demand scan</CriterionName>
<Weight>4.3</Weight>
<Description>Description 1</Description>
</Vote>
</Votes>
<Values>
<Value>
<CharacteristicName>License</CharacteristicName>
<Value>Proprietary</Value>
<Description>Description 1</Description>
</Value>
</Values>
</Decision>
</Decisions>
</Decision>
从这个XML可以看出,有两个 Criterion
节点通过两条不同的路径:
- Decision/Criteria/Criterion
- Decision/CriterionGroups/CriterionGroup/Criteria/Criterion
这是我的对象模型:
@ObjectCreate(pattern = "Decision")
public class DecisionNode {
@BeanPropertySetter(pattern = "Decision/Name")
private String name;
@BeanPropertySetter(pattern = "Decision/Description")
private String description;
@BeanPropertySetter(pattern = "Decision/Url")
private String url;
@BeanPropertySetter(pattern = "Decision/ImageUrl")
private String imageUrl;
private List<CriterionGroupNode> criterionGroupNodes = new ArrayList<>();
private List<CriterionNode> criterionNodes = new ArrayList<>();
private List<CharacteristicGroupNode> characteristicGroupNodes = new ArrayList<>();
private List<CharacteristicNode> characteristicNodes = new ArrayList<>();
private List<DecisionNode> decisionNodes = new ArrayList<>();
private List<VoteNode> voteNodes = new ArrayList<>();
private List<ValueNode> valueNodes = new ArrayList<>();
....
@SetNext
public boolean addCriterionGroupNode(CriterionGroupNode criterionGroupNode) {
return criterionGroupNodes.add(criterionGroupNode);
}
....
}
@ObjectCreate(pattern = "Decision/CriterionGroups/CriterionGroup")
public class CriterionGroupNode {
@BeanPropertySetter(pattern = "Decision/CriterionGroups/CriterionGroup/Name")
private String name;
@BeanPropertySetter(pattern = "Decision/CriterionGroups/CriterionGroup/Description")
private String description;
private List<CriterionNode> criterionNodes = new ArrayList<>();
....
@SetNext
public boolean addCriterionNode(CriterionNode criterionNode) {
return criterionNodes.add(criterionNode);
}
....
}
@ObjectCreate(pattern = "Decision/Criteria/Criterion")
public class CriterionNode {
@BeanPropertySetter(pattern = "Decision/Criteria/Criterion/Name")
private String name;
@BeanPropertySetter(pattern = "Decision/Criteria/Criterion/Description")
private String description;
public CriterionNode() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
现在我只能解析 Decision/Criteria/Criterion
,但 Decision/CriterionGroups/CriterionGroup/Criteria/Criterion
仍然是 NULL
。如何配置我的模型并更改注释以便能够用两个不同的位置解析 CriterionNode
?
此外,我不明白为什么解析器通过 Decision/Criteria/Criterion
:
Criterion
节点而不是一个节点
我能看到两个问题:
首先,您发布的代码仅匹配作为决策的直接子项的标准。也就是说,您匹配了 "Decision/Criteria/Criterion",但没有匹配 "Decision/CriterionGroups/CriterionGroup/Criteria/Criterion",因此永远不会创建更深层次的元素。最简单的解决方案就是使用通配符:
@ObjectCreate(pattern = "*/Criteria/Criterion")
public static class CriterionNode {
@BeanPropertySetter(pattern = "*/Criteria/Criterion/Name")
private String name;
@BeanPropertySetter(pattern = "*/Criteria/Criterion/Description")
private String description;
第二个问题是 CriterionNode
的 SetNext
规则,这个有点棘手。切入正题,我认为这段代码应该适合你:
@ObjectCreate(pattern = "Decision")
public class DecisionNode {
...
@SetNext
public boolean addCriterionNode(CriterionNode criterionNode) {
return criterionNodes.add(criterionNode);
}
}
@ObjectCreate(pattern = "Decision/CriterionGroups/CriterionGroup")
public class CriterionGroupNode {
...
// no SetNext rule on this method
public boolean addCriterionNode(CriterionNode criterionNode) {
return criterionNodes.add(criterionNode);
}
}
这样做的原因是注释构建下一个规则集的方式。
一个set next rule需要三样东西:
- 一个模式。
- 方法名称。
- 一个参数类型。
所以这个注解试图达到的效果相当于:
digester.addSetNext("*/Criteria/Criterion", "addCriterionNode", "CriterionNode")
请注意,此规则中的任何地方都没有提及所有者 DecisionNode
和 CriterionGroupNode
。
方法名称和参数类型很简单 - 它们直接来自带注释的方法 - 但模式不太清楚。注释处理会查看与参数匹配的注释来推断模式,因此在这种情况下,参数是 CriterionNode
,并且具有与“*/Criteria/Criterion”匹配的 ObjectCreate
注释,所以它创建了所需的规则。
在 CriterionGroupNode
class 中不需要第二个 SetNextRule
的原因是它会复制完全相同的处理,因此会添加重复的规则。
关于 Digester 注释的注释
我将在此添加关于 Digester 注释的标准免责声明:我个人不喜欢它们并且从不使用它们,原因有二:
- 一个更笼统:我认为注解应该只用在什么地方 他们说的是 class 本身,而不是它的使用方式,但这只是我个人对过度使用注释的看法。
- 特别是 Digester 注释:它们只能满足极其简单的情况,甚至像这样简单的事情也会导致上述问题。
我发现基于规则的配置对于快速映射来说最简单,如果需要扩展它也是最强大的。在这种情况下,类似于:
RulesModule rules = new AbstractRulesModule() {
@Override
public void configure() {
forPattern("Decision")
.createObject().ofType(DecisionNode.class);
forPattern("Decision/Name").addRule(new BeanPropertySetterRule("name"));
forPattern("Decision/Description").addRule(new BeanPropertySetterRule("description"));
forPattern("Decision/Url").addRule(new BeanPropertySetterRule("url"));
forPattern("Decision/ImageUrl").addRule(new BeanPropertySetterRule("imageUrl"));
forPattern("Decision/CriterionGroups/CriterionGroup")
.createObject().ofType(CriterionGroupNode.class)
.then().setNext("addCriterionGroupNode");
forPattern("Decision/CriterionGroups/CriterionGroup/Name").addRule(new BeanPropertySetterRule("name"));
forPattern("Decision/CriterionGroups/CriterionGroup/Description").addRule(new BeanPropertySetterRule("description"));
forPattern("*/Criterion")
.createObject().ofType(CriterionNode.class)
.then().setNext("addCriterionNode");
forPattern("*/Criterion/Name").addRule(new BeanPropertySetterRule("name"));
forPattern("*/Criterion/Description").addRule(new BeanPropertySetterRule("description"));
}
};
DigesterLoader loader = DigesterLoader.newLoader(rules);
Digester digester = loader.newDigester();
DecisionNode dn = digester.parse(...);
请注意,扩展版本的 BeanPropertySetterRule 是必需的,因为您的 XML 实体不遵循 Java Bean 约定(属性 必须是 lower-camel - 所以 getName 和setName 定义 属性 "name" 而不是 "Name")。因此,如果您的 XML 使用小写实体,例如 "name" 和 "description",那么您可以使用更短的:
forPattern("*/Criterion/Name").setBeanProperty();
forPattern("*/Criterion/Description").setBeanProperty();
绝对没有理由让您的 XML 遵循 Java Bean 约定 - 如果您想知道为什么需要扩展版本,我只是指出这一点。
干杯