Is it warranted to use Reflection in this use case to solve a workaround problem that I cannot fix?

Is the use case justified for Reflection to be considered as follows?

There are many classes generated from XSD (hundreds currently in the project) that represent various answers.

All of these responses include a common response data structure and then expand on it.

When an event such as timeout occurs, I only need to set one line for a specific value.

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

So I wrote a simple utility for my services that uses reflection to get a setter for a String field and call it with a predefined value. The only known alternative to me would be to have class-specific methods that duplicate the code to handle the timeout with the only difference being the Response class being returned.

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 reworking hundreds of interfaces.

Can anyone point out if there are any pitfalls that I missed, or if there is an alternative reflection solution that achieves the same result?

Edit . I just don't have the authority to make any changes to the XSD, so any solution has to be done locally. There shouldn't be any problem with serializing such objects as they are shared between components.

+3


source to share


5 answers


I would try an alternative solution for generating your classes from the xml schema, preferring reflection.

You can specify xjc with custom binding like:

<?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 the XmlSuperClass like this:

@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;
    }
}

      



Calling xjc like this:

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

      

will generate related classes like:

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

      

+2


source


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

I realize this requires changing the definition of many classes (but changing is trivial - just add "implements MytimeoutThing" for each class) - even for 1000 classes this seems like a pretty easy solution to me.



I think there are real problems with reflection:

  • You create a secret interface for all your classes that need to be maintained, but there is no contract for that interface - when a new developer wants to add a new class, he has to magically learn about the name and signature for this method - if he is wrong, the code does not work at runtime as the compiler is unaware of this contract. (So ​​something as simple as the spelling error of the setter name isn; t obtained by the compiler)

  • It is an ugly, hidden and incomprehensible piece of any particular piece of software. Dev supporting ANY of these classes will find that this function (the setter) notices that it is never called and simply removes it - after all, no code in the rest of the project references this setter, so it is clearly not needed.

  • Many static analysis tools have won, i.e. work - for example, in most IDEs you can set all the places from which a certain function is called and all the places that a certain function calls - obviously such functionality is not available if you are using reflection. In a project with hundreds of close identical classes, I would not want to lose this object.

+4


source


The real problem you are facing is that you have many classes that need to share a common abstraction between them (either inheriting the same class or implementing the same interface), but they don't. Trying to keep it that way and constructing it around will mostly care about the symptoms, not the cause, and will likely cause more problems in the future.

I suggest instead to solve the root cause if all generated classes have a common interface / superclass. You don't have to do it manually - since they are all created, they can be automatically changed without much struggle.

+3


source


To become objective again:

No mention of an object instance. If you were to postulate a constructor with a String code parameter:

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

      

Will there be critics of salvation? To a lesser extent, because parameterized constructors are even more undefined. Wait for voting here.

The interface looks better.

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;

      

+1


source


Okay, let's assume you have this generated code:

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

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

      

What you need to do 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 just adds implements ResponseCodeAware

after each class definition. (Assuming the interfaces are no longer implemented, in which case you have to play around with string handling a bit.)

So, your generated and post-processed classes will 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 has changed, so code that is not aware of your interface (including serialization) should work exactly the same.

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 need to use reflection to create our object, and if we don't create some kind of factory as well, it will remain that way. But code generation can also help you here, you can create a factory class in parallel with labeling classes with an interface.

+1


source







All Articles