Java CXF:处理不同包或名称空间下的通用对象的最佳方式是什么?

Java CXF: What is the best way to handle Common Objects under different packages or namespaces?

我们通过使用 Wsdl2Java 将他们的模式和端点转换为 Java 他们提供的三种不同的网络服务,从而与第 3 方网络服务集成。

这个特定的提供者使用了很多相同的对象(想想代表地址、金钱、体重等的对象),但是,在他们无限的智慧中,他们决定为每个网络服务创建一个不同的名称空间并复制每个模式的定义。结果是 CXF 集成的 类 输出如下:

com.thirdpartyguys.api.firstApi.Money

com.thirdpartyguys.api.secondApi.Money

com.thirdpartyguys.api.thirdApi.Money

将我们的数据转换成他们的数据可能涉及很多业务逻辑,因此,我们必须定义代码,为每个单独的 Web 服务创建一式三份的对象 API。

为了克服这个问题,我创建了一个这样定义的接口:

import org.apache.commons.beanutils.BeanUtils;
public interface CommonObjectInterface<A, R, S> {
    
    A toFirstApi();
    
    R toSecondApi();
    
    S toThirdApi();
    
    default Object doTransform(Object destination, Object source) {
        try {
            BeanUtils.copyProperties(destination, source);
        } catch (Exception e) {
            throw new RuntimeException("Fatal error transforming Object", e);
        }
        return destination;
    }
    
}

然后,您可以让每个公共对象实现接口,定义自己的构造函数,流畅 API 等,并调用 toXXX() 方法为相应的 API.

现在大多数实现 类 的工作方式是在本地保留一个 API 的副本,在其上设置数据,然后使用 [=30] 将其转换为正确的 API =]doTransform() 方法,其默认形式使用 Apache Commons BeanUtils.copyProperties() 方法。

它比在三个不同的地方存在相同的代码更优雅,但不是很多!有很多样板文件,即使这不会受到太多打击,但效率不高。

我想从社区获得反馈,了解这是否是个好主意,或者是否有更好的方法。一个类似的问题was asked years ago here,但不知道问了以后有没有更好的解决办法。我想最好的办法是配置 wsdl2Java 以允许在运行时设置命名空间,但从我最初的研究来看,这似乎是不可能的。

这个问题的解决方案是针对这个具体情况的:

1) 在不同命名空间中具有相同对象的 Web 服务提供者 2) 使用 wsdl2Java 或一些底层 Apache CXF 技术生成用于编写客户端的 Web 工件。

这是一个边缘案例,所以我不确定这对社区有多大帮助,但诀窍是考虑 copyProperties 方法不起作用的一些情况。在这种情况下,我使用 Spring 的 BeanUtils 和 BeanWrapper 类,尽管我确信这也适用于 Apache。以下代码可以解决问题:

final String TARGET_PACKAGE = "com.thirdpartyguys.api";
public Object doTransform(Object destination, Object source) {
    /*
     * This will copy all properties for the same data type for which there is a getter method in
     * source, and a setter method in destination
     */
    BeanUtils.copyProperties(source, destination);

    BeanWrapper sourceWrapper = new BeanWrapperImpl(source);
    for(PropertyDescriptor p : sourceWrapper.getPropertyDescriptors()) {
        /*
         * Properties that are references to other schema objects are identical in structure, but have
         * different packages. We need to copy these separately
         */
        if(p.getPropertyType().getPackage().getName().startsWith(TARGET_PACKAGE)) {
            try {
                commonPropertyCopy(destination, source, p);
            } catch (Exception e) {
                throw new RuntimeException("Fatal error creating Data", e);
            }
        }
        /*
         * Properties that reference list don't create setters according to the Apache CXF
         * convention. We have to call the get method and addAll()
         */
        else if(Collection.class.isAssignableFrom(p.getPropertyType())) {
            try {
                collectionCopy(destination, source, p);
            } catch (Exception e) {
                throw new RuntimeException("Fatal error creating Data", e);
            }
        }
    }
    return destination;
}

private void collectionCopy(Object destination, Object source, PropertyDescriptor sourceProperty) throws Exception {
    BeanWrapper destWrapper= new BeanWrapperImpl(destination);
    PropertyDescriptor destProperty = destWrapper.getPropertyDescriptor(sourceProperty.getName());
    Collection<?> sourceCollection = (Collection<?>) sourceProperty.getReadMethod().invoke(source);
    Collection<Object> destCollection = (Collection<Object>) destProperty.getReadMethod().invoke(destination);
    destCollection.addAll(sourceCollection);

}

private void commonPropertyCopy(Object destination, Object source, PropertyDescriptor sourceProperty) throws Exception {
    if(sourceProperty.getPropertyType().isEnum()) {
        instantiateEnum(destination, source, sourceProperty);
    }
    else {
        instantiateObject(destination, source, sourceProperty);
    }
}

private void instantiateEnum(Object destination, Object source, PropertyDescriptor sourceProperty) throws Exception {
    BeanWrapper destWrapper= new BeanWrapperImpl(destination);
    Enum<?> sourceEnum = (Enum<?>) sourceProperty.getReadMethod().invoke(source);
    PropertyDescriptor destProperty = destWrapper.getPropertyDescriptor(sourceProperty.getName());

    Object enumValue = Enum.valueOf(destProperty.getPropertyType().asSubclass(Enum.class), sourceEnum.name());
    destProperty.getWriteMethod().invoke(destination, enumValue);
}

private void instantiateObject(Object destination, Object source, PropertyDescriptor sourceProperty) throws Exception {
    Object subObj = sourceProperty.getReadMethod().invoke(source);
    if(subObj!=null) {
        BeanWrapper destWrapper = new BeanWrapperImpl(destination);
        String subObjName = sourceProperty.getName();
        PropertyDescriptor destProperty = destWrapper.getPropertyDescriptor(subObjName);
        Class<?> propertyType = destProperty.getReadMethod().getReturnType();
        Object subObjCopy = propertyType.getConstructor().newInstance();
        doTransform(subObjCopy, subObj);
        destProperty.getWriteMethod().invoke(destination, subObjCopy);
    }
}

instantiateObject 用于从不同的包中创建 "identical" 对象的新实例。这也适用于枚举类型并需要其自己的方法,因此需要实现 instantiateEnum。最后,默认的 CXF 实现没有为列表提供 setter 方法。我们在collectionCopy中处理这种情况。