1

I'm working on a framework in which we have the concept of a task. The hope is that lots of people will write tasks, and to do so they will simply need to implement the Task interface. The tasks need to be triggered via their name, meaning there needs to be a central registry of task-name->task-class somewhere. Currently I'm using an Enum to do this, but this requires task implementers to add code into the Enum for every task they implement, which is gross.

Another option I've come up with is to search via reflection for all classes that implement the Task interface and register them from there. This also strikes me as gross and I feel there must be a better way.

A static initializer would be perfect if it didn't require the class be referenced at least once before it ran.

Annotations are another option, but they require either more reflection work, or changing the build process if we were to do compile time code-gen to make the dispatcher.

Any simpler ideas?


A simple version of what I'm doing now is below, to give an idea of the control flow I want (smashed into a single class for simplicity):

class Disp {
    interface Task {
        public String doTask();
    }

    static class HelloTask implements Task {
        public String doTask() {
            return "Hello";
        }
    }

    static class ByeTask implements Task {
        public String doTask() {
            return "Bye";
        }
    }

    static class TaskDispatcher {
        static enum Tasks {
            Hello {
                Task getTask() {
                    return new HelloTask();
                }
            },
            Bye {
                Task getTask() {
                    return new ByeTask();
                }
            };
            abstract Task getTask();
        };

        public static Task getTask(String taskName) {
            return Tasks.valueOf(taskName).getTask();
        }
    }

    public static void main(String[] args) {
        System.out.println("Hello task says: "+TaskDispatcher.getTask("Hello").doTask());
        System.out.println("Bye task says: "+TaskDispatcher.getTask("Bye").doTask());
    }

}

What would be much nicer is if the code to register (say) HelloTask could live only inside HelloTask and not have to leak out into TaskDispatcher (As in the real project these are multiple different files)

nick
  • 11
  • 1
  • 3

4 Answers4

1

Don't be afraid to use frameworks! With Spring it's just a matter of few lines of code. Each Task has to be annotated (it doesn't have to be @Service from Spring) and all you have to do in your dispatcher (after bootstrapping the context and enabling component scanning) is:

class TaskDispatcher {
    @Autowired
    List<Task> tasks;

    //...
}

Spring will automatically find all Tasks on the CLASSPATH and inject them for you. You can use bean/class name as identifier or whatever you want.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • I assume spring is going to do this via reflection right? I can easily implement that on my own, and might go that route, but I felt there should be a cleaner way. – nick Oct 28 '12 at 14:32
  • So, I've had a bash at using this method, and I'm stuck because it seems impossible to not have to instantiate an instance of every Task to fill the list. What I really want is a List>, so I can set up some config that the task wants in its constructor, and then call newInstance on one of the elements in the list. This doesn't seem possible in spring however. – nick Oct 31 '12 at 11:48
0

I have implemented this kind of thing in the past by simply having a configuration file that contains the mappings from names to classnames:

Task1=com.acme.tasks.MyTask1
Task2=com.acme.tasks.more.MyTask2

This is used to populate the Registry, then you can do class.forName() on the classname, and create a new instance of the specific task class.

Adding a new task then just means adding the new task class(es) to the classpath, and adding a line to the configuration file - no recompilation of existing code is needed.

DNA
  • 42,007
  • 12
  • 107
  • 146
  • That's a bit cleaner than adding to the Enum, but still unfortunate to have to add stuff in multiple places to implement a new task. – nick Oct 28 '12 at 14:24
0

You could check out the service provider mechanism, for example in this SO post. Using this you could create an interface through which implementors of Tasks notify your central piece of code about all the tasks they implement.

I assume that each implementor will contribute their own JAR, this technique assumes such a scenario.

Community
  • 1
  • 1
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • The tutorial linked there doesn't seem to exist anymore, and I don't totally get what they are trying to do from the context in the question. While some tasks might be in their own jar file, they don't have to be, so this doesn't seem like the best option. Thanks though! – nick Oct 28 '12 at 14:29
0

I recently had the same problem and came up with the following solution. Each Task (in my case, I called it SomethingHandler) was annotated with a custom annotation @RequestHandlerBean:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface RequestHandlerBean {

    String taskIdentifier();

}

Here is an implementation class using this annotation:

@RequestHandlerBean(taskIdentifier = "task1")
public class HecAckIndexPlanRequestHandler implements  RequestHandler {

    public void handleRequest(Request request) { 
        // ... 
    }

}

Within my dispatcher, I now used Spring's getBeansWithAnnotation method of the ApplicationContext:

Map<String, Object> annotatedHandlers = 
    context.getBeansWithAnnotation(RequestHandlerBean.class);

You can now create a map Map<String, RequestHandler> with the following code:

Map<String, Object> annotatedHandlers = context.getBeansWithAnnotation(RequestHandlerBean.class);
Map<String, RequestHandler> handlers  = new HashMap<>();
for (Map.Entry<String, Object> entry : annotatedHandlers.entrySet()) {
    ServiceInstanceRequestHandler handler = (RequestHandler) entry.getValue();
    Annotation a = AnnotationUtils.findAnnotation(handler.getClass(), RequestHandlerBean.class);
    String taskIdentifier = AnnotationUtils.getValue(a, "taskIdentifier")
    handlers.put(taskIdentifier, handler);
 }

You can now invoke a task via

handlers.get(taskIdentifier).handleRequest(...)

Hope, this helps.

Michael Lihs
  • 7,460
  • 17
  • 52
  • 85