簡體   English   中英

從Rest控制器返回對象層次結構時如何使用Spring MVC @JsonView

[英]How to use Spring MVC @JsonView when returning an object hierarchy from a Rest Controller

我正在構建一個使用Spring MVC 4.10和jackson 2.3.2的應用程序。 我有一個Project類,其中包含子Proposal對象和一個Customer對象。 這些投標對象很復雜,我想返回它們的摘要JSON視圖。 Customer對象也會發生類似情況。 我正在嘗試使用@JsonView注釋來實現。

我想問一下,是否擴展容器對象類視圖中成員對象類的視圖是執行此操作的方法,如果不是,是否存在一種我不知道的更干凈的方法來實現。

語境

在今天之前,我有一種錯誤的印象,那就是您可以使用多個視圖來注釋控制器,並且所產生的JSON表示將被相應地過濾。

@JsonView({Project.Extended.class, Proposal.Summary.class, Customer.Summary.class})
@Transactional
@RequestMapping(value="/project", method=RequestMethod.GET)
public @ResponseBody List<Project> findAll() {
    return projectDAO.findAll();    
}

每個類都有自己的JsonView注釋和接口,例如:

public class Customer {
    ...
    public interface Summary {}
    public interface Normal extends Summary {}
    public interface Extended extends Normal {}
}

但是,僅考慮到陣列中的第一個視圖。 根據https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

只能使用@JsonView批注指定一個類或接口,但是您可以使用繼承來表示JSON View層次結構(如果字段是JSON View的一部分,那么它將也是父視圖的一部分)。 例如,此處理程序方法將序列化以@JsonView(View.Summary.class)和@JsonView(View.SummaryWithRecipients.class)注釋的字段:

以及http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview中的官方文檔

要將其與@ResponseBody控制器方法或返回ResponseEntity的控制器方法一起使用,只需添加@JsonView批注以及一個類參數,該參數指定要使用的視圖類或接口:

因此,我最終在容器對象的視圖中擴展了成員的視圖,就像這樣

@Entity
public class Project {
    ...
    public static interface Extended extends Normal, Proposal.Extended {}
    public static interface Normal extends Summary, Customer.Normal {}
    public static interface Summary {}
}

並將控制器更改為此

@JsonView(Project.Extended.class)
@Transactional
@RequestMapping(value="/project", method=RequestMethod.GET)
public @ResponseBody List<Project> findAll() {
    return projectDAO.findAll();    
}

這似乎可以解決問題,但是我找不到有關這種情況的文檔或討論。 這是JsonViews的預期用途還是有點黑?

先感謝您

-帕特里西奧·馬洛

我相信您已經根據需要配置了視圖。 問題的根源不是Spring的@JsonView,而是Jackson的視圖實現。 如Jackson的查看文檔所述

每個序列化只有一個活動視圖; 但由於繼承了視圖,因此可以通過聚合來合並視圖。

因此,看來Spring只是在繼續並遵循Jackson 2設定的限制。

我使用Jersey + Jackson,但發出了同樣的問題。

這是我為應用程序做的一個技巧, 讓我在序列化過程中需要多個JSON視圖 我敢打賭,用Spring MVC而不是Jersey也有可能,但是不能100%確定。 它似乎也沒有性能問題。 也許這對於您的情況有些復雜,但是如果您有一個帶有大量可能視圖的大型對象,那么它比進行大量繼承要好。

因此,我使用傑克遜過濾器方法在序列化中需要多個視圖。 但是,我還沒有找到克服將@JsonFilter(“ name”)放在要映射的類之上的問題的方法,這並沒有使其變得如此干凈。 但是我至少在自定義注釋中將其屏蔽:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonFilter(JSONUtils.JACKSON_MULTIPLE_VIEWS_FILTER_NAME)
public @interface JsonMultipleViews {}

過濾器本身看起來像這樣:

public class JsonMultipleViewsFilter extends SimpleBeanPropertyFilter {

    private Collection<Class<?>> wantedViews;

    public JsonMultipleViewsFilter(Collection<Class<?>> wantedViews) {
        this.wantedViews = wantedViews;
    }

