Last active
March 9, 2025 12:53
-
-
Save cristiandelahooz/8d62503c199e7f1752875d4d8917c62e to your computer and use it in GitHub Desktop.
DynamicControllerRoutesWithReflection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example.annotations; | |
import java.lang.annotation.*; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.TYPE) | |
public @interface Controller { | |
String path() default ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example.annotations; | |
import java.lang.annotation.*; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.METHOD) | |
public @interface Delete { | |
String path() default ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example.annotations; | |
import java.lang.annotation.*; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.METHOD) | |
public @interface Get { | |
String path() default ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example.annotations; | |
import java.lang.annotation.*; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.METHOD) | |
public @interface Post { | |
String path() default ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example.annotations; | |
import java.lang.annotation.*; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.METHOD) | |
public @interface Put { | |
String path() default ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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