Skip to content

Instantly share code, notes, and snippets.

@cristiandelahooz
Last active March 9, 2025 12:53
Show Gist options
  • Save cristiandelahooz/8d62503c199e7f1752875d4d8917c62e to your computer and use it in GitHub Desktop.
Save cristiandelahooz/8d62503c199e7f1752875d4d8917c62e to your computer and use it in GitHub Desktop.
DynamicControllerRoutesWithReflection
package org.example.utils;
import io.javalin.Javalin;
import io.javalin.http.Context;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import org.example.annotations.Controller;
import org.example.annotations.Delete;
import org.example.annotations.Get;
import org.example.annotations.Post;
import org.example.annotations.Put;
public class ApiRouter {
public static void registerRoutes(Javalin app) {
List<Class<?>> controllers = ReflectionUtils.getClasses("org.example.controllers");
for (Class<?> clazz : controllers) {
if (clazz.isAnnotationPresent(Controller.class)) {
Controller controller = clazz.getAnnotation(Controller.class);
try {
Object controllerInstance = clazz.getDeclaredConstructor().newInstance();
for (Method method : clazz.getDeclaredMethods()) {
String path = controller.path();
if (method.isAnnotationPresent(Get.class)) {
Get get = method.getAnnotation(Get.class);
path += get.path();
app.get(path, ctx -> invokeMethod(method, controllerInstance, ctx));
} else if (method.isAnnotationPresent(Post.class)) {
Post post = method.getAnnotation(Post.class);
path += post.path();
app.post(path, ctx -> invokeMethod(method, controllerInstance, ctx));
} else if (method.isAnnotationPresent(Put.class)) {
Put put = method.getAnnotation(Put.class);
path += put.path();
app.put(path, ctx -> invokeMethod(method, controllerInstance, ctx));
} else if (method.isAnnotationPresent(Delete.class)) {
Delete delete = method.getAnnotation(Delete.class);
path += delete.path();
app.delete(path, ctx -> invokeMethod(method, controllerInstance, ctx));
}
}
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e) {
System.err.println("❌ Error initializing controller: " + clazz.getName());
e.printStackTrace();
}
} else {
System.out.println("⏩ Skipping non-controller class: " + clazz.getName());
}
}
}
private static void invokeMethod(Method method, Object instance, Context ctx) {
System.out.println(
"🔄 Invoking method: " + method.getName() + " in " + instance.getClass().getName());
try {
if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Context.class) {
method.invoke(instance, ctx);
} else {
System.err.println(
"❌ Invalid method signature: " + method.getName() + " must accept only Context.");
}
} catch (IllegalAccessException | InvocationTargetException e) {
System.err.println(
"❌ Error invoking method: " + method.getName() + " in " + instance.getClass().getName());
e.printStackTrace();
}
}
}
package org.example.annotations;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
String path() default "";
}
package org.example.annotations;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {
String path() default "";
}
package org.example.annotations;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Get {
String path() default "";
}
package org.example.annotations;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Post {
String path() default "";
}
package org.example.annotations;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Put {
String path() default "";
}
package org.example.utils;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ReflectionUtils {
public static List<Class<?>> getClasses(String packageName) {
List<Class<?>> classes = new ArrayList<>();
String path = packageName.replace('.', '/');
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL resource = classLoader.getResource(path);
if (resource == null) {
throw new RuntimeException("Package not found: " + packageName);
}
try {
if (resource.getProtocol().equals("file")) {
// Running from filesystem
File directory = new File(resource.toURI());
if (!directory.exists()) {
throw new RuntimeException("Directory not found: " + directory.getAbsolutePath());
}
for (String file : directory.list()) {
if (file.endsWith(".class")) {
String className = packageName + '.' + file.substring(0, file.length() - 6);
classes.add(Class.forName(className));
}
}
} else if (resource.getProtocol().equals("jar")) {
// Running from a JAR file
JarFile jarFile;
String jarPath;
if (resource.toString().startsWith("jar:file:")) {
jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!"));
jarFile = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
} else {
JarURLConnection jarConnection = (JarURLConnection) resource.openConnection();
jarFile = jarConnection.getJarFile();
}
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.startsWith(path) && entryName.endsWith(".class")) {
String className = entryName.replace('/', '.').replace(".class", "");
classes.add(Class.forName(className));
}
}
jarFile.close();
}
} catch (IOException | ClassNotFoundException | URISyntaxException e) {
throw new RuntimeException("Failed to load classes from package: " + packageName, e);
}
return classes;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment