java – JAXB:解组期间的拦截?

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

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

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

有人有主意吗?

最佳答案

My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there’s
anything that can be leveraged from any of those, that’s all the
better.

创建自己的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<String, Object> properties = new HashMap<String, Object>(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的创建方式.

> http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html

独立示例

Java模型(Foo)

下面是一个示例类,我们将使用字段访问(没有setter).

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<String, Object> properties = new HashMap<String, Object>(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("<foo><bar>Hello World</bar></foo>");
        Foo foo = (Foo) unmarshaller.unmarshal(xml);
    }

}

产量

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

UPDATE

So this works, but I have a few issues. First, the domainObject is
always logging as 0 in my system. Not sure why that’s occurring.

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

Second, I am not able to tell if the property in question is on the
top-level item that is being unmarshalled or on a sub-element. That’s
actually quite annoying.

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

Third, your solution is per JAXBContext, but I don’t know if I really
want to create a new context for every request. Isn’t that bad from an
overhead perspective?

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

点赞