    @Override
    public void serializeAsField( Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer ) throws Exception {
        if( include( writer ) ) {
            JsonView jsonViewAnnotation = writer.getAnnotation(JsonView.class);
            // serialize the field only if there is no @JsonView annotation or, if there is one, check that at least one
            // of view classes above the field fits one of required classes. if yes, serialize the field, if no - skip the field
            if( jsonViewAnnotation == null || containsJsonViews(jsonViewAnnotation.value()) ) {
                writer.serializeAsField( pojo, jgen, provider );
            }
        }
        else if( !jgen.canOmitFields() ) { 
            // since 2.3
            writer.serializeAsOmittedField( pojo, jgen, provider );
        }
    }    

    private boolean containsJsonViews(Class<?>[] viewsOfProperty) {
        for (Class<?> viewOfProperty : viewsOfProperty) {
            for (Class<?> wantedView : wantedViews) {
                // check also subclasses of required view class
                if (viewOfProperty.isAssignableFrom(wantedView)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    protected boolean include( BeanPropertyWriter writer ) {
        return true;
    }

    @Override
    protected boolean include( PropertyWriter writer ) {
        return true;
    }
}

我可以像這樣使用此過濾器:

public static String toJson( Object object, Collection<Class<?>> jsonViewClasses) throws JsonProcessingException {
    // if no json view class is provided, just map without view approach
    if (jsonViewClasses.isEmpty()) {
        return mapper.writeValueAsString(object);
    }
    // if only one json view class is provided, use out of the box jackson mechanism for handling json views
    if (jsonViewClasses.size() == 1) {
        return mapper.writerWithView(jsonViewClasses.iterator().next()).writeValueAsString(object);
    }

    // if more than one json view class is provided, uses custom filter to serialize with multiple views
    JsonMultipleViewsFilter jsonMultipleViewsFilter = new JsonMultipleViewsFilter(jsonViewClasses);
    return mapper.writer(new SimpleFilterProvider() // use filter approach when serializing
                .setDefaultFilter(jsonMultipleViewsFilter) // set it as default filter in case of error in writing filter name
                .addFilter(JACKSON_MULTIPLE_VIEWS_FILTER_NAME, jsonMultipleViewsFilter) // set custom filter for multiple views with name
                .setFailOnUnknownId(false)) // if filter is unknown, don't fail, use default one
                .writeValueAsString(object);
}

之后,Jersey允許我們在運行應用程序時添加Jersey過濾器(它遍歷應用程序啟動時每個Controller中的每個端點,如果@JsonView中有多個值,我們此時可以輕松綁定Jersey過濾器端點上方的注釋)。

在@JsonView批注的Jersey過濾器中,在端點上方有多個值,一旦啟動時綁定,根據批注校正端點,我們可以通過調用utils方法輕松地覆蓋響應實體

toJson(previousResponeObjectReturned, viewClassesFromAnnoation);

由於您使用的是Spring MVC,因此無需在此處提供Jersey過濾器的代碼。 我只是希望在Spring MVC中以相同的方式輕松實現。

域對象如下所示:

@JsonMultipleViews
public class Example
{
    private int id;
    private String name;

    @JsonView(JsonViews.Extended.class)
    private String extendedInfo;

    @JsonView(JsonViews.Meta.class)
    private Date updateDate;

    public static class JsonViews {
        public interface Min {} 
        public interface Extended extends Min {} 
        public interface Meta extends Min {} 
        //...
        public interface All extends Extended, Meta {} // interfaces are needed for multiple inheritence of views
    }
}

我們可以忽略將Min.class放在我總是需要不依賴於視圖的那些字段中的情況。 我們只是將Min放在必需的視圖中,它將對沒有@JsonView批注的所有字段進行序列化。

View All.class對我來說是必需的,因為例如,如果我們為每個域類擁有一組特定的視圖(例如在我的情況下),然后我們需要映射一個由多個都使用視圖方法的域對象組成的復雜模型-對象1的某些視圖,但對象2的所有視圖,將其放在端點上方比較容易,如下所示:

 @JsonView({ObjectOneViews.SomeView.class, ObjectTwoViews.All.class})

因為如果我們在這里省略ObjectTwoViews.All.class並且僅要求ObjectOneViews.SomeView.class,則在對象2中標記為注釋的那些字段將不會被序列化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM