简体   繁体   English

JAXB:在解组时拦截?

[英]JAXB: Intercept during unmarshalling?

I've got a typical web service using JAX-RS and JAXB, and upon unmarshalling I would like to know which setters were explicitly called by JAXB. 我有一个使用JAX-RS和JAXB的典型Web服务,在解组时我想知道JAXB明确调用了哪些setter。 This effectively lets me know which elements were included in the document provided by the caller. 这有效地让我知道调用者提供的文档中包含哪些元素。

I know I can probably solve this with an XmlAdapter , but I have a lot of classes in a number of different packages, and I don't want to create adapters for each and every one of them. 我知道我可以使用XmlAdapter解决这个XmlAdapter ,但是我在很多不同的包中都有很多类,我不想为每一个创建适配器。 Nor do I want to put hooks into each and every setter. 我也不想把钩子放进每一个二传手。 I would like a general solution if possible. 如果可能,我想要一个通用的解决方案。 Note that all of my classes are setup to use getters and setters; 请注意,我的所有类都设置为使用getter和setter; none of them use fields for the access type. 它们都不使用访问类型的字段。

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. 我的服务使用Jersey 2.4,Spring 3.2和MOXy 2.5.1,所以如果有任何可以利用其中任何一个的东西,那就更好了。 Our original thought was we could dynamically create a factory class (akin to what @XmlType supports) that would return a proxy object that would intercept the setters. 我们最初的想法是我们可以动态创建一个工厂类(类似于@XmlType支持的),它将返回一个拦截setter的代理对象。 We thought we could make this happen using the MetadataSource concept in MOXy, but that does not seem to be possible. 我们认为我们可以使用MOXy中的MetadataSource概念来实现这一点,但这似乎不可能。

Anyone have any ideas? 有人有主意吗?

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. 我的服务使用Jersey 2.4,Spring 3.2和MOXy 2.5.1,所以如果有任何可以利用其中任何一个的东西,那就更好了。

Create your own EclipseLink AttributeAccessor 创建自己的EclipseLink AttributeAccessor

MOXy (which is a component of EclipseLink) leverages a class called AttributeAccessor to do operations with fields and properties. MOXy(EclipseLink的一个组件)利用名为AttributeAccessor的类对字段和属性进行操作。 You could wrap this class to capture all the information that you need. 您可以包装此类以捕获所需的所有信息。

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);
    }

}

Tell MOXy to use your AttributeAccessor 告诉MOXy使用您的AttributeAccessor

We can leverage a SessionEventListener to access the underlying metadata to specify your implementation of AttributeAccessor . 我们可以利用SessionEventListener来访问底层元数据,以指定AttributeAccessor的实现。 This is passed in as a property when creating the JAXBContext . 在创建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);

Leverage a JAX-RS ContextResolver when Creating the JAXBContext 在创建JAXBContext时利用JAX-RS ContextResolver

Since you are in a JAX-RS environment you can leverage a ContextResolver to control how the JAXBContext is created. 由于您处于JAX-RS环境中,因此可以利用ContextResolver控制JAXBContext的创建方式。


Standalone Example 独立示例

Java Model (Foo) Java模型(Foo)

Below is a sample class where we will use field access (no setters). 下面是一个示例类,我们将使用字段访问(没有setter)。

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {

    private String bar;
    private String baz;

}

Demo 演示

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);
    }

}

Output 产量

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

UPDATE UPDATE

So this works, but I have a few issues. 所以这有效,但我有一些问题。 First, the domainObject is always logging as 0 in my system. 首先,domainObject始终在我的系统中记录为0。 Not sure why that's occurring. 不知道为什么会这样。

I have not idea why that is occuring, may need to check the toString() for the object you are logging. 我不知道为什么会发生这种情况,可能需要检查要记录的对象的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. 那真的很烦人。

You will need to beef up the logic here. 你需要在这里加强逻辑。 Based on the objects being set you should be able to do what you want. 根据设置的对象,您应该能够做您想做的事情。

Third, your solution is per JAXBContext, but I don't know if I really want to create a new context for every request. 第三,你的解决方案是根据JAXBContext,但我不知道我是否真的想为每个请求创建一个新的上下文。 Isn't that bad from an overhead perspective? 从头顶上来看,这不是很糟糕吗?

You can cache the created JAXBContext to prevent rebuilding it. 您可以缓存创建的JAXBContext以防止重建它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM