在此用例中使用反射来解决我不允许修复的设计问题是否合理?
Is it justified to use Reflection in this use case to workaround design issue that i am not allowed to fix?
以下用例是否被认为有理由进行反思?
从 XSD(目前项目中有数百个)生成了一堆 classes,代表各种响应。
所有这些响应都包含通用响应数据结构,而不是对其进行扩展。
当超时等事件发生时,我只需要将单个字符串设置为特定值。
如果这些 classes 扩展了通用响应结构,我总是可以设置此响应代码而无需反射,但事实并非如此。
因此我为我的服务编写了简单的实用程序,它使用反射来获取 String 字段的 setter 方法并使用预定义的值调用它。
我唯一知道的替代方法是使用 class 特定方法,这些方法会复制代码来处理超时,唯一的区别是返回的响应 class.
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 进行任何更改,因此任何解决方案都必须在本地完成。序列化此类对象应该没有问题,因为它们在组件之间共享。
你面临的实际问题是你有很多 classes 应该在它们之间共享一个共同的抽象(继承相同的 class 或实现相同的接口),但是它们不。试图保持这种状态并围绕设计基本上会处理症状而不是原因,并且将来可能会导致更多问题。
我建议通过使所有生成的 classes 具有公共接口/superclass 来解决根本原因。您不必手动执行此操作 - 因为它们都是生成的,所以应该可以毫不费力地自动更改它们。
首先,@kutschkem 建议有一个标准的、正常的日常解决方案,具体来说:声明一个只包含这个 setter 方法的接口,并在每个需要它的 class 中实现该接口.这使用标准多态性来完全满足您的需求。
我知道这需要更改很多 classes 的定义(但更改是微不足道的 - 只需将 'implements MytimeoutThing' 添加到每个 class) - 即使是 1000 个 class是的,这对我来说似乎很容易解决。
我认为反射确实存在问题:
您正在为所有必须支持的 classes 创建一个秘密接口,但该接口没有合同 - 当新开发人员想要添加新的 class 他必须神奇地知道这个方法的名称和签名——如果他弄错了,代码会在 运行-time 失败,因为编译器不知道本合同。 (因此,像拼写错误 setter 的名字这样简单的事情不会被编译器拾取)
它丑陋、隐藏并且不属于软件的任何特定部分。维护这些 classes 中的任何一个的开发人员会发现这个函数(setter)注意到它永远不会被调用并且只是删除它 - 毕竟项目其余部分中没有代码引用那个 setter 所以显然不需要。
很多静态分析工具都不起作用——例如,在大多数 IDE 中,您可以确定调用特定函数的所有位置以及调用特定函数的所有位置一个特定的函数调用——显然,如果你使用反射,这种功能是不可用的。在一个有数百个几乎相同的 classes 的项目中,我不想失去这个设施。
再次成为objective:
没有提到对象实例化。
如果您假定构造函数带有字符串代码参数:
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;
好的,让我们假设您有这个生成的代码:
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);
}
然后您需要编写一个脚本遍历所有生成的代码文件,并在每个 class 定义之后简单地添加 implements ResponseCodeAware
。 (假设还没有实现任何接口,在这种情况下,您必须稍微尝试一下字符串处理。)
所以您生成的和 post 处理的 classes 现在看起来像这样:
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.");
}
}
如您所见,不幸的是,我们仍然必须使用反射来创建我们的对象,除非我们也创建某种工厂,否则它将保持这种状态。但是代码生成也可以在这里帮助你,你可以建立一个工厂 class 并行地用接口标记 classes。
我会尝试使用替代解决方案从 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
}
以下用例是否被认为有理由进行反思?
从 XSD(目前项目中有数百个)生成了一堆 classes,代表各种响应。
所有这些响应都包含通用响应数据结构,而不是对其进行扩展。
当超时等事件发生时,我只需要将单个字符串设置为特定值。
如果这些 classes 扩展了通用响应结构,我总是可以设置此响应代码而无需反射,但事实并非如此。
因此我为我的服务编写了简单的实用程序,它使用反射来获取 String 字段的 setter 方法并使用预定义的值调用它。 我唯一知道的替代方法是使用 class 特定方法,这些方法会复制代码来处理超时,唯一的区别是返回的响应 class.
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 进行任何更改,因此任何解决方案都必须在本地完成。序列化此类对象应该没有问题,因为它们在组件之间共享。
你面临的实际问题是你有很多 classes 应该在它们之间共享一个共同的抽象(继承相同的 class 或实现相同的接口),但是它们不。试图保持这种状态并围绕设计基本上会处理症状而不是原因,并且将来可能会导致更多问题。
我建议通过使所有生成的 classes 具有公共接口/superclass 来解决根本原因。您不必手动执行此操作 - 因为它们都是生成的,所以应该可以毫不费力地自动更改它们。
首先,@kutschkem 建议有一个标准的、正常的日常解决方案,具体来说:声明一个只包含这个 setter 方法的接口,并在每个需要它的 class 中实现该接口.这使用标准多态性来完全满足您的需求。
我知道这需要更改很多 classes 的定义(但更改是微不足道的 - 只需将 'implements MytimeoutThing' 添加到每个 class) - 即使是 1000 个 class是的,这对我来说似乎很容易解决。
我认为反射确实存在问题:
您正在为所有必须支持的 classes 创建一个秘密接口,但该接口没有合同 - 当新开发人员想要添加新的 class 他必须神奇地知道这个方法的名称和签名——如果他弄错了,代码会在 运行-time 失败,因为编译器不知道本合同。 (因此,像拼写错误 setter 的名字这样简单的事情不会被编译器拾取)
它丑陋、隐藏并且不属于软件的任何特定部分。维护这些 classes 中的任何一个的开发人员会发现这个函数(setter)注意到它永远不会被调用并且只是删除它 - 毕竟项目其余部分中没有代码引用那个 setter 所以显然不需要。
很多静态分析工具都不起作用——例如,在大多数 IDE 中,您可以确定调用特定函数的所有位置以及调用特定函数的所有位置一个特定的函数调用——显然,如果你使用反射,这种功能是不可用的。在一个有数百个几乎相同的 classes 的项目中,我不想失去这个设施。
再次成为objective:
没有提到对象实例化。 如果您假定构造函数带有字符串代码参数:
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;
好的,让我们假设您有这个生成的代码:
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);
}
然后您需要编写一个脚本遍历所有生成的代码文件,并在每个 class 定义之后简单地添加 implements ResponseCodeAware
。 (假设还没有实现任何接口,在这种情况下,您必须稍微尝试一下字符串处理。)
所以您生成的和 post 处理的 classes 现在看起来像这样:
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.");
}
}
如您所见,不幸的是,我们仍然必须使用反射来创建我们的对象,除非我们也创建某种工厂,否则它将保持这种状态。但是代码生成也可以在这里帮助你,你可以建立一个工厂 class 并行地用接口标记 classes。
我会尝试使用替代解决方案从 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
}