JAXB:在解组时拦截?

我有一个使用JAX-RS和JAXB的典型Web服务,在解组时我想知道JAXB显式调用了哪些setter。 这有效地让我知道调用者提供的文档中包含哪些元素。

我知道我可以使用XmlAdapter解决这个XmlAdapter ,但是我在很多不同的包中都有很多类,我不想为每一个都创建适配器。 我也不想把钩子放进每一个二传手。 如果可能,我想要一个通用的解决方案。 请注意,我的所有类都设置为使用getter和setter; 它们都没有使用访问类型的字段。

我的服务使用Jersey 2.4,Spring 3.2和MOXy 2.5.1,所以如果有任何东西可以从任何一个中得到充分利用,那就更好了。 我们最初的想法是我们可以动态创建一个工厂类(类似于@XmlType支持的),它将返回一个拦截setter的代理对象。 我们认为我们可以使用MOXy中的MetadataSource概念来实现这一点,但这似乎不可能。

有人有主意吗?

我的服务使用Jersey 2.4,Spring 3.2和MOXy 2.5.1,所以如果有任何东西可以从任何一个中得到充分利用,那就更好了。

创建自己的EclipseLink AttributeAccessor

MOXy(它是EclipseLink的一个组件)利用一个名为AttributeAccessor的类来对字段和属性进行操作。 您可以包装此类以捕获所需的所有信息。

 import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.mappings.AttributeAccessor; public class MyAttributeAccessor extends AttributeAccessor { private AttributeAccessor attributeAccessor; public MyAttributeAccessor(AttributeAccessor attributeAccessor) { this.attributeAccessor = attributeAccessor; } @Override public Object getAttributeValueFromObject(Object domainObject) throws DescriptorException { return attributeAccessor.getAttributeValueFromObject(domainObject); } @Override public void setAttributeValueInObject(Object domainObject, Object value) throws DescriptorException { System.out.println("Thread: " + Thread.currentThread().getId() + " - Set value: " + value + " on property: " + attributeAccessor.getAttributeName() + " for object: " + domainObject); attributeAccessor.setAttributeValueInObject(domainObject, value); } } 

告诉MOXy使用您的AttributeAccessor

我们可以利用SessionEventListener来访问底层元数据,以指定AttributeAccessor的实现。 这在创建JAXBContext时作为属性传入。

  Map properties = new HashMap(1); properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() { @Override public void postLogin(SessionEvent event) { Project project = event.getSession().getProject(); for(ClassDescriptor descriptor : project.getOrderedDescriptors()) { for(DatabaseMapping mapping : descriptor.getMappings()) { mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor())); } } super.preLogin(event); } }); JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties); 

在创建JAXBContext时利用JAX-RS ContextResolver

由于您处于JAX-RS环境中,因此可以利用ContextResolver控制JAXBContext的创建方式。


独立示例

Java模型(Foo)

下面是一个示例类,我们将使用字段访问(无设置器)。

 import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Foo { private String bar; private String baz; } 

演示

 import java.io.StringReader; import java.util.*; import javax.xml.bind.*; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.jaxb.JAXBContextProperties; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.sessions.*; public class Demo { public static void main(String[] args) throws Exception { Map properties = new HashMap(1); properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() { @Override public void postLogin(SessionEvent event) { Project project = event.getSession().getProject(); for(ClassDescriptor descriptor : project.getOrderedDescriptors()) { for(DatabaseMapping mapping : descriptor.getMappings()) { mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor())); } } super.preLogin(event); } }); JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties); Unmarshaller unmarshaller = jc.createUnmarshaller(); StringReader xml = new StringReader("Hello World"); Foo foo = (Foo) unmarshaller.unmarshal(xml); } } 

产量

 Thread: 1 - Set value: Hello World on property: bar for object: forum21044956.Foo@37e47e38 

UPDATE

所以这有效,但我有一些问题。 首先,domainObject始终在我的系统中记录为0。 不知道为什么会这样。

我不知道为什么会发生这种情况,可能需要检查要记录的对象的toString()

其次,我无法判断相关属性是否在正在解组的顶级项目或子元素上。 那真的很烦人。

你需要在这里加强逻辑。 根据设置的对象,您应该能够做您想做的事情。

第三,你的解决方案是根据JAXBContext,但我不知道我是否真的想为每个请求创建一个新的上下文。 从头顶上来看,这不是很糟糕吗?

您可以缓存创建的JAXBContext以防止重建它。