2

Jackson 2.2.2 を Json (デ) シリアライザーとして使用して、動作中の Spring MVC 3.1.2 アプリケーションの上に RESTful レイヤーを開発しようとしています。問題は、参照に深く入り込みすぎており、以前はロードに最大 1 秒しかかからなかったページが、サーバー側のみで 22 秒かかることです。

問題は、Jackson がすべてのアソシエーションを通過し、すべてをロードして解析するのに永遠にかかることです。

私は知って@JsonIgnoreいますが、深さの制限を設けたいと思います。

私の素晴らしい説明

B と C の間にリンクを張る@JsonIgnoreと、A をシリアライズするときにうまくいきますが、B をシリアライズする必要があり、C も一緒にシリアライズしたい場合はどうすればよいでしょうか? 私が考えることができる最善の方法は、シリアライザーに深さレベルの制限を与えることです。たとえば、深さ制限 = 1 の場合、A をシリアル化するときに C をシリアル化しませんが、B をシリアル化するときにシリアル化します。そのようなことを行う方法はありますか?

注釈を見た@JsonViewことがありますが、プロパティを除外するのではなく、含めるように設計されています。一部のプロパティを除外するために使用できますが、関連するのは 1 つのクラス レベルのみです。

独自のシリアライザーを作成する必要がありますか? 独自のシリアライザーを作成した場合、そのようなことを実装する方法はありますか?

これに対処できないとは思いませんが、私のケースを助けるものは何も見つかりません...

ありがとう!

4

3 に答える 3

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

私は少し前に解決策にたどり着きましたが、それを投稿する時間とエネルギーがありませんでしたが、ここに行きます:

注: このソリューションは、誰もが夢見る完璧な 2 行のソリューションではありませんが、私が望んでいたとおりに機能します。注意 2: このソリューションでは、XML ファイルを読み取るためにXOMライブラリが必要です。

まず、どのように機能するか: 一連の XML ファイルを作成します。各ファイルは、jackson によってシリアル化される (またはカスタマイズされたシリアル化が必要な) 1 つのエンティティを表します。

以下はそのようなファイルの例です - 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には、すべてのシリアライズ可能なオブジェクトが実装する必要があるインターフェイスである引数が必要です (以下で提供)。
すべてのドメイン オブジェクトを 1 つの特定のタイプにまとめて再グループ化するために使用されるインターフェイスが既にある場合は、このインターフェイスも使用できます (メソッドを 1 つ追加するだけで済みます。以下を参照してください)。

インターフェース 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 に加えて使用されます。DB からオブジェクトのリストまたは単一のオブジェクトを取得し、ObjectConverter の特殊なメソッド (serializeList など) を使用してそれらを変換し、XML 構成にキーを提供します。ファイル (例: Assignment.xml)。次に、Jackson 自身によってシリアル化された Map にそれらを追加します。

したがって、XML ファイルを読み取るこの ObjectConverter の目的は、これらの XML ファイルを使用してカスタマイズできるデータ構造を構築することです。これにより、シリアル化する必要があるオブジェクトごとにコンバーター クラスを作成する必要がなくなります。

ObjectConverter クラスは、XML ファイルのすべての要素をループしてから、java.lang.reflect パッケージを使用して、変換するオブジェクト内のこれらの属性を見つけます。
XML ファイルではスペルが非常に重要であることは明らかですが、順序は重要ではありません。

私はそのソリューションを自分で使用し、独自の小さなアプリを使用して、すべての XML ファイルを生成し、必要に応じてカスタマイズすることができました。これは重いように思えるかもしれませんが、これは本当に私を大いに助け、パフォーマンスの低下は見られませんでした.

これが役立つことを願っています!

于 2013-10-16T15:08:20.280 に答える