Jersey REST/ JAXB error , mapping an Interface

2019-07-06 00:38发布

I have to use an interface in my REST web service. Here is the Interface Specs.java :

@XmlJavaTypeAdapter(MyAdapter.class)
public interface Specs {

    public BaseProperties getBaseProps();
    public void setBaseProps(BaseProperties baseProps);

}

MyAdapter.java :

public class MyAdapter extends XmlAdapter<Object,Object> 
{  
    public Object unmarshal(Object v) 
    { 
        return v; 
    }  
    public Object marshal(Object v) 
    { 
        return v; 
    }
}

RegSpecs.java

@XmlType
public class RegSpecs implements Specs{
private BaseProperties baseProps;

    public BaseProperties getBaseProps()
    {
        return baseProps;
    }
    public void setBaseProps(BaseProperties baseProps)
    {
        this.baseProps = baseProps;
    }

}

MapSpecs.java

@XmlType
public class MagSpecs implements Specs {

private BaseProperties baseProps;
private Features features;

    public BaseProperties getBaseProps()
    {
        return baseProps;
    }
    public void setBaseProps(BaseProperties baseProps)
    {
        this.baseProps = baseProps;
    }
    public Features getFeatures() {
        return features;
    }
    public void setFeatures(Features features) {
        this.features = features;
    }

}

Accessing this service throws the following error :

javax.xml.bind.MarshalException - with linked exception: [javax.xml.bind.JAXBException: class entities.MagSpecs nor any of its super class is known to this context.]

How to modify my context ? I am using JAXB bundled with Jersey 1.5

Thanks !

EDIT : In an attempt to update my context, I added this code to my client (resource) class :

public class BookService  implements ContextResolver<JAXBContext> 
{

        private JAXBContext jaxbContext;

        public BookService() {
            try {
                // Bootstrap your JAXBContext will all necessary classes
                jaxbContext = JAXBContext.newInstance(Specs.class,MagSpecs.class, RegSpecs.class);
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }

        public JAXBContext getContext(Class<?> clazz) {
            if(BookService.class == clazz) {
                return jaxbContext;
            }
            return null;
        }

In this case I get error :

entities.Specs is an interface, and JAXB can't handle interfaces. this problem is related to the following location: at entities.Specs entities.Specs does not have a no-arg default constructor. this problem is related to the following location: at entities.Specs

2条回答
我欲成王,谁敢阻挡
2楼-- · 2019-07-06 00:39

EclipseLink JAXB (MOXy) can map interfaces to XML (Note I'm the tech lead). You need to be sure to have a create method on the corresponding object factory to return a concrete impl:

MOXy integrates cleaning into REST environments:

查看更多
戒情不戒烟
3楼-- · 2019-07-06 00:59

The client of the Specs interface needs to know that MagSpecs can be an instance of it so that it knows to look at it for tooling purposes. The easiest way of doing this is to put an @XmlSeeAlso annotation on the Specs interface:

@XmlSeeAlso({ MagSpecs.class, RegSpecs.class })
@XmlJavaTypeAdapter(MyAdapter.class) // Never needed this annotation myself...
public interface Specs {
    public BaseProperties getBaseProps();
    public void setBaseProps(BaseProperties baseProps);
}

In general, whenever I'm working with JAXB annotations I make sure I write plenty of tests to check that an XML schema can be generated from the classes in question, checking that from each (sane) entry point into the web of classes and interfaces I can generate a sensible schema without exceptions. For example (and I apologize for this being a bit long):

private SchemaOutputResolver sink;
StringWriter schema;

@Before
public void init() {
    schema = new StringWriter();
    sink = new SchemaOutputResolver() {
        @Override
        public Result createOutput(String namespaceUri,
                String suggestedFileName) throws IOException {
            StreamResult sr = new StreamResult(schema);
            sr.setSystemId("/dev/null");
            return sr;
        }
    };
    Assert.assertTrue(schema.toString().isEmpty());
}

private void testJAXB(Class<?>... classes) throws Exception {
    JAXBContext.newInstance(classes).generateSchema(sink);
    Assert.assertTrue(schema.toString().length() > 0);
}

@Test
public void testJAXBForSpecs() throws Exception {
    testJAXB(Specs.class);
}

[EDIT]: You also need to change the Specs interface into a class and have the current implementations inherit from it. It can be a fully abstract class if you want. As long as you're not putting serious functionality in the classes, it should work.

查看更多
登录 后发表回答