在此用例中使用Reflection来解决我不允许解决的设计问题是否合理?

时间:2022-09-17 21:48:08

Would the following use case be considered as justified for Reflection?

以下用例是否被视为反映的合理性?

There are bunch of classes generated from XSDs (hundreds currently on project) which represent various Responses.

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

All of these Responses include common response data structure, rather then extending it.

所有这些响应都包括常见的响应数据结构,而不是扩展它。

When event such as timeout happens, i only need to set single String to specific value.

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

If these classes were extending common response structure i could always set this response code without reflection, but this is not the case.

如果这些类扩展了常见的响应结构,我总是可以设置这个响应代码而不进行反射,但事实并非如此。

Therefore i wrote simple utility for my services which uses reflection to get setter method for the String field and invoke it with predefined value. Only known alternative to me would be to have class specific methods which would duplicate code to handle timeout, with the only difference of returned Response class.

因此,我为我的服务编写了一个简单的实用程序,它使用反射来获取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\" !");
    }

}

Relevant fact:

  • this setter method should never change as it would require rework of hundreds of interfaces
  • 这个setter方法永远不会改变,因为它需要返工数百个接口

Could somebody point out if there are some pitfalls i have missed or if there is alternate solution for reflection which would achieve the same result ?

有人可能会指出我是否遗漏了一些陷阱,或者是否存在可以实现相同结果的反射解决方案?

Edit: I simply have no authority to get any changes done on XSDs, so any solution would have to be done locally. There should be no problems with serializing such objects, as they are shared between components.

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

5 个解决方案

#1


2  

I'd try an alternate solution for generating your classes from the xml schema, in preference over reflection.

我尝试了一种替代解决方案,用于从xml架构生成类,而不是反射。

You can supply xjc with a custom binding like this:

您可以为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>

and implement you XmlSuperClass like this:

并实现你像这样的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;
    }
}

Invoking xjc like this:

像这样调用xjc:

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

will generate bound classes like:

将生成绑定类,如:

@XmlRootElement(name = "whatever")
public class Whatever extends XmlSuperClass {
    @XmlElement(required = true)
    protected String responseCode;    // shadowing
    public void setResponseCode(String...) //overriding
}

#2


4  

Firstly there is a standard, normal everyday solution as suggested by @kutschkem, specifically: declare an interface that only contains this one setter method and implement that interface in every class which requires it. This uses standard polymorphism to do exactly what you need.

首先,@ kutschkem建议使用标准的,日常的日常解决方案,具体来说:声明一个只包含这一个setter方法的接口,并在每个需要它的类中实现该接口。这使用标准多态来完全按照您的需要进行操作。

I understand this requires changing the definition of a lot of classes (but the change is trivial - just add 'implements MytimeoutThing' to every class) - even for 1000's of classes this seems a fairly easy fix for me.

我理解这需要更改很多类的定义(但是更改很简单 - 只需在每个类中添加'implements MytimeoutThing') - 即使对于1000个类,这对我来说似乎是一个相当容易的修复。

I think that there are real problems with reflection:

我认为反思存在实际问题:

  1. You are creating a secret interface to all your classes that must be supported but there is no contract for this interface - when a new developer wants to add a new class he has to magically know about the name and signature for this method - if he gets it wrong the code fails at run-time as the compiler doesn't know about this contract. (So something as simple as misspelling the setters name isn;t picked up by the compiler)

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

  2. It's ugly, hidden and not clearly part of any particular part of the software. A dev maintaining ANY of these classes will find this function (the setter) notice that it is never being called and just delete it - after all no code in the rest of the project refers to that setter so it obviously isn't needed.

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

  3. A whole lot of static analysis tools won;t work - for example in most IDE's you can establish all the places that specific function is called from and all the places that a specific function calls - obviously this kind of functionality is not available if you use reflection. In a project with hundreds of near identical classes I would hate to loose this facility.

    大量的静态分析工具赢得了工作 - 例如在大多数IDE中,您可以建立调用特定函数的所有位置以及特定函数调用的所有位置 - 显然,如果您使用此类功能,则无法使用反射。在一个有数百个几乎相同类的项目中,我不愿意放弃这个设施。

#3


3  

The actual problem you are facing is that you have a lot of classes that should share a common abstraction between them (inheriting the same class or implementing the same interface), but they don't. Trying to keep it that way and designing around would basically be taking care of the symptoms instead of the cause and will likely cause more problems in the future.

你面临的实际问题是你有很多类应该在它们之间共享一个共同的抽象(继承相同的类或实现相同的接口),但它们没有。试图保持这种方式和设计周围基本上将照顾症状而不是原因,并可能在未来导致更多的问题。

I suggest to solve the root cause instead by making all the generated classes have a common interface / superclass. You do not have to do this by hand - as they are all generated it should be possible to change them automatically without much struggle.

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

#4


1  

To become objective again:

再次成为客观:

There is no mention of the object instantiation. If you would have postulated a constructor with a String code parameter:

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

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

Would the save critics arise? To a lower extent, as parametrized constructors are even more indeterminate. Let's await the voting here.

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

Interface is better looking though.

接口看起来更好看。

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;

#5


1  

Okay, so let's suppose you've got this generated code:

好的,我们假设您已经生成了这个代码:

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

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

What you need to do then is write an interface:

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

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

