В моей предыдущей статье мы рассмотрели использование функции MOXy для управления уровнем вывода данных для конкретной сущности. В этом посте рассматривается абстракция, предоставленная в Jersey 2.x, которая позволяет вам определять собственный набор аннотаций, чтобы иметь тот же эффект.
Как и прежде, у нас есть почти тривиальный ресурс, который возвращает объект, который Джерси будет для нас преобразовать в JSON, обратите внимание, что на данный момент в этом коде нет ничего для фильтрации — я не собираюсь передавать аннотации для
Объект Response как в примерах на Джерси:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
import javax.ws.rs.GET;import javax.ws.rs.Path;import javax.ws.rs.Produces;@Path("hello")public class SelectableHello { @GET @Produces({ "application/json; level=detailed", "application/json; level=summary", "application/json; level=normal" }) public Message hello() { return new Message(); }} |
В моем дизайне я собираюсь определить четыре аннотации: NoView , SummaryView , NormalView и NormalView . Все корневые объекты должны реализовывать аннотацию NoView, чтобы не открывать аннотированные поля — вы можете не чувствовать, что это необходимо в вашем проекте. Все эти классы выглядят одинаково, поэтому я собираюсь показать только один. Обратите внимание, что фабричный метод, создающий AnnotationLiteral , должен использоваться вместо фабрики, которая создала бы динамический прокси, чтобы иметь тот же эффект. В версии 2.5 есть код, который будет игнорировать любые аннотации, реализованные объектом java.lang.reflect.Proxy , включая любые аннотации, которые вы, возможно, получили из класса. Я работаю над отправкой исправления для этого.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import javax.enterprise.util.AnnotationLiteral;import org.glassfish.jersey.message.filtering.EntityFiltering;@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })@Retention(RetentionPolicy.RUNTIME)@Documented@EntityFilteringpublic @interface NoView { /** * Factory class for creating instances of the annotation. */ public static class Factory extends AnnotationLiteral<NoView> implements NoView { private Factory() { } public static NoView get() { return new Factory(); } }} |
Теперь мы можем быстро взглянуть на наш бин Message, это немного сложнее, чем в моем предыдущем примере, чтобы показать фильтрацию подграфов в очень простой форме. Как я уже говорил, до того, как класс будет аннотирован аннотацией NoView в корне — это должно означать, что privateData никогда не возвращается клиенту, поскольку он специально не аннотируется.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import javax.xml.bind.annotation.XmlRootElement;@XmlRootElement@NoViewpublic class Message { private String privateData; @SummaryView private String summary; @NormalView private String message; @DetailedView private String subtext; @DetailedView private SubMessage submessage; public Message() { summary = "Some simple summary"; message = "This is indeed the message"; subtext = "This is the deep and meaningful subtext"; submessage = new SubMessage(); privateData = "The fox is flying tonight"; } // Getters and setters not shown}public class SubMessage { private String message; public SubMessage() { message = "Some sub messages"; } // Getters and setters not shown} |
Как отмечалось ранее, в классе ресурсов нет кода для работы с фильтрацией — я посчитал это сквозной проблемой, поэтому абстрагировал это в WriterInterceptor. Обратите внимание на исключение, которое выдается, если используется объект, на котором нет аннотации NoView.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
import java.io.IOException;import java.lang.annotation.Annotation;import java.util.Arrays;import java.util.LinkedHashSet;import java.util.Set;import javax.ws.rs.ServerErrorException;import javax.ws.rs.WebApplicationException;import javax.ws.rs.core.Context;import javax.ws.rs.core.HttpHeaders;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Response;import javax.ws.rs.ext.Provider;import javax.ws.rs.ext.WriterInterceptor;import javax.ws.rs.ext.WriterInterceptorContext;@Providerpublic class ViewWriteInterceptor implements WriterInterceptor { private HttpHeaders httpHeaders; public ViewWriteInterceptor(@Context HttpHeaders httpHeaders) { this.httpHeaders = httpHeaders; } @Override public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException { // I assume this case will never happen, just to be sure if (writerInterceptorContext.getEntity() == null) { writerInterceptorContext.proceed(); return; } else { Class<?> entityType = writerInterceptorContext.getEntity() .getClass(); String entityTypeString = entityType.getName(); // Ignore any Jersey system classes, for example wadl // if (entityType == String.class || entityType.isArray() || entityTypeString.startsWith("com.sun") || entityTypeString.startsWith("org.glassfish")) { writerInterceptorContext.proceed(); return; } // Fail if the class doesn't have the default NoView annotation // this prevents any unannotated fields from showing up // else if (!entityType.isAnnotationPresent(NoView.class)) { throw new ServerErrorException("Entity type should be tagged with @NoView annotation " + entityType, Response.Status.INTERNAL_SERVER_ERROR); } } // Get hold of the return media type: // MediaType mt = writerInterceptorContext.getMediaType(); String level = mt.getParameters().get("level"); // Get the annotations and modify as required // Set<Annotation> current = new LinkedHashSet<>(); current.addAll(Arrays.asList( writerInterceptorContext.getAnnotations())); switch (level != null ? level : "") { default: case "detailed": current.add(com.example.annotation.DetailedView.Factory.get()); case "normal": current.add(com.example.annotation.NormalView.Factory.get()); case "summary": current.add(com.example.annotation.SummaryView.Factory.get()); } writerInterceptorContext.setAnnotations( current.toArray(new Annotation[current.size()])); // writerInterceptorContext.proceed(); }} |
Наконец, вы должны включить EntityFilterFeature вручную, чтобы сделать это, вы можете просто зарегистрировать его в своем классе Application
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import java.lang.annotation.Annotation;import javax.ws.rs.ApplicationPath;import org.glassfish.jersey.message.filtering.EntityFilteringFeature;import org.glassfish.jersey.server.ResourceConfig;@ApplicationPath("/resources/")public class SelectableApplication extends ResourceConfig { public SelectableApplication() { packages("..."); // Set entity-filtering scope via configuration. property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE, new Annotation[] { NormalView.Factory.get(), DetailedView.Factory.get(), NoView.Factory.get(), SummaryView.Factory.get() }); register(EntityFilteringFeature.class); }} |
Как только вы все это запустите, приложение ответит, как и раньше:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
GET .../hello Accept application/json; level=detailed or application/json{ "message" : "This is indeed the message", "submessage" : { "message" : "Some sub messages" }, "subtext" : "This is the deep and meaningful subtext", "summary" : "Some simple summary"}GET .../hello Accept application/json; level=normal{ "message" : "This is indeed the message", "summary" : "Some simple summary"}GET .../hello Accept application/json; level=summary{ "summary" : "Some simple summary"} |
Похоже, что это лучшая альтернатива непосредственному использованию аннотаций MOXy — использование пользовательских аннотаций должно значительно облегчить портирование приложения на реализацию, даже если вы должны предоставить собственный фильтр. Наконец, стоит также изучить расширение Джерси, которое позволяет фильтровать на основе ролей, что я считаю полезным в аспекте безопасности.