2

我一直在尝试在使用 Jackson 2.2.2 作为 Json (de)serializer 的 Spring MVC 3.1.2 应用程序之上开发一个 RESTful 层。问题是,它对引用的影响太深了,对于一个过去最多需要 1 秒才能加载的页面,现在只需要 22 秒的服务器端。

问题是杰克逊正在经历每一个关联,并且需要永远加载和解析它。

我知道@JsonIgnore但是很好,我宁愿有一个深度限制,因为如果我们说:

我惊人的解释

如果我@JsonIgnore在 B 和 C 之间建立链接,那么在序列化 A 时我会很好,但是如果我需要序列化 ​​B 并希望 C 一起序列化怎么办?我能想到的最好方法是给序列化器一个深度级别的限制。假设深度限制= 1,那么在序列化A时它不会序列化C,但在序列化B时仍然会序列化它。有没有办法做这样的事情?

我看过@JsonView注释,但它旨在包含属性而不是排除它们。它可用于排除某些属性,但仅在一类级别上相关。

我需要编写自己的序列化程序吗?如果我编写自己的序列化程序,有没有办法实现这样的事情?

我认为这无法解决,但我找不到任何可以帮助我解决问题的东西...

谢谢!

4

3 回答 3

1

不久前我找到了一个解决方案,并没有真正的时间和精力来发布它,但我们开始吧:

注意:这个解决方案不是每个人都梦寐以求的完美的两行解决方案,但它可以按我的意愿工作。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的类型idname序列化的。

这主要是您如何选择要序列化的内容和不序列化的内容,从而限制序列化的深度。

现在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 文件,然后根据需要自定义它们。这可能看起来很重,但这确实对我帮助很大,而且我还没有看到任何性能受到影响。

希望这可以帮助!

于 2013-10-16T15:08:20.280 回答
1

这是一个非常常见的问题,并且已经得到解决,请查看 Jackson 的 @JsonBackReference 注释。样本:

@Entity
@Table(name = 'EMPLOYERS')
public class Employer implements Serializable {
    @JsonManagedReference('employer-employee')
    @OneToMany(mappedBy = 'employer', cascade = CascadeType.PERSIST)
    public List getEmployees() {
        return employees;
    }
}

@Entity
@Table(name = 'EMPLOYEES')
public class Employee implements Serializable {
    @JsonManagedReference('employee-benefit')
    @OneToMany(mappedBy = 'employee', cascade = CascadeType.PERSIST)
    public List getBenefits() {
        return benefits;
    }

    @JsonBackReference('employer-employee')
    @ManyToOne(optional = false)
    @JoinColumn(name = 'EMPLOYER_ID')
    public Employer getEmployer() {
        return employer;
    }
}

@Entity
@Table(name = 'BENEFITS')
public class Benefit implements Serializable {
    @JsonBackReference('employee-benefit')
    @ManyToOne(optional = false)
    @JoinColumn(name = 'EMPLOYEE_ID')
    public Employee getEmployee() {
        return employee;
    }
}

完整的例子

于 2014-02-02T18:01:22.813 回答
1

如果您正在使用Hibernate,Jackson Hibernate 模块(https://github.com/FasterXML/jackson-datatype-hibernate)支持禁用加载“延迟”属性(延迟获取的集合)。这将允许您限制要访问和序列化的对象图的大部分。

除此之外,杰克逊没有基于深度的限制;核心包对特定的域/数据模块一无所知。可以编写扩展模块来改变特定域的行为;这就是 Hibernate 模块的工作原理。也许这可用于通用 JPA 特定的功能。

于 2013-08-27T18:59:07.240 回答