写业务代码时,你可能天天都在用注解:
java
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
// ...
}
}
但有没有想过:这些注解是怎么生效的 ?Spring 是怎么找到这些类的 ?为什么加个注解就能自动注入?
反射和注解是 Spring、MyBatis 等框架的核心基石 。理解它们,不仅能帮你更好地使用框架,还能让你有能力自定义注解来实现业务需求。
这篇帮你把反射和注解彻底搞明白。下面我按「反射基础 → 反射实战 → 注解基础 → 注解实战 → Spring 应用」的顺序往下聊。
1. 反射:运行时动态操作类 🔍
1.1 什么是反射?
反射(Reflection) 是在运行时动态获取类的信息(属性、方法、构造函数)并操作对象的能力。
正常情况下,代码在编译时就知道要操作哪个类:
java
User user = new User();
user.setName("Tom");
但有时候,我们在编译时不知道要操作哪个类,只有在运行时才知道:
java
// 运行时才知道类名
String className = "com.example.User";
Class<?> clazz = Class.forName(className); // 反射获取 Class
Object obj = clazz.newInstance(); // 创建实例
1.2 Class 对象:反射的核心
每个类在 JVM 中只有一个 Class 对象,包含了类的所有信息。
java
// 获取 Class 对象的三种方式
Class<?> clazz1 = User.class; // 方式 1:类字面量
Class<?> clazz2 = new User().getClass(); // 方式 2:实例对象
Class<?> clazz3 = Class.forName("com.example.User"); // 方式 3:Class.forName
1.3 反射获取类信息
java
Class<?> clazz = User.class;
// 获取类名
System.out.println(clazz.getName()); // com.example.User
System.out.println(clazz.getSimpleName()); // User
// 获取包名
System.out.println(clazz.getPackage()); // package com.example
// 获取父类
System.out.println(clazz.getSuperclass()); // class java.lang.Object
// 获取接口
Class<?>[] interfaces = clazz.getInterfaces();
// 获取修饰符
int modifiers = clazz.getModifiers();
System.out.println(Modifier.isPublic(modifiers)); // true
1.4 反射获取属性
java
Class<?> clazz = User.class;
// 获取所有公开属性
Field[] fields = clazz.getFields();
// 获取所有属性(包括私有)
Field[] allFields = clazz.getDeclaredFields();
// 获取指定属性
Field nameField = clazz.getDeclaredField("name");
// 操作属性
User user = new User();
nameField.setAccessible(true); // 私有属性需要设置可访问
nameField.set(user, "Tom"); // 设置值
System.out.println(nameField.get(user)); // 获取值
1.5 反射获取方法
java
Class<?> clazz = User.class;
// 获取所有公开方法
Method[] methods = clazz.getMethods();
// 获取所有方法(包括私有)
Method[] allMethods = clazz.getDeclaredMethods();
// 获取指定方法
Method setName = clazz.getMethod("setName", String.class);
Method getName = clazz.getMethod("getName");
// 调用方法
User user = new User();
setName.invoke(user, "Tom"); // 调用 user.setName("Tom")
String name = (String) getName.invoke(user); // 调用 user.getName()
1.6 反射获取构造函数
java
Class<?> clazz = User.class;
// 获取所有构造函数
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
// 获取指定构造函数
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 创建实例
User user = (User) constructor.newInstance("Tom", 20);
2. 反射实战 🎯
2.1 通用对象复制
java
public static void copyProperties(Object source, Object target) throws Exception {
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();
for (Field field : sourceClass.getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(source);
// 查找目标类的同名属性
try {
Field targetField = targetClass.getDeclaredField(field.getName());
targetField.setAccessible(true);
targetField.set(target, value);
} catch (NoSuchFieldException e) {
// 目标没有同名属性,跳过
}
}
}
2.2 通用 JSON 序列化
java
public static String toJson(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
StringBuilder sb = new StringBuilder("{");
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
Object value = fields[i].get(obj);
sb.append("\"").append(fields[i].getName()).append("\":");
sb.append("\"").append(value).append("\"");
if (i < fields.length - 1) {
sb.append(",");
}
}
sb.append("}");
return sb.toString();
}
2.3 动态调用方法
java
public static Object invokeMethod(Object obj, String methodName, Object... args) throws Exception {
Class<?> clazz = obj.getClass();
// 获取参数类型数组
Class<?>[] paramTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
paramTypes[i] = args[i].getClass();
}
// 获取方法
Method method = clazz.getMethod(methodName, paramTypes);
// 调用方法
return method.invoke(obj, args);
}
3. 注解:元数据的标记 📌
3.1 什么是注解?
注解(Annotation) 是元数据,用来给代码提供额外信息,但不直接影响代码执行。
java
// 这是一个注解
@Override
public void toString() {
// ...
}
3.2 注解的定义
java
// 定义注解
public @interface MyAnnotation {
// 注解属性
String value() default ""; // 默认值
String name() default "unknown";
int[] ids() default {};
}
3.3 注解的分类
| 分类 | 作用 | 示例 |
|---|---|---|
| 元注解 | 注解的注解 | @Retention、@Target、@Documented |
| 内置注解 | JDK 自带 | @Override、@Deprecated、@SuppressWarnings |
| 自定义注解 | 业务定义 | @Controller、@Service、@Autowired |
3.4 元注解
java
// 元注解:定义注解的注解
// 1. @Retention:注解保留到什么时候
@Retention(RetentionPolicy.SOURCE) // 只在源码中保留,编译时丢弃
@Retention(RetentionPolicy.CLASS) // 编译时保留,运行时丢弃(默认)
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
// 2. @Target:注解可以用在什么地方
@Target(ElementType.TYPE) // 类、接口
@Target(ElementType.METHOD) // 方法
@Target(ElementType.FIELD) // 字段
@Target(ElementType.PARAMETER) // 参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
// 3. @Documented:是否生成到文档
@Documented
// 4. @Inherited:是否可继承
@Inherited
3.5 注解的使用
java
// 使用注解
@MyAnnotation(value = "test", name = "Tom", ids = {1, 2, 3})
public class User {
@MyAnnotation(value = "field")
private String name;
@MyAnnotation("method")
public void method() {
}
}
4. 注解处理器:解析注解 🛠️
4.1 运行时注解处理
java
// 注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
// 处理器
public class TableProcessor {
public static void process(Class<?> clazz) {
if (clazz.isAnnotationPresent(Table.class)) {
Table table = (Table) clazz.getAnnotation(Table.class);
System.out.println("表名:" + table.value());
}
}
}
// 使用
@Table("t_user")
class User {
}
public static void main(String[] args) {
TableProcessor.process(User.class); // 输出:表名:t_user
}
4.2 自定义注解实现 ORM
java
// 注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
}
// 实体类
@Table("t_user")
public class User {
@Column("id")
private Long id;
@Column("name")
private String name;
@Column("age")
private Integer age;
}
// 处理器
public class OrmProcessor {
public static String toSql(Class<?> clazz) {
Table table = (Table) clazz.getAnnotation(Table.class);
StringBuilder sql = new StringBuilder("SELECT ");
for (Field field : clazz.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
sql.append(column.value()).append(", ");
}
}
sql.setLength(sql.length() - 2);
sql.append(" FROM ").append(table.value());
return sql.toString();
}
}
// 使用
public static void main(String[] args) {
System.out.println(OrmProcessor.toSql(User.class));
// SELECT id, name, age FROM t_user
}
5. Spring 中反射与注解的应用 🔥
5.1 IoC 容器:自动创建 Bean
Spring 通过反射创建 Bean:
java
// Spring 大致原理
public class ApplicationContext {
private Map<String, Object> beans = new HashMap<>();
public void registerBean(Class<?> clazz) {
// 获取 @Component 注解
Component comp = clazz.getAnnotation(Component.class);
String beanName = comp.value();
try {
// 反射创建实例
Object instance = clazz.newInstance();
beans.put(beanName, instance);
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getBean(String name) {
return beans.get(name);
}
}
5.2 依赖注入:自动注入属性
java
// Spring 大致原理
public class AutowireProcessor {
public void process(Object bean) {
for (Field field : bean.getClass().getDeclaredFields()) {
// 查找 @Autowired 注解
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
// 从容器中获取依赖的 Bean
Object dependency = getBean(field.getType());
try {
// 反射注入
field.set(bean, dependency);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
5.3 请求映射:@RequestMapping
java
// Spring MVC 大致原理
public class RequestMappingHandler {
private Map<String, Method> mappings = new HashMap<>();
public void register(Class<?> controllerClass) {
// 获取类上的 @RequestMapping
RequestMapping classMapping = controllerClass.getAnnotation(RequestMapping.class);
String classPath = classMapping.value()[0];
// 遍历所有方法
for (Method method : controllerClass.getDeclaredMethods()) {
// 获取方法上的 @RequestMapping
if (method.isAnnotationPresent(GetMapping.class)) {
GetMapping mapping = method.getAnnotation(GetMapping.class);
String methodPath = mapping.value()[0];
// 注册映射
mappings.put(classPath + methodPath, method);
}
}
}
public void handle(String path, HttpServletRequest request, HttpServletResponse response) {
Method method = mappings.get(path);
if (method != null) {
try {
// 反射调用方法
Object controller = getBean(method.getDeclaringClass());
method.invoke(controller, request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5.4 AOP:切面编程
java
// AOP 大致原理
public class AopProxy {
public Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
// 前置通知
before();
// 反射调用原方法
Object result = method.invoke(target, args);
// 后置通知
after();
return result;
}
);
}
}
6. 自定义注解实战 🎯
6.1 日志注解
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
// 使用
public class UserService {
@Log("查询用户")
public User getUser(Long id) {
return userMapper.selectById(id);
}
@Log("删除用户")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
}
// 处理器(切面)
@Aspect
@Component
public class LogAspect {
@Around("@annotation(log)")
public Object around(ProceedingJoinPoint pjp, Log log) throws Throwable {
System.out.println("开始执行:" + log.value());
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println("执行完成:" + log.value() + ",耗时:" + (end - start) + "ms");
return result;
}
}
6.2 权限注解
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
String value();
}
// 使用
public class AdminService {
@RequiresRole("admin")
public void deleteUser(Long id) {
// 删除用户
}
}
// 处理器
@Aspect
@Component
public class AuthAspect {
@Around("@annotation(requiresRole)")
public Object around(ProceedingJoinPoint pjp, RequiresRole role) throws Throwable {
// 获取当前用户角色
String currentRole = getCurrentUserRole();
if (!currentRole.equals(role.value())) {
throw new SecurityException("权限不足");
}
return pjp.proceed();
}
}
6.3 限流注解
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int value() default 100; // 每秒允许的请求数
}
// 处理器
@Aspect
@Component
public class RateLimitAspect {
private Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
String key = pjp.getSignature().toShortString();
AtomicInteger counter = counters.computeIfAbsent(key, k -> new AtomicInteger(0));
if (counter.incrementAndGet() > rateLimit.value()) {
throw new RuntimeException("请求过于频繁,请稍后重试");
}
try {
return pjp.proceed();
} finally {
counter.decrementAndGet();
}
}
}
小结
- 反射是在运行时动态获取类信息并操作对象的能力,核心是 Class 对象
- 反射可以获取类的属性、方法、构造函数,并动态调用
- 注解是元数据,用来给代码提供额外信息
- 元注解 包括
@Retention、@Target、@Documented、@Inherited - Spring 通过反射创建 Bean、通过注解实现依赖注入和请求映射
- 自定义注解需要配合注解处理器(反射或 AOP)才能生效
下一篇(022)预告:JVM 运行时数据区与对象创建------Class 文件结构、类加载过程、对象创建流程、内存布局。