【021】反射与注解:Spring 里背后的影子

写业务代码时,你可能天天都在用注解:

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 文件结构、类加载过程、对象创建流程、内存布局。

相关推荐
lulu12165440781 小时前
Claude Code项目大了响应慢怎么办?Subagents、Agent Teams、Git Worktree、工作流编排四种方案深度解析
java·人工智能·python·ai编程
riNt PTIP1 小时前
SpringBoot创建动态定时任务的几种方式
java·spring boot·spring
Ares-Wang2 小时前
Flask》》 Flask-Bcrypt 哈希加密
后端·python·flask
小码哥_常2 小时前
Spring Boot项目大变身:为何要拆成这六大模块?
后端
老星*2 小时前
AI选股核心设计思路
java·ai·开源·软件开发
invicinble2 小时前
spirng的bean的生命周期,以及为什么这么设计
spring
それども2 小时前
Comparator.comparing 和 拆箱问题
java·jvm
星晨羽3 小时前
西门子机床opc ua协议实现变量读写及NC文件上传下载
java·spring boot
码事漫谈4 小时前
兵临城下:DeepSeek-V4 的技术突围与算力“成人礼”
后端