3

以下用例是否被认为是反射的合理性?

有一堆从 XSD 生成的类(目前项目中有数百个)代表各种响应。

所有这些响应都包含通用响应数据结构,而不是对其进行扩展。

当发生超时等事件时,我只需要将单个字符串设置为特定值。

如果这些类正在扩展通用响应结构,我总是可以设置此响应代码而无需反射,但事实并非如此。

因此,我为我的服务编写了简单的实用程序,它使用反射来获取 String 字段的 setter 方法并使用预定义的值调用它。我唯一知道的替代方法是使用特定于类的方法来复制代码来处理超时,唯一的区别是返回的 Response 类。

protected T handleTimeout(Class<T> timeoutClass) {
    try {
        T timeout = timeoutClass.newInstance();
        Method setCode = timeoutClass.getDeclaredMethod(SET_RESPONSE_CODE, String.class);
        setCode.invoke(timeout, Response.TIMEOUT.getCode());
        return timeout;
    } catch (InstantiationException | IllegalAccessException  | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
        throw new RuntimeException("Response classes must have field: \"code\" !");
    }

}

相关事实:

  • 这个 setter 方法永远不应该改变,因为它需要对数百个接口进行返工

有人可以指出我是否遗漏了一些陷阱,或者是否有替代的反射解决方案可以达到相同的结果?

编辑:我根本没有权限在 XSD 上完成任何更改,因此任何解决方案都必须在本地完成。序列化此类对象应该没有问题,因为它们在组件之间共享。

4

5 回答 5

4

首先,@kutschkem 建议有一个标准的、正常的日常解决方案,特别是:声明一个只包含这个 setter 方法的接口,并在每个需要它的类中实现该接口。这使用标准多态性来完全满足您的需求。

我知道这需要更改很多类的定义(但更改是微不足道的——只需在每个类中添加“实现 MytimeoutThing”)——即使对于 1000 个类,这对我来说似乎也很容易解决。

我认为反射存在真正的问题:

  1. 您正在为所有必须支持的类创建一个秘密接口,但该接口没有合同 - 当新开发人员想要添加一个新类时,他必须神奇地知道该方法的名称和签名 - 如果他得到错误的是代码在运行时失败,因为编译器不知道这个合同。(所以像错误拼写 setter 名称这样简单的事情;编译器不会拾取)

  2. 它是丑陋的,隐藏的,并且不是软件任何特定部分的明确部分。维护这些类中的任何一个的开发人员会发现这个函数(setter)注意到它永远不会被调用并删除它 - 毕竟项目其余部分中没有代码引用该 setter,因此显然不需要它。

  3. 很多静态分析工具都不起作用——例如,在大多数 IDE 中,你可以建立调用特定函数的所有位置以及调用特定函数的所有位置——显然,如果你使用这种功能是不可用的反射。在一个有数百个几乎相同的类的项目中,我不想放弃这个设施。

于 2015-06-02T12:12:41.990 回答
3

您面临的实际问题是您有很多类应该在它们之间共享一个公共抽象(继承相同的类或实现相同的接口),但它们没有。试图保持这种状态并围绕它进行设计基本上会处理症状而不是原因,并且将来可能会导致更多问题。

我建议通过使所有生成的类都具有公共接口/超类来解决根本原因。您不必手动执行此操作 - 因为它们都是生成的,应该可以自动更改它们而无需太多努力。

于 2015-06-02T12:09:18.227 回答
2

我会尝试另一种解决方案来从 xml 模式生成你的类,而不是反射。

您可以为 xjc 提供这样的自定义绑定:

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
      xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
      xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
      xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd" version="2.1">
    <globalBindings>
        <xjc:superClass name="XmlSuperClass" />
    </globalBindings>
</bindings>

并像这样实现你的 XmlSuperClass:

@XmlTransient                     // to prevent that the shadowed responseCode be marshalled
public class XmlSuperClass {
    private String responseCode;         // this will be shadowed
    public String getResponseCode() {    // this will be overridden
        return responseCode;
    }
    public void setResponseCode(String value) { //overridden too
        this.responseCode = value;
    }
}

像这样调用 xjc:

xjc -extension -b <yourbinding.xjb> -cp <XmlSuperClass> <xmlschemas.xsd...>

将生成绑定类,如:

@XmlRootElement(name = "whatever")
public class Whatever extends XmlSuperClass {
    @XmlElement(required = true)
    protected String responseCode;    // shadowing
    public void setResponseCode(String...) //overriding
}
于 2015-06-02T14:11:14.307 回答
1

再次变得客观:

没有提到对象实例化。如果您假设一个带有字符串代码参数的构造函数:

T timeout = timeoutClass.getConstructor(String.class)
    .newInstance(Response.TIMEOUT.getCode());

拯救批评者会出现吗?在较低程度上,参数化构造函数甚至更加不确定。让我们在这里等待投票。

不过界面更好看。

interface CodeSetter {
   void setCode(String code);
}

protected <T extends CodeSetter> handleTimeout(Class<T> timeoutClass) {
    try {
        T timeout = timeoutClass.newInstance();
        timeout.setCode(Response.TIMEOUT.getCode());
        return timeout;
于 2015-06-02T12:38:19.403 回答
1

好的,让我们假设你有这个生成的代码:

public class Response1 {
   public void setResponseCode(int code) {...}
}

public class Response2 {
   public void setResponseCode(int code) {...}
}

然后你需要做的是编写一个接口:

public interface ResponseCodeAware { //sorry for the poor name
   public void setResponseCode(int code);
} 

然后,您需要编写一个脚本来遍历所有生成的代码文件,并implements ResponseCodeAware在每个类定义之后简单地添加。(假设还没有实现接口,在这种情况下,您必须对字符串处理进行一些尝试。)

所以你生成的和后处理的类现在看起来像这样:

public class Response1 implements ResponseCodeAware {
   public void setResponseCode(int code) {...}
}

public class Response2 implements ResponseCodeAware {
   public void setResponseCode(int code) {...}
}

请注意,没有其他任何更改,因此不了解您的接口(包括序列化)的代码应该完全相同。

最后我们可以重写你的方法:

protected T handleTimeout(Class<T extends ResponseCodeAware> timeoutClass) {
    try {
        T timeout = timeoutClass.newInstance();
        timeout.setResponseCode( Response.TIMEOUT.getCode() );
        return timeout;
    } catch (InstantiationException | IllegalAccessException  | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
        throw new RuntimeException("Response class couldn't be instantiated.");
    }
}

如您所见,不幸的是,我们仍然必须使用反射来创建对象,除非我们还创建某种工厂,否则将保持这种状态。但是代码生成在这里也可以为您提供帮助,您可以在使用接口标记类的同时构建一个工厂类。

于 2015-06-02T12:39:28.890 回答