一、背景
xml文档节点比较多,结构层次复杂,而无需根据xml结构映射实体和取所有的节点内容(一两百个节点,只需取二十多个节点信息)。
二、实现思路
把xmlpath通用注释映射对应的字段。通过反射读取xmlPath,根据xmlPath读取xml文档对应节点内容后反射设值。
三、代码实现
1、缓存字段与xmlpath映射
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 缓存解析过的xmlField
*
*/
public class XmlFieldBeanFactory {
private final static ConcurrentMap<Class<?>, Map<Field,XmlField>> xmlTypeMap = new ConcurrentHashMap<Class<?>,Map<Field,XmlField>>();
public static Map<Field,XmlField> getForClass(Class<?> type){
Map<Field,XmlField> xmlType = xmlTypeMap.get(type);
if(xmlType == null) {
xmlType = getXmlType(type);
xmlTypeMap.put(type, xmlType);
}
return xmlType;
}
private static Map<Field,XmlField> getXmlType(Class<?> type){
Map<Field,XmlField> xmlType = new HashMap<>();
while (type != null) {//当父类为null的时候说明到达了最上层的父类(Object类).
for (Field field : type .getDeclaredFields()) {
if(field.isAnnotationPresent(XmlField.class)){//判断成员变量是否有注解
xmlType.put(field, field.getAnnotation(XmlField.class));
}
}
type = type.getSuperclass(); //得到父类,然后赋给自己
}
return xmlType;
}
}
2、根据xmlPath获取body值或属性值
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
/**
* xml path 映射
*
*/
public class XmlPathUtil {
/**
* xml字符串转换Object
* @param xml
* @param clz 转换对象class
* @return
* @throws Exception
*/
public static <T> T convert(String xml,Class<T> clz) throws Exception{
InputStream in = null;
try {
in = IOUtils.toInputStream(xml);
return convert(in,clz);
}finally {
IOUtils.closeQuietly(in);
}
}
/**
* xml字符串转换Object
* @param xml xml字符串
* @param clz 转换对象class
* @param ns 命名空间
* @param alisa 别名
* @return
* @throws Exception
*/
public static <T> T convert(String xml,Class<T> clz,String ns,String alisa) throws Exception{
InputStream in = null;
try {
in = IOUtils.toInputStream(xml);
return convert(in,clz,ns,alisa);
}finally {
IOUtils.closeQuietly(in);
}
}
/**
* inputStream xml转Object
* @param in
* @param clz
* @return
* @throws Exception
*/
public static <T> T convert(InputStream in,Class<T> clz) throws Exception {
Document doc = new SAXReader().read(in);
return convert(doc,clz);
}
/**
* inputStream xml转Object
* @param in
* @param clz
* @param ns 命名空间
* @param alisa 别名
* @return
* @throws Exception
*/
public static <T> T convert(InputStream in,Class<T> clz,String ns,String alisa) throws Exception {
SAXReader saxReader = new SAXReader();
Document doc = saxReader.read(in);
Map<String,String> map = new HashMap<>();
map.put(alisa,ns);
saxReader.getDocumentFactory().setXPathNamespaceURIs(map);
return convert(doc,clz);
}
/**
* document 转 Object
* @param doc
* @param clz
* @return
* @throws Exception
*/
public static <T> T convert(Document doc,Class<T> clz) throws Exception {
T t = clz.newInstance();
getValue(doc, t, clz,"");
return t;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void getValue(Document doc,Object obj,Class clz,String parentPath) throws Exception{
Map<Field,XmlField> xmlType = XmlFieldBeanFactory.getForClass(clz);
for(Field field : xmlType.keySet()) {
XmlField xmlField = xmlType.get(field);
Object value = field.getType().newInstance();
BeanUtils.setProperty(obj, field.getName(), value);
if(!xmlField.haveSon()) {
if(isCollection(field.getType())) {
Collection cl = (Collection) value;
List<Node> nodes = doc.selectNodes(parentPath+xmlField.path());
if(CollectionUtils.isNotEmpty(nodes)) {
for (Node node : nodes) {
cl.add(node.getText());
}
}
}else {
if(xmlField.isText()) {
Node node = doc.selectSingleNode(parentPath+xmlField.path());
if(node!=null) {
BeanUtils.setProperty(obj,field.getName() , node.getText());
}
}else {
BeanUtils.setProperty(obj,field.getName() , doc.valueOf(parentPath+xmlField.path()));
}
}
}else {
if(isCollection(field.getType())){
Collection cl = (Collection) value;
List<Node> nodes = doc.selectNodes(parentPath+xmlField.path());
if(CollectionUtils.isNotEmpty(nodes)) {
for (Node node : nodes) {
Type genericType = field.getGenericType();
if(genericType == null)
return; // 如果是泛型参数的类型
if(genericType instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType) genericType;//得到泛型里的class类型对象
Class<?> accountPrincipalApproveClazz = (Class<?>)pt.getActualTypeArguments()[0];
Object v = accountPrincipalApproveClazz.newInstance();
cl.add(v);
getValue(node.getDocument(), v,accountPrincipalApproveClazz,node.getUniquePath());
}
}
}
}else {
Node node = doc.selectSingleNode(xmlField.path());
if(node != null) {
Object v = field.getType().newInstance();
getValue(doc, v,field.getType(),node.getUniquePath());
}
}
}
}
}
private static boolean isCollection(Class<?> type) {
return Collection.class.isAssignableFrom(type);
}
}
3、xmlField注解
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* 用于注解字段对应的xpath,进行解析xml
*
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface XmlField {
/**
* xml对应的xPath路径
* @return
*/
String path();
/**
* 是否嵌套对象
* @return
*/
boolean haveSon() default false;
/**
* 取属性值
* @return
*/
boolean isText() default true;
}
4、Test
实体类
public class Grade {
@XmlField(path = "/grade/student",haveSon=true)
private ArrayList<Student> students;
@XmlField(path = "/grade/gradeCode/text()",isText=false)
private String gradeCode;
@XmlField(path = "/grade/gradeName")
private String gradeName;
//省略get set方法
}
public class Student {
@XmlField(path="/id")
private String id;
@XmlField(path="/name")
private String name;
//默认是直接获得text(),如果isText是false时,直接读取path的内容如@arr获取属性,text()获得文本内容
@XmlField(path="/@aliso",isText=false)
private String aliso;
//省略get set方法
}
xml 文档内容
<grade>
<gradeCode>SD</gradeCode>
<gradeName>grade2</gradeName>
<student aliso="aa">
<id>1</id>
<name>zhangsan</name>
</student>
<student aliso="bb">
<id>2</id>
<name>lisi</name>
</student>
</grade>
测试
public class Test {
public static void main(String[] args) throws Exception {
String xml = "<grade>\r\n" +
" <gradeCode>SD</gradeCode>\r\n" +
" <gradeName>grade2</gradeName>\r\n" +
" <student aliso=\"aa\">\r\n" +
" <id>1</id>\r\n" +
" <name>zhangsan</name>\r\n" +
" </student>\r\n" +
" <student aliso=\"bb\">\r\n" +
" <id>2</id>\r\n" +
" <name>lisi</name>\r\n" +
" </student>\r\n" +
"</grade>";
Grade grade = XmlPathUtil.convert(xml, Grade.class);
System.out.println(grade);
}
}