反射与注解
9/6/26JavaJava基础About 4 min
反射(Reflection)
反射是 Java 在运行期动态获取类信息(构造器、方法、字段)并操作类成员的机制。普通编码在编译期就知道要操作哪个类,而反射允许程序在运行时通过类的全限定名来加载、探查和使用类,是很多框架(Spring、MyBatis、Jackson)的基石。
获取 Class 对象的三种方式
// 1. 类名.class(编译期已知,不会触发类初始化)
Class<?> c1 = String.class;
// 2. 对象.getClass()
String s = "";
Class<?> c2 = s.getClass();
// 3. Class.forName(最常用,按全限定名加载,会触发类初始化)
Class<?> c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2 && c2 == c3); // true,同一个类的 Class 对象全局唯一常用反射 API
Class<?> clazz = Class.forName("com.example.User");
// 构造器
Constructor<?> ctor = clazz.getDeclaredConstructor(String.class, int.class);
ctor.setAccessible(true); // 突破 private
Object obj = ctor.newInstance("Tom", 18);
// 方法
Method m = clazz.getDeclaredMethod("sayHello", String.class);
m.setAccessible(true);
m.invoke(obj, "World");
// 字段
Field f = clazz.getDeclaredField("name");
f.setAccessible(true);
f.set(obj, "Jerry");
Object name = f.get(obj);| API | 作用 | 说明 |
|---|---|---|
getConstructor(...) / getDeclaredConstructor(...) | 获取构造器 | Declared 可获取 private,不含父类 |
newInstance() | 创建实例 | JDK 9 后建议用 Constructor.newInstance |
getMethod(...) / getDeclaredMethod(...) | 获取方法 | getMethod 仅 public 且含继承的方法 |
getField(...) / getDeclaredField(...) | 获取字段 | 同上 |
setAccessible(true) | 关闭访问检查 | 可操作 private 成员,但破坏封装 |
反射的优缺点
- 优点:极高的灵活性和扩展性,实现通用框架、动态代理、依赖注入、序列化等。
- 缺点:
- 性能开销:反射调用涉及运行时解析,比直接调用慢(JVM 难以内联优化)。
- 安全与封装:
setAccessible(true)可绕过访问修饰符;受安全管理器限制。 - 可维护性:绕过了编译期类型检查,错误只能到运行时暴露。
动态代理
反射的重要应用之一是动态代理:在运行时为接口动态生成代理类,把方法调用转发给 InvocationHandler。
interface Service {
void hello(String name);
}
class RealService implements Service {
public void hello(String name) { System.out.println("Hello " + name); }
}
// 调用处理器
class LogHandler implements InvocationHandler {
private final Object target;
LogHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after " + method.getName());
return result;
}
}
public class ProxyDemo {
public static void main(String[] args) {
Service real = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[]{Service.class},
new LogHandler(real));
proxy.hello("Java"); // 在调用前后自动打印日志
}
}动态代理的限制
Proxy.newProxyInstance 只能代理接口。若要代理类(无接口),需使用 CGLIB 等基于字节码的技术(通过继承 + 方法拦截实现)。
注解(Annotation)
注解是一种元数据,本身对代码逻辑无直接影响,但可被编译器、框架在编译期或运行期读取并据此执行特定逻辑。
内置注解
@Override:标记方法重写父类方法,编译期校验签名正确。@Deprecated:标记过时元素,使用时编译器警告。@SuppressWarnings:抑制指定编译器警告。@FunctionalInterface:标记函数式接口(仅一个抽象方法)。
元注解(修饰注解的注解)
| 元注解 | 作用 |
|---|---|
@Target | 指定注解可作用的位置(TYPE/METHOD/FIELD/PARAMETER 等) |
@Retention | 指定保留策略(SOURCE/CLASS/RUNTIME) |
@Documented | 注解是否包含在 JavaDoc 中 |
@Inherited | 子类是否继承父类的注解 |
@Repeatable | 注解是否可以重复声明(JDK 8+) |
@Retention 的三种策略:
SOURCE:仅保留在源码,编译后丢弃(如@Override)。CLASS:保留到字节码,但运行时不可见(默认)。RUNTIME:保留到运行时,可通过反射读取(最常用,框架注解多为该策略)。
自定义注解 + 反射解析
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
int level() default 1;
}
class Demo {
@Log(value = "用户登录", level = 2)
public void login() { }
}
public class AnnotationParser {
public static void main(String[] args) throws Exception {
Method method = Demo.class.getMethod("login");
if (method.isAnnotationPresent(Log.class)) {
Log log = method.getAnnotation(Log.class);
System.out.println(log.value() + ", level=" + log.level());
}
}
}反射 + 注解 的典型场景
- 依赖注入:Spring 扫描
@Component/@Autowired并反射创建、装配 Bean。 - ORM 映射:Hibernate/JPA 读取
@Table/@Column把对象与数据库表字段对应。 - 序列化:Jackson 通过
@JsonProperty反射读写字段。 - 测试框架:JUnit 通过
@Test反射调用测试方法。 - 路由映射:Spring MVC 通过
@RequestMapping反射注册 URL 与方法的映射。