После кучи копания и головных болей, я закончил разработку решения.
Шаг 1 (по желанию)
EDIT: Вам не придется латать Джерси добиться такого поведения. Я не могу найти его документально нигде, но аннотация org.glassfish.jersey.internal.inject.Custom
отмечает, что поставщик является обычным (тогда как @Provider
недостаточно). Вы также должны отключить MOXy, установив CommonProperties.MOXY_JSON_FEATURE_DISABLE
в true, если ваш провайдер находится в состоянии application/json
. Таким образом, вы только должны сделать:
@Custom
@Provider
public class MyCustomMessageBodyReader...
[...]
Это моя нелюбимой часть моего решения, но и спасли меня от связки дублирования кода. У алгоритма сортировки Джерси для выбора
MessageBodyReader/Writers
нет способа расставить приоритеты поставщикам приложений (которые я мог найти). Я хотел продлить
AbstractRootElementJaxbProvider
, чтобы повторно использовать его функциональность, но это означало, что я не мог сделать его более конкретным, чем предоставленный Джерси
XmlRootElementJaxbProvider
. Поскольку по умолчанию Джерси сортирует только расстояние между медиаданными, расстояние по типу объекта и регистрируется ли поставщик как пользовательский поставщик (поставщики, обнаруженные через аннотацию
@Provider
, не зарегистрированы как заказные поставщики), реализация Джерси всегда выбиралась вместо мой
MessageBodyReader/Writer
.
Я проверил Джерси 2.10.4 из Github и исправлено
MessageBodyFactory
, чтобы использовать аннотации
@Priority
как часть алгоритма выбора для
MessageBodyReader/Writers
.
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java
index 3845b0c..110f18c 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java
@@ -72,6 +72,7 @@ import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;
+import javax.annotation.Priority;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.xml.transform.Source;
@@ -107,6 +108,8 @@ import jersey.repackaged.com.google.common.primitives.Primitives;
*/
public class MessageBodyFactory implements MessageBodyWorkers {
+ private static final int DEFAULT_WORKER_PRIORITY = 1000;
+
private static final Logger LOGGER = Logger.getLogger(MessageBodyFactory.class.getName());
/**
@@ -218,13 +221,15 @@ public class MessageBodyFactory implements MessageBodyWorkers {
public final T provider;
public final List<MediaType> types;
public final Boolean custom;
+ public final int priority;
public final Class<?> providerClassParam;
protected WorkerModel(
- final T provider, final List<MediaType> types, final Boolean custom, Class<T> providerType) {
+ final T provider, final List<MediaType> types, final Boolean custom, final int priority, Class<T> providerType) {
this.provider = provider;
this.types = types;
this.custom = custom;
+ this.priority = priority;
this.providerClassParam = getProviderClassParam(provider, providerType);
}
@@ -239,8 +244,8 @@ public class MessageBodyFactory implements MessageBodyWorkers {
private static class MbrModel extends WorkerModel<MessageBodyReader> {
- public MbrModel(MessageBodyReader provider, List<MediaType> types, Boolean custom) {
- super(provider, types, custom, MessageBodyReader.class);
+ public MbrModel(MessageBodyReader provider, List<MediaType> types, Boolean custom, int priority) {
+ super(provider, types, custom, priority, MessageBodyReader.class);
}
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
@@ -263,8 +268,8 @@ public class MessageBodyFactory implements MessageBodyWorkers {
private static class MbwModel extends WorkerModel<MessageBodyWriter> {
- public MbwModel(MessageBodyWriter provider, List<MediaType> types, Boolean custom) {
- super(provider, types, custom, MessageBodyWriter.class);
+ public MbwModel(MessageBodyWriter provider, List<MediaType> types, Boolean custom, int priority) {
+ super(provider, types, custom, priority, MessageBodyWriter.class);
}
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
@@ -437,6 +442,10 @@ public class MessageBodyFactory implements MessageBodyWorkers {
if (modelA.custom^modelB.custom) {
return (modelA.custom) ? -1 : 1;
}
+
+ if(modelA.priority != modelB.priority) {
+ return modelA.priority - modelB.priority;
+ }
return 0;
}
@@ -578,17 +587,27 @@ public class MessageBodyFactory implements MessageBodyWorkers {
}
}
+ private static int getPriority(Priority annotation) {
+ if (annotation == null) {
+ return DEFAULT_WORKER_PRIORITY;
+ }
+
+ return annotation.value();
+ }
+
private static void addReaders(List<MbrModel> models, Set<MessageBodyReader> readers, boolean custom) {
for (MessageBodyReader provider : readers) {
+ int priority = getPriority(provider.getClass().getAnnotation(Priority.class));
List<MediaType> values = MediaTypes.createFrom(provider.getClass().getAnnotation(Consumes.class));
- models.add(new MbrModel(provider, values, custom));
+ models.add(new MbrModel(provider, values, custom, priority));
}
}
private static void addWriters(List<MbwModel> models, Set<MessageBodyWriter> writers, boolean custom) {
for (MessageBodyWriter provider : writers) {
+ int priority = getPriority(provider.getClass().getAnnotation(Priority.class));
List<MediaType> values = MediaTypes.createFrom(provider.getClass().getAnnotation(Produces.class));
- models.add(new MbwModel(provider, values, custom));
+ models.add(new MbwModel(provider, values, custom, priority));
}
}
После построения Джерси, я заменил джерси-общий сосуд в Glassfish модули каталог с моей исправленной версии. Это позволит мне аннотировать мой
MessageBodyReader/Writers
с
@Priority(500)
и выбрать их на Джерси.
Я чувствовал, что это был самый чистый способ позволить мне расставить приоритеты для моего
MessageBodyReader/Writers
, не затрагивая ничего другого в Glassfish, который полагается на Джерси.
Шаг 2
Вдохновленный this post я решил, что использование Unmarshaller.Listener
будет чище, чем мой оригинальный путь реализации afterUnmarshal
на каждом из моих классов JAXB. Я сделал интерфейс (CanBeValidated
) и расширил Unmarshaller.Listener
следующим образом.
public final class ValidatingUnmarshallerListener
extends Unmarshaller.Listener
{
private final ValidationEventHandler validationEventHandler;
public ValidatingUnmarshallerListener(
ValidationEventHandler validationEventHandler)
{
this.validationEventHandler = validationEventHandler;
}
@Override
public void afterUnmarshal(Object target, Object parent)
{
if (target == null
|| !(target instanceof CanBeValidated))
{
return;
}
CanBeValidated v = (CanBeValidated) target;
Collection<Throwable> validationErrors = v.validate();
for (Throwable t : validationErrors)
{
ValidationEvent event = new ValidationEventImpl(
ValidationEvent.ERROR,
t.getLocalizedMessage(),
null,
t);
this.validationEventHandler.handleEvent(event);
}
}
}
Шаг 3
Наконец, я продлил org.glassfish.jersey.message.internal.AbstractRootElementJaxbProvider
переопределить метод readFrom
.
@Override
protected Object readFrom(
Class<Object> type,
MediaType mediaType,
Unmarshaller u,
InputStream entityStream)
throws JAXBException
{
final SAXSource source = getSAXSource(spf.provide(), entityStream);
ValidationEventCollector eventCollector = new ValidationEventCollector();
ValidatingUnmarshallerListener listener = new ValidatingUnmarshallerListener(eventCollector);
u.setEventHandler(eventCollector);
u.setListener(listener);
final Object result;
if (type.isAnnotationPresent(XmlRootElement.class))
{
result = u.unmarshal(source);
}
else
{
result = u.unmarshal(source, type).getValue();
}
if (eventCollector.hasEvents())
{
HttpError error = new HttpError(Response.Status.BAD_REQUEST);
for (ValidationEvent event : eventCollector.getEvents())
{
error.addMessage(ValidationUtil.toString(event));
}
throw new WebApplicationException(error.toResponse());
}
return result;
}
Не могли бы вы показать полный код вашего подкласса 'org.glassfish.jersey.message.internal.AbstractRootElementJaxbProvider'? Я не могу заставить меня работать; Я попытался ввести «Провайдеры» с '@ Context' в свой конструктор, который мне нужно получить, чтобы передать конструктору супертипа, но я получаю это исключение:' org.jboss.weld.exceptions.CreationException: WELD- 001530: Не удается создать экземпляр класса [...]. ' –
Я столкнулся с той же проблемой. Я работал вокруг этого, лениво загружая класс. Я написал обертку 'public abstract class LazyJaxbProvider реализует MessageBodyReader , MessageBodyWriter ' и ввел поставщиков в оболочку '@Context private Providers ps'. Затем я написал метод 'getMessageBodyReaderWriter', который возвращает новый экземпляр моего подкласса' AbstractRootElementJaxbProvider', предоставляя 'this.ps' в качестве аргумента конструктора. –
В зависимости от того, что вы делаете, вы также можете посмотреть на [API проверки боба] (https://jersey.java.net/documentation/latest/bean-validation.html). Я не пробовал использовать его вообще, поэтому я даже не уверен, что версия Джерси, упакованная с Glassfish 4.1, поддерживает проверку валиков. Я чувствовал, что имеет смысл иметь объекты, которые выполняют свою собственную проверку, вместо того, чтобы писать кучу шаблонных пользовательских аннотаций, которые в любом случае будут тесно связаны с целевым классом. –