Java – Annotation to Define Values for Method Argument

annotationsjavareflection

In a project, I have a task scheduling service, which is allowed to execute certain public methods from other services. Each service decides itself, which methods it want to make available for the task scheduling service by annotating them with @Operation. The task scheduling service simply will scan all given services for this annotation to get aware of methods it is allowed to be configured to execute. This means a user which wants to create a task will have a list of @Operation methods presented.

Let's take the example following example of an update service (one service among a few)

public interface UpdateService extends Service{

    @Operation
    public void update(String updateFileName);

    public List<String> getAvailableUpdateFileNames();

    @Operation
    public void ...

    ...
}

The way the UpdateService interface is declared, a user may create a task over the task service executing the update method. By letting the user freely configure a task with any string is mostly certain erroneous. The interface, in this case, therefore should present a list of predefined values, which can be used with the update method.

As it is, the update service also knows a getter method which reveals all available update. This would be ideal as a source for the predefined values.

That's why I came with the following approach:

public interface UpdateService extends Service{

    @Operation(sourceClass=UpdateService.class,sourceMethod="getAvailableUpdateFileNames")
    public void update(String updateFileName);

    public List<String> getAvailableUpdateFileNames();
}

The @Operation interface is introduced to a couple parameters.
The task service still scans all given service interfaces for @Operation annotations, to present a defined list to the user.

The Task manager also understands the arguments:

  • sourceClass: This class has to contain the source method
  • sourceMethod: This method returns values which can be used for the annotated interface function.

With the help of reflection, the task service executes the sourceMethod and will offer the returned values generically as predefined values for the task configuration of the update(String updateFileName) method in the user interface. The user can select such a value and schedule the update method with this selected value as an argument.

This example is very basic and currently would only work if the annotated method would have 1 argument. Although it would be completely generic and decoupled.

Is there a better way to solve this issue? Or isn't this approach that bad at all?

Best Answer

The design you presented has issues

  • You're using an annotation where an additional method of your Service interface should probably be used instead. Something like an execute() method that can call getAvailableUpdateFileNames and then invoke update. You're already implementing Service. Why design this complex reflection-based system to invoke update?
  • The workaround to the problem you've designed with a parameterized annotation is short-sighted. It may solve your immediate issues with UpdateService, but you'll likely encounter another service that you want to behave slightly differently (like having two parameters as you've acknowledged), and you've set a precedent for introducing annotation parameters to solve these problems.
  • Annotations, while fancy and widely-used, introduce several problems that affect your code's readability, maintainability, and testability. I would not use them when another design is clearly present.

If you want to separate the concerns of updating and the service that executes it, I suggest collaboration.

enter image description here

Where UpdateService.execute is implemented like:

List<String> fileNames = updater.getAvailableFileNames();
updater.update(fileNames.get(0));

This design has the benefit that it can execute any method on any object instead of just those with a single parameter. The concerns are separated, and each piece can be unit-tested independently.