不久前我找到了一个解决方案,并没有真正的时间和精力来发布它,但我们开始吧:
注意:这个解决方案不是每个人都梦寐以求的完美的两行解决方案,但它可以按我的意愿工作。NB2:此解决方案需要XOM 库才能读取 XML 文件。
首先,它是如何工作的:我创建了一组 XML 文件,每个文件代表一个将由 jackson 序列化(或需要自定义序列化)的实体。
这是此类文件的示例 - Assignment.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<Assignment>
<endDate/>
<id/>
<missionType>
<id/>
<name/>
</missionType>
<numberOfDaysPerWeek/>
<project>
<id/>
<name/>
</project>
<resource>
<id/>
<firstName/>
<lastName/>
<fte/>
</resource>
<role>
<id/>
<name/>
</role>
<startDate/>
<workLocation/>
</Assignment>
在这里,我们使用 XML 元素表示的每个属性来表示 Assignment 类。请注意,任何未表示的元素都不会使用我稍后将展示的转换器进行序列化。
具有子元素的元素是由 Assignment 实例引用的对象。这些子元素将与其余元素一起序列化。
例如,一个 Assignment 实例有一个名为“role”的属性,它是Role
我们想要role
的类型id
和name
序列化的。
这主要是您如何选择要序列化的内容和不序列化的内容,从而限制序列化的深度。
现在ObjectConverter
上课:
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.ParsingException;
import nu.xom.ValidityException;
import model.ModelObject;
/**
* This helper class will convert DOM objects (those implementing ModelObject) into a data structure built on the fly.
* Typically, a simple object will be converted into a Map<String, Object> where keys will be the object's field names and values be corresponding field values.
* The convertion uses an XML configuration file that is located in webservices/jackson/converters.
*
* @author mdekeys
*
*/
public class ObjectConverter {
private static Logger logger = Logger.getLogger(ObjectConverter.class);
private static final String CONFIGURATION_DIR = "../standalone/deployments/resources-management.war/WEB-INF/classes/com/steria/rm/webservices/jackson/converters/";
/**
*
* @param obj The object to convert
* @param element An XML element (based on XOM library) which represents the object structure.
* @return Returns the object converted in a corresponding data structure
*/
@SuppressWarnings("unchecked")
private static Object serialize(ModelObject obj, Element element) {
//initialize return value
Map<String, Object> map = new HashMap<String, Object>();
//find all child elements
Elements children = element.getChildElements();
//loop through children elements
for (int i = 0; i < children.size(); i++) {
//get the current child
Element child = children.get(i);
//child's qualifiedName shoud be the name of an attribute
String fieldName = child.getQualifiedName();
//find get method for this attribute
Method getMethod = null;
try {
getMethod = obj.getConvertedClass().getMethod("get" + firstLetterToUpperCase(fieldName));
} catch (NoSuchMethodException e) {
logger.error("Cannot find getter for "+fieldName, e);
return null;
} catch (SecurityException e) {
logger.error("Cannot access getter for "+fieldName, e);
return null;
}
//invoke get method
Object value = null;
try {
value = getMethod.invoke(obj, (Object[]) null);
} catch (IllegalAccessException e) {
logger.error("Cannot invoke getter for "+fieldName, e);
return null;
} catch (IllegalArgumentException e) {
logger.error("Bad arguments passed to getter for "+fieldName, e);
return null;
} catch (InvocationTargetException e) {
logger.error("Cannot invoke getter for "+fieldName, e);
return null;
}
//if value is null, return null
if (value == null || (value instanceof List && ((List<?>) value).size() == 0)) {
map.put(fieldName, null);
} else if (value instanceof List<?>) { //if value is a list, recursive call
map.put(fieldName, serializeList((List<ModelObject>) value, child));
} else if (value instanceof ModelObject) { //if value is another convertable object, recursive call
map.put(fieldName, serialize((ModelObject) value, child));
} else { //simple value, put it in
map.put(fieldName, value);
}
}
return map;
}
/**
* Intermediary method that is called from outside of this class.
* @param list List of objects to be converted.
* @param confFileName Name of the configuration file to be used.
* @return The list of converted objects
*/
public static List<Object> serializeList(List<ModelObject> list, String confFileName) {
return serializeList(list, findRootElement(confFileName));
}
/**
* Method that is called inside this class with an XML element (based on XOM library)
* @param list List of objects to be converted.
* @param element XML element (XOM) representing the object's structure
* @return List of converted objects.
*/
public static List<Object> serializeList(List<ModelObject> list, Element element) {
ArrayList<Object> res = new ArrayList<Object>();
for (ModelObject obj : list) {
res.add(serialize(obj, element));
}
return res;
}
/**
* Method that is called from outside of this class.
* @param object Object to be converted.
* @param confFileName Name of the XML file to use for the convertion.
* @return Converted object.
*/
public static Object serialize(ModelObject object, String confFileName) {
return serialize(object, findRootElement(confFileName));
}
/**
* Helper method that is used to set the first letter of a String to upper case.
* @param str The string to be modified.
* @return Returns the new String with its first letter in upper case.
*/
private static String firstLetterToUpperCase(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
/**
* Helper method that is taking an XML configuration file name and returns its the root element (based on XOM library).
* @param confFileName Name of the XML configuration file
* @return Returns the root element (XOM based)
*/
private static Element findRootElement(String confFileName) {
Builder parser = new Builder();
Document doc = null;
String confFile = confFileName + ".xml";
try {
doc = parser.build(CONFIGURATION_DIR + confFile);
} catch (ValidityException e) {
doc = e.getDocument();
logger.warn("XML configuration file for "+confFileName+" isn't valid", e);
} catch (ParsingException e) {
logger.error("XML configuration file for "+confFileName+" isn't parseable", e);
} catch (IOException e) {
logger.error("IOException on XML configuration file for "+confFileName, e);
}
return doc.getRootElement();
}
}
如您所见,serialize、serializeList 和 serializeMap 方法需要一个ModelObject
参数,该参数是所有可序列化对象都必须实现的接口(在下面提供)。
如果您已经有一个接口,用于在一个给定类型下将所有域对象重新组合在一起,那么也可以使用此接口(您只需要添加一种方法,见下文)。
接口模型对象:
/**
* Interface that identifies an object as a DOM object and is used by class {@ObjectConverter} to retrieve the class of the object to convert.
* @author mdekeys
*
*/
public interface ModelObject {
/**
* This method returns the implementer's class
* @return The implementer Class
*/
Class<?> getConvertedClass();
}
您可以按如下方式使用此 ObjectConverter:
@Override
@RequestMapping(value = "/populatedLists", method = RequestMethod.GET)
public @ResponseBody Map<String, Object> populateLists() {
Map<String, Object> map = new HashMap<String, Object>();
final List<ModelObject> assignments = (List<ModelObject>)(List<?>) this.assignmentService.listAll();
final List<ModelObject> projects = (List<ModelObject>)(List<?>) this.projectService.listAll();
map.put("assignments", ObjectConverter.serializeList(assignments, "Assignment"));
map.put("projects", ObjectConverter.serializeList(projects, "Project"));
return map;
}
PS:忽略奇怪的转换,当你知道 XX 可以转换为 YY 时,这是一个将 XX 列表转换为 YY 列表的 Java 技巧。
如您所见,除了 Jackson 之外,它还被使用:您从数据库中检索列表或单个对象,然后使用 ObjectConverter 的专用方法(serializeList 等)转换它们并提供 XML 配置的密钥文件(例如 Assignment.xml)。然后将它们添加到由 Jackson 自己序列化的 Map 中,然后就可以了。
因此,此 ObjectConverter 读取 XML 文件的目标是构建您可以使用这些 XML 文件自定义的数据结构。这避免了为您需要序列化的每个对象创建一个转换器类。
ObjectConverter 类将遍历 XML 文件的所有元素,然后使用 java.lang.reflect 包在要转换的对象中查找这些属性。
请注意,显然拼写在 XML 文件中非常重要,但顺序却不是。
我自己使用该解决方案,并使用我自己的一个小应用程序,我能够生成所有 XML 文件,然后根据需要自定义它们。这可能看起来很重,但这确实对我帮助很大,而且我还没有看到任何性能受到影响。
希望这可以帮助!