JAXB:在解组时拦截? [英] JAXB: Intercept during unmarshalling?

查看:78
本文介绍了JAXB:在解组时拦截?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我知道我可以用 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,所以如果有
任何可以从这些中获利的东西,那就更好了




< h2>创建自己的EclipseLink AttributeAccessor

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

  import org.eclipse.persistence.exceptions.DescriptorException; 
import org.eclipse.persistence.mappings.AttributeAccessor;

公共类MyAttributeAccessor扩展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()+ - 设置值:+ 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);



创建时利用JAX-RS ContextResolver JAXBContext



由于您处于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 static void main(String [] args)抛出异常{
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);
}

}

输出

 线程:1  - 设置值:物业上的Hello World:对象栏:forum21044956.Foo@37e47e38 






UPDATE



< blockquote>

所以这有效,但我有一些问题。首先,domainObject是
,总是在我的系统中记录为0。不确定为什么会这样。


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


其次,我无法判断该属性是否属于问题在于正在解组或子元素的
顶级项目。那个
实际上非常烦人。


你需要在这里加强逻辑。基于所设置的对象,你应该能够做你想做的事。


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


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


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. 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. 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; 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. 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. We thought we could make this happen using the MetadataSource concept in MOXy, but that does not seem to be possible.

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.

Create your own EclipseLink AttributeAccessor

MOXy (which is a component of EclipseLink) leverages a class called AttributeAccessor to do operations with fields and properties. 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

We can leverage a SessionEventListener to access the underlying metadata to specify your implementation of AttributeAccessor. This is passed in as a property when creating the 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

Since you are in a JAX-RS environment you can leverage a ContextResolver to control how the JAXBContext is created.


Standalone Example

Java Model (Foo)

Below is a sample class where we will use field access (no setters).

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

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.

I have not idea why that is occuring, may need to check the toString() for the object you are logging.

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. Isn't that bad from an overhead perspective?

You can cache the created JAXBContext to prevent rebuilding it.

这篇关于JAXB:在解组时拦截?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