Then you need to write a script that goes through all the generated code files and simply adds implements ResponseCodeAware after every class definition. (That's assuming that there are no interfaces implemented already, in that case you have to play around a bit with the string processing.)

然后,您需要编写一个遍历所有生成的代码文件的脚本,并在每个类定义后添加实现ResponseCodeAware。 (假设已经没有实现接口,在这种情况下你必须使用字符串处理。)

So your generated and post-processed classes will now look like this:

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

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

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

Note that nothing else changed, so code that doesn't know about your interface (including serialization) should work exactly the same.

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

And finally we can rewrite your method:

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

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

As you can see, unfortunately we still have to use reflection to create our object, and unless we also create some kind of factory, that will stay that way. But code generation can help you here too, you can build up a factory class in parallel to marking the classes with the interface.

正如您所看到的,遗憾的是我们仍然必须使用反射来创建我们的对象,除非我们也创建某种工厂,否则将保持这种状态。但代码生成也可以帮助您,您可以并行构建工厂类以使用接口标记类。

#1


2  

I'd try an alternate solution for generating your classes from the xml schema, in preference over reflection.

我尝试了一种替代解决方案,用于从xml架构生成类,而不是反射。

You can supply xjc with a custom binding like this:

您可以为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>

and implement you XmlSuperClass like this:

并实现你像这样的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;
    }
}

Invoking xjc like this:

像这样调用xjc:

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

will generate bound classes like:

将生成绑定类,如:

@XmlRootElement(name = "whatever")
public class Whatever extends XmlSuperClass {
    @XmlElement(required = true)
    protected String responseCode;    // shadowing
    public void setResponseCode(String...) //overriding
}

#2


4  

Firstly there is a standard, normal everyday solution as suggested by @kutschkem, specifically: declare an interface that only contains this one setter method and implement that interface in every class which requires it. This uses standard polymorphism to do exactly what you need.

首先,@ kutschkem建议使用标准的,日常的日常解决方案,具体来说:声明一个只包含这一个setter方法的接口,并在每个需要它的类中实现该接口。这使用标准多态来完全按照您的需要进行操作。

I understand this requires changing the definition of a lot of classes (but the change is trivial - just add 'implements MytimeoutThing' to every class) - even for 1000's of classes this seems a fairly easy fix for me.

我理解这需要更改很多类的定义(但是更改很简单 - 只需在每个类中添加'implements MytimeoutThing') - 即使对于1000个类,这对我来说似乎是一个相当容易的修复。

I think that there are real problems with reflection:

我认为反思存在实际问题:

  1. You are creating a secret interface to all your classes that must be supported but there is no contract for this interface - when a new developer wants to add a new class he has to magically know about the name and signature for this method - if he gets it wrong the code fails at run-time as the compiler doesn't know about this contract. (So something as simple as misspelling the setters name isn;t picked up by the compiler)

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

  2. It's ugly, hidden and not clearly part of any particular part of the software. A dev maintaining ANY of these classes will find this function (the setter) notice that it is never being called and just delete it - after all no code in the rest of the project refers to that setter so it obviously isn't needed.

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

  3. A whole lot of static analysis tools won;t work - for example in most IDE's you can establish all the places that specific function is called from and all the places that a specific function calls - obviously this kind of functionality is not available if you use reflection. In a project with hundreds of near identical classes I would hate to loose this facility.

    大量的静态分析工具赢得了工作 - 例如在大多数IDE中,您可以建立调用特定函数的所有位置以及特定函数调用的所有位置 - 显然,如果您使用此类功能,则无法使用反射。在一个有数百个几乎相同类的项目中,我不愿意放弃这个设施。

#3


3  

The actual problem you are facing is that you have a lot of classes that should share a common abstraction between them (inheriting the same class or implementing the same interface), but they don't. Trying to keep it that way and designing around would basically be taking care of the symptoms instead of the cause and will likely cause more problems in the future.

你面临的实际问题是你有很多类应该在它们之间共享一个共同的抽象(继承相同的类或实现相同的接口),但它们没有。试图保持这种方式和设计周围基本上将照顾症状而不是原因,并可能在未来导致更多的问题。

I suggest to solve the root cause instead by making all the generated classes have a common interface / superclass. You do not have to do this by hand - as they are all generated it should be possible to change them automatically without much struggle.

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

#4


1  

To become objective again:

再次成为客观:

There is no mention of the object instantiation. If you would have postulated a constructor with a String code parameter:

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

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

Would the save critics arise? To a lower extent, as parametrized constructors are even more indeterminate. Let's await the voting here.

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

Interface is better looking though.

接口看起来更好看。

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;

#5


1  

Okay, so let's suppose you've got this generated code:

好的,我们假设您已经生成了这个代码:

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

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

What you need to do then is write an interface:

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

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

Then you need to write a script that goes through all the generated code files and simply adds implements ResponseCodeAware after every class definition. (That's assuming that there are no interfaces implemented already, in that case you have to play around a bit with the string processing.)

然后,您需要编写一个遍历所有生成的代码文件的脚本,并在每个类定义后添加实现ResponseCodeAware。 (假设已经没有实现接口,在这种情况下你必须使用字符串处理。)

So your generated and post-processed classes will now look like this:

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

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

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

Note that nothing else changed, so code that doesn't know about your interface (including serialization) should work exactly the same.

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

And finally we can rewrite your method:

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

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

As you can see, unfortunately we still have to use reflection to create our object, and unless we also create some kind of factory, that will stay that way. But code generation can help you here too, you can build up a factory class in parallel to marking the classes with the interface.

正如您所看到的,遗憾的是我们仍然必须使用反射来创建我们的对象,除非我们也创建某种工厂,否则将保持这种状态。但代码生成也可以帮助您,您可以并行构建工厂类以使用接口标记类。