在 Glassfish 4.1 中捕获来自 Object.afterUnmarshal 的异常
Catch exception from Object.afterUnmarshal in Glassfish 4.1
我目前 运行 Glassfish 4.1 JDK 1.8.0-40。我正在使用 javaee-web-api-7.0 和 jersey-media-moxy-2.22。我是 marshalling/unmarshalling JSON 和 XML from/to JAXB 注释的 java 对象。
我有一个 ContextResolver<Unmarshaller>
设置来为 Unmarshaller
提供自定义 ValidationEventHandler
,它将收集来自 属性 设置器的异常并抛出 BadRequestException
与聚合验证错误。这部分正在运行。
但是,我在检查未设置属性的对象上也有 beforeMarshal
和 afterUnmarshal
方法(必须设置属性的规则因属性值而异,导致我排除针对模式的验证)。如果 afterUnmarshal
方法抛出异常,ValidationEventHandler
看不到它,而是冒泡到 ExceptionMapper
.
是否有任何方法可以捕获来自单个对象的 beforeMarshal
和 afterUnmarshal
方法的异常并将它们传送到 ValidationEventHandler
?
我认为可以实现一个 MessageBodyReader
来捕获异常,使用 Unmarshaller.getEventHandler
,手动调用 ValidationEventHandler.handleEvent
,然后抛出一个 BadRequestException
if handleEvent
returns false [编辑:如果从 Unmarshaller.unmarshal
抛出异常,则无法继续解组,因此唯一可能的办法是终止处理]。但这会丢失事件位置信息,而且我并不特别喜欢实现我自己的 MessageBodyReader
。我希望有一种更简单的内置方法来执行此操作,但我还没有发现。
在此先感谢您的帮助。
经过大量的挖掘和头痛之后,我最终开发了一个解决方案。
第 1 步(可选)
编辑: 您不必修补 Jersey 即可实现此行为。我无法在任何地方找到它的记录,但是 org.glassfish.jersey.internal.inject.Custom
注释将提供者标记为自定义(而仅 @Provider
是不够的)。如果您的提供商是 application/json
,您还必须通过将 CommonProperties.MOXY_JSON_FEATURE_DISABLE
设置为 true 来禁用 MOXy。因此你只需要做:
@Custom
@Provider
public class MyCustomMessageBodyReader...
[...]
这是我的解决方案中我最不喜欢的部分,但也使我免于一堆代码重复。泽西岛用于选择 MessageBodyReader/Writers
的排序算法无法确定应用程序提供商的优先级(我可以找到)。我想扩展 AbstractRootElementJaxbProvider
以重新使用它的功能,但这意味着我不能使它比 Jersey 提供的 XmlRootElementJaxbProvider
更具体。由于默认情况下 Jersey 仅根据媒体类型距离、对象类型距离以及提供者是否注册为自定义提供者(通过 @Provider
注释检测到的提供者未注册为自定义提供者)进行排序,Jersey 实现将始终被选中而不是我的MessageBodyReader/Writer
。
我从 Github 检查了 Jersey 2.10.4 源代码并修补了 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));
}
}
在构建 Jersey 之后,我用我的补丁版本替换了 Glassfish 模块目录中的 jersey-common jar。这让我用 @Priority(500)
注释我的 MessageBodyReader/Writers
并让它们被 Jersey 选择。
我觉得这是让我优先考虑我的 MessageBodyReader/Writers
而不会影响依赖 Jersey 的 Glassfish 中的任何其他内容的最干净的方法。
第 2 步
受到 this post 的启发,我决定使用 Unmarshaller.Listener
比我在每个 JAXB 类 上实现 afterUnmarshal
的原始路径更清晰。我做了一个接口(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;
}
我目前 运行 Glassfish 4.1 JDK 1.8.0-40。我正在使用 javaee-web-api-7.0 和 jersey-media-moxy-2.22。我是 marshalling/unmarshalling JSON 和 XML from/to JAXB 注释的 java 对象。
我有一个 ContextResolver<Unmarshaller>
设置来为 Unmarshaller
提供自定义 ValidationEventHandler
,它将收集来自 属性 设置器的异常并抛出 BadRequestException
与聚合验证错误。这部分正在运行。
但是,我在检查未设置属性的对象上也有 beforeMarshal
和 afterUnmarshal
方法(必须设置属性的规则因属性值而异,导致我排除针对模式的验证)。如果 afterUnmarshal
方法抛出异常,ValidationEventHandler
看不到它,而是冒泡到 ExceptionMapper
.
是否有任何方法可以捕获来自单个对象的 beforeMarshal
和 afterUnmarshal
方法的异常并将它们传送到 ValidationEventHandler
?
我认为可以实现一个 MessageBodyReader
来捕获异常,使用 Unmarshaller.getEventHandler
,手动调用 ValidationEventHandler.handleEvent
,然后抛出一个 BadRequestException
if [编辑:如果从 handleEvent
returns falseUnmarshaller.unmarshal
抛出异常,则无法继续解组,因此唯一可能的办法是终止处理]。但这会丢失事件位置信息,而且我并不特别喜欢实现我自己的 MessageBodyReader
。我希望有一种更简单的内置方法来执行此操作,但我还没有发现。
在此先感谢您的帮助。
经过大量的挖掘和头痛之后,我最终开发了一个解决方案。
第 1 步(可选)
编辑: 您不必修补 Jersey 即可实现此行为。我无法在任何地方找到它的记录,但是 org.glassfish.jersey.internal.inject.Custom
注释将提供者标记为自定义(而仅 @Provider
是不够的)。如果您的提供商是 application/json
,您还必须通过将 CommonProperties.MOXY_JSON_FEATURE_DISABLE
设置为 true 来禁用 MOXy。因此你只需要做:
@Custom
@Provider
public class MyCustomMessageBodyReader...
[...]
这是我的解决方案中我最不喜欢的部分,但也使我免于一堆代码重复。泽西岛用于选择 MessageBodyReader/Writers
的排序算法无法确定应用程序提供商的优先级(我可以找到)。我想扩展 AbstractRootElementJaxbProvider
以重新使用它的功能,但这意味着我不能使它比 Jersey 提供的 XmlRootElementJaxbProvider
更具体。由于默认情况下 Jersey 仅根据媒体类型距离、对象类型距离以及提供者是否注册为自定义提供者(通过 @Provider
注释检测到的提供者未注册为自定义提供者)进行排序,Jersey 实现将始终被选中而不是我的MessageBodyReader/Writer
。
我从 Github 检查了 Jersey 2.10.4 源代码并修补了 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));
}
}
在构建 Jersey 之后,我用我的补丁版本替换了 Glassfish 模块目录中的 jersey-common jar。这让我用 @Priority(500)
注释我的 MessageBodyReader/Writers
并让它们被 Jersey 选择。
我觉得这是让我优先考虑我的 MessageBodyReader/Writers
而不会影响依赖 Jersey 的 Glassfish 中的任何其他内容的最干净的方法。
第 2 步
受到 this post 的启发,我决定使用 Unmarshaller.Listener
比我在每个 JAXB 类 上实现 afterUnmarshal
的原始路径更清晰。我做了一个接口(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;
}