如何处理由请求体上的JAXB自动解组的setter引发的异常?

Sor*_*aru 5 java rest web-services jax-rs jaxb

我将以下JSON请求主体发送到我的控制器:

{"Game": {"url": "asd"}}
Run Code Online (Sandbox Code Playgroud)

Game我的模型类在哪里注释@XmlRootElement(和一些JPA注释在这个上下文中不重要).

控制器:

@PUT
@Path("/{name}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createRow(
    @PathParam("name") String name, 
    Game gameData) throws Exception{

    Game.createRow(gameData); // + exception handling etc.
}
Run Code Online (Sandbox Code Playgroud)

现在,我明白了当Game gameData创建控制器方法的参数时,我会调用模型类中的setter.需要注意的二传手是:

public void setUrl(String url) throws Exception{
  String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
  Pattern pattern = Pattern.compile(regex);

  System.out.println("URL: " + url);
  if ( url == null || url.length() == 0) {
    throw new Exception("The url of the game is mandatory!");
  } else {
    Matcher matcher = pattern.matcher(url);
    if (!matcher.matches()) { 
      throw new Exception("The url is invalid! Please check its syntax!");
    } else {
      this.url = url;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

发生的事情是,在将JSON字符串反序列化到Game对象期间,The url is invalid!抛出异常,但仅在控制台中抛出异常TomEE.我想要做的是将此错误发送给客户端.

如果我使用扩展WebApplicationException而不是泛型异常的异常,那么我在客户端中得到一个异常,但不是关于url有效性的异常.相反,在反序列化之后,gameData.url为NULL,当我尝试使用来自数据创建一个Game实例时gameData,setter将被调用,当gameToBeCreated.set(NULL)Url is mandatory从客户端发送一个URL时,我会得到异常,但是句法.从客户端发送时不是NULL.

那么,我可以以某种方式拦截自动解组时发生的异常并将它们转发给客户端吗?

bdo*_*han 5

使用JAXB拦截异常

A ValidationEventHandler用于拦截解组期间发生的异常.如果要收集发生的所有错误,可以使用a ValidationEventCollector.

Java模型

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Foo {

    private String bar;

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        throw new RuntimeException("Always throw an error");
    }

}
Run Code Online (Sandbox Code Playgroud)

演示

import java.io.StringReader;
import javax.xml.bind.*;
import javax.xml.bind.util.ValidationEventCollector;

public class Demo {

    private static String XML = "<foo><bar>Hello World</bar></foo>";

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Foo.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.unmarshal(new StringReader(XML));

        ValidationEventCollector vec = new ValidationEventCollector();
        unmarshaller.setEventHandler(vec);
        unmarshaller.unmarshal(new StringReader(XML));
        System.out.println(vec.getEvents().length);
    }

}
Run Code Online (Sandbox Code Playgroud)

产量

1
Run Code Online (Sandbox Code Playgroud)

与JAX-RS集成

MessageBodyReader

您可以创建MessageBodyReader一个ValidationEventHandler在解组过程中可以利用a 的实例.下面是一个链接,给出了如何实现a的示例MessageBodyReader.

ContextResolver<Unmarshaller>

稍微高一点,你可以实现ContextResolver<Unmarshaller>并让Unmarshaller返回有适当的ValidationEventHandler.它看起来像下面这样:

import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import javax.xml.bind.helpers.DefaultValidationEventHandler;

@Provider
public class UnmarshallerResolver implements ContextResolver<Unmarshaller> {

    @Context Providers providers;

    @Override
    public Unmarshaller getContext(Class<?> type) {
        try {
            ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, MediaType.APPLICATION_XML_TYPE);
            JAXBContext jaxbContext;
            if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
                jaxbContext = JAXBContext.newInstance(type);
            }
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setEventHandler(new DefaultValidationEventHandler());
            return unmarshaller;
        } catch(JAXBException e) {
            throw new RuntimeException(e);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)