0

I am creating a route controller structure for commands. Every controller has a @ControlController annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component // Because @Component all controllers will be spring managed.
public @interface ControlController {
}

The controller should contain methods with the @CommandMapping annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandMapping {
    String value();
}

The value of the @CommandMapping annotation is the command. So the method should be called when the value is the same as the command that is called.

At the start of the application the following code is called to fetch all the @CommandMappings:

/**
 * Load all controller mappings.
 */
private void fetchControllers() {
    // Get all beans with the ControlController annotation.
    Map<String, Object> controllers = this.applicationContext.getBeansWithAnnotation(ControlController.class);

    for (Map.Entry<String, Object> entry : controllers.entrySet()) {
        Class controller = entry.getValue().getClass();

        for (Method method: controller.getMethods()) {
            // Check every method in a controller for the CommandMapping annotation.
            // When the annotation is present the method is a command mapping.
            if (method.isAnnotationPresent(CommandMapping.class)) {
                CommandMapping commandMapping = method.getAnnotation(CommandMapping.class);
                // Add the command mapping to the controller list.
                this.controllers.put(commandMapping.value(), method);
            }
        }
    }
}

This code will find all the beans with the @ControlController annotation and will loop trough all the methods to find the @CommandMapping annotation. All the methods will be put in a Map<String, Method>.

Until this far everything works perfect.

The following method is used to execute the right method that belongs to a command:

/**
 * Execute a command for a client.
 *
 * @param client The client.
 * @param command The command.
 */
public void executeCommand(Client client, String command) {
    // Get the method that belongs to the command.
    Method method = this.controllers.get(command);
    Class<?> controllerClass = method.getDeclaringClass();

    // The the controller that belongs to the method.
    Object controller = this.applicationContext.getBean(controllerClass); // Here the code just stops.
    System.out.println("Yeah"); // This isn't executed.

    try {
        List<Object> arguments = new ArrayList<>();
        for (Parameter parameter: method.getParameters()) {
            // Add arguments based on the parameter type.
        }

        method.invoke(controller, arguments.toArray(new Object[arguments.size()]));
    } catch (Exception exception) {
        exception.printStackTrace();
    }
}

The code just stops without any exception at the this.applicationContext.getBean(controllerClass);

I found out that when I AutoWire the controllerClass it for some reason works. It doesn't matter in what class I autowire the controllers. But of course AutoWiring every controller is an ugly fix.

Why does the ApplicationContext.getBean get stuck and how can I fix this?

UPDATE: I just found out that using the bean name in getBean also works. Example:

this.applicationContext.getBean(MainController.class); //Doesn't work

this.applicationContext.getBean("mainController"); // Works

UPDATE: I forgot to mention something very important(I think): The executeCommand method is called from a thread, but the thread is spring managed. When I run it without a thread it works, but I really need threads. How can I make beans work in a thread?

Jan Wytze
  • 3,307
  • 5
  • 31
  • 51
  • https://stackoverflow.com/questions/4914012/how-to-inject-applicationcontext-itself – Zorglube Oct 02 '17 at 15:59
  • @Zorglube ApplicationContext is already injected properly. – Jan Wytze Oct 02 '17 at 16:03
  • `this.applicationContext.getBean(MainController.class); //Doesn't work this.applicationContext.getBean("mainController"); // Works` This is strange... Have you tryed using `@Component(value = "MyControllerSpecificName")` to set your controller name ? – Zorglube Oct 02 '17 at 16:11
  • Or Maybe `MainController controller = this.applicationContext.getBean(MainController.class);` – Zorglube Oct 02 '17 at 16:15
  • Giving a name to the component(Controller) works by giving the name, but not by giving the class. I really want to give the class because that is what I get from the method: `Class> controllerClass = method.getDeclaringClass();`. `MainController controller = this.applicationContext.getBean(MainController.class);` doesn't work. – Jan Wytze Oct 02 '17 at 17:10

2 Answers2

1

You can try by searching the Controller using the 'name' ; this solution implie to find the name of the Controller by getting the annotation.

i.e.:

@Service
@Component(value = "statService")
public class Controller {...} 

public class AnnotationFinder {

    public static String findComponentName(Class cls) {
        for (Annotation annotation : cls.getDeclaredAnnotations()) {
            if (annotation.annotationType().equals(Component.class)) {
                return annotation.value();
            }
        }
        return null;
    }
}

When you get your @Component you get the value member and =>

Object controller = this.applicationContext.getBean(AnnotationFinder.findComponentName(controllerClass));

Zorglube
  • 664
  • 1
  • 7
  • 15
0

I found out the the web application wasn't working either.

The problem was that the loop that was accepting connections was not running in a separate thread, but just in a component's @PostConstruct, so the application was never fully started, but the server(My SocketServer) was running.

Because the application was not fully started the beans didn't work like expected. So it had nothing to do with the code I posted...

I hope someone else can still learn of my answer.

Jan Wytze
  • 3,307
  • 5
  • 31
  • 51