定义运行时注解必须加@Retention(RetentionPolicy.RUNTIME),否则反射获取为空;@Target需明确指定作用位置;扫描时应使用上下文类加载器并缓存Class对象。

怎么用 @interface 定义运行时注解
必须加 @Retention(RetentionPolicy.RUNTIME),否则反射拿不到。不加这句,getAnnotations() 返回空数组是常态,不是 bug。
常见错误:只加了 @Target(ElementType.METHOD) 却漏掉 @Retention,结果调试半天发现注解“不存在”。
建议写法:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Route {
String value() default "";
String method() default "GET";
}
-
value()是约定俗成的默认属性,调用时可省略键名:@Route("/user") - 如果注解要作用在类或方法上,
@Target得明确列出,不能只写TYPE却想用在方法上 - 避免用
Class类型属性——它在运行时可能被泛型擦除或类加载器隔离,导致isAssignableFrom失效
怎么用 Class.getAnnotations() 和 Method.getAnnotation() 扫描注解
别遍历所有类手动 Class.forName() —— 类没被加载,反射根本看不到。得靠已知入口(比如启动类、配置包名)+ ClassLoader + File 或 URL 扫包。
真实场景中,你几乎不会直接调 clazz.getAnnotations() 拿类上的注解,而是先找类,再查它的方法:
for (Method m : clazz.getDeclaredMethods()) {
Route route = m.getAnnotation(Route.class);
if (route != null) {
handlerMap.put(route.value(), new Handler(clazz, m));
}
}
- 用
getDeclaredMethods(),不是getMethods()—— 后者只返回 public 且含父类继承的方法,会漏掉 private 或 package-private 的处理器 - 记得调
m.setAccessible(true),否则私有方法反射调用直接抛IllegalAccessException - 扫描包路径时,IDE 编译后 class 文件在
target/classes,但 jar 包里是 zip 结构,要用URL#protocol.equals("jar")分支处理
怎么安全地用 Method.invoke() 执行带注解的方法
反射调用失败十有八九是因为参数类型不匹配或实例不对。比如目标方法是 void handle(User user),你传 new Object[]{jsonStr} 就必然报 IllegalArgumentException。
轻量级框架里,通常需要做一层参数解析(比如从 HTTP 请求体转对象),而不是裸调 invoke:
Object[] args = resolveArgs(m, request); // 自定义解析逻辑 m.invoke(instance, args);
- 不要复用同一个
Method对象跨不同类实例调用——invoke第一个参数必须是该方法所属类的具体实例 - 捕获
InvocationTargetException,它的getCause()才是业务代码真正抛的异常;直接打印e会看到一堆 “wrapped in…” - 高频调用场景下,
Method.invoke()性能较差,可考虑用MethodHandle或生成字节码(如 ByteBuddy),但对轻量级框架属于过早优化
为什么 Spring 不让你手动扫包,而你的小框架却要自己实现
因为 Spring 有完整的 BeanFactory 和 ClassPathBeanDefinitionScanner,底层依赖 ResourcePatternResolver 统一处理 file/jar/gradle build output 等路径。你的小框架没这层抽象,就得直面 ClassLoader.getResources("com/example/controller") 返回空的问题。
最容易被忽略的一点:模块化(Java 9+)或某些容器(如 Tomcat 的 webapp classloader)下,Thread.currentThread().getContextClassLoader() 和 MyFramework.class.getClassLoader() 可能不是同一个,导致 getResources() 查不到类。
解决方法很简单:
- 统一用
Thread.currentThread().getContextClassLoader()扫包,它是 Web 容器/主流框架设好的“当前上下文” - 加一层缓存:把扫到的
Class对象存ConcurrentHashMap,避免每次 HTTP 请求都重扫 - 别在 static 块里初始化扫描逻辑——类加载时机不可控,可能早于配置就绪

















