Java案例如何使用注解功能?

wen java案例 8

Java注解深度解析:从入门到实战,一文掌握注解使用精髓

目录导读

  • 什么是Java注解:注解的本质、作用与分类
  • 注解的核心语法:定义、元注解与属性
  • 实战案例一:自定义@Log注解实现方法日志记录
  • 实战案例二:@NotNull字段校验注解的完整实现
  • 注解的底层机制:反射与动态代理如何驱动注解
  • 常见问题解答(Q&A)
  • 总结与最佳实践

什么是Java注解

Q:注解和注释有什么区别?
A:注解(Annotation)是代码的元数据,可以被编译器或运行时解析执行;而注释(Comment)仅供开发者阅读,不参与编译和执行,例如@Override就是注解,它能强制子类重写父类方法,编译期报错。

Java案例如何使用注解功能?

核心分类

  • 编译时注解:如@SuppressWarnings,仅影响编译器行为。
  • 运行时注解:如@Autowired,通过反射在程序运行时被读取处理。
  • 源码注解:如@IntDef,仅存在于源文件,不参与编译。

注解的核心语法

自定义注解定义

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)  // 运行时可用
@Target(ElementType.METHOD)          // 仅可用于方法
public @interface Log {
    String value() default "";       // 属性,带默认值
    boolean showArgs() default true;
}

元注解详解

  • @Retention:指定生命周期(SOURCE/CLASS/RUNTIME)
  • @Target:指定作用范围(方法、字段、类等)
  • @Documented:是否显示在Javadoc中
  • @Inherited:是否允许子类继承

关键规则:要实现运行时动态处理,必须使用RetentionPolicy.RUNTIME


实战案例一:自定义@Log注解实现方法日志记录

场景需求

统计每个方法的执行时间,并记录输入参数和返回结果,禁止硬编码重复日志代码。

实现步骤

  1. 定义注解(同上@Log
  2. 编写切面处理类(使用Java反射+动态代理)
public class LogHandler implements InvocationHandler {
    private Object target;
    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.isAnnotationPresent(Log.class)) {
            Log log = method.getAnnotation(Log.class);
            long start = System.currentTimeMillis();
            System.out.println("调用方法:" + method.getName() + ",参数:" + Arrays.toString(args));
            Object result = method.invoke(target, args);
            long cost = System.currentTimeMillis() - start;
            System.out.println("方法执行耗时:" + cost + "ms,结果:" + result);
            return result;
        } else {
            return method.invoke(target, args);
        }
    }
}
  1. 使用示例
    public class UserService {
     @Log("用户查询")
     public String findUser(int id) {
         return "user_" + id;
     }
    }

// 客户端调用 UserService service = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new LogHandler(new UserService()) ); service.findUser(100);


**输出日志**:

调用方法:findUser,参数:[100] 方法执行耗时:2ms,结果:user_100


**Q:为什么要用动态代理而不是直接修改原类?**  
A:遵循开闭原则,不修改原有业务代码,通过AOP思想统一注入横切关注点(如日志、事务)。
---
## 实战案例二:@NotNull字段校验注解的完整实现
### 需求
对JavaBean的字段进行非空校验,拒绝空值并给出友好提示。
### 注解定义
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
    String message() default "字段不能为空";
}

校验处理器

public class Validator {
    public static <T> void validate(T obj) throws IllegalAccessException {
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(NotNull.class)) {
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value == null) {
                    NotNull annotation = field.getAnnotation(NotNull.class);
                    throw new IllegalArgumentException(field.getName() + ":" + annotation.message());
                }
            }
        }
    }
}

使用

public class User {
    @NotNull(message = "用户名必填")
    private String name;
    @NotNull
    private Integer age;
}
// 校验
User user = new User();
user.setName(null); // 故意置空
Validator.validate(user); // 抛出异常:name:用户名必填

Q:能否校验嵌套对象(如User中的Address字段)?
A:可以,需递归处理字段类型,对每个非基本类型字段再次调用validate()


注解的底层机制:反射与动态代理

注解本身只是接口,其实际功能完全依赖代码解析,以下机制是注解发挥作用的基石:

  1. 反射获取注解
    method.getAnnotation(Log.class)field.getAnnotations()
  2. 判断注解存在
    field.isAnnotationPresent(NotNull.class)
  3. 解析属性值
    annotation.message() 返回注解中设置的属性

性能考虑:反射操作有损耗,对于高频调用的方法,建议使用编译期处理(如Lombok的@Slf4j)或AOP框架(如Spring AOP)缓存元数据。


常见问题解答(Q&A)

Q1:Spring中的@Transactional是如何通过注解实现事务的?
A:Spring通过AOP代理,在方法执行前开启事务,成功则提交,异常则回滚,原理是TransactionInterceptor基于@Transactional的属性值动态生成事务管理逻辑。

Q2:@Retention(CLASS)和RUNTIME有什么区别?
A:CLASS表示注解保留在字节码文件但不被JVM加载(反射无法读取);RUNTIME表示加载到内存,反射可用,大多数自定义业务注解需要RUNTIME。

Q3:注解能否继承?
A:通过@Inherited元注解:父类使用@Inherited注解的注解,子类可以继承(但仅限于类上,不适用于方法和字段)。

Q4:如何避免反射的性能问题?
A:使用缓存(如将类的注解元数据存入Map)、优先使用编译期插件(如Lombok),或在框架初始化阶段一次性解析注解。

Q5:注解参数类型支持哪些?
A:基本类型、String、Class、枚举、注解类型,以及以上类型的数组,注意不支持封装类型。


总结与最佳实践

  • 善用注解减少样板代码:例如@Data(Lombok)、@RestController(Spring)能大幅提升效率。
  • 自定义注解三步骤:定义注解 → 定义处理器(反射/AOP) → 在业务代码中标记。
  • 运行时注解 vs 编译时注解:业务逻辑校验用RUNTIME,代码生成类(如Lombok)用SOURCE。
  • 避免过度设计:注解数量膨胀会增加维护成本,仅对高频横切关注点(日志、校验、缓存)使用。
  • 结合框架实战:学习Spring的注解体系(如@Component@Autowired@Value)是进阶最佳路径。

一句话总结:注解是Java“约定优于配置”哲学的极致体现,掌握其原理和实战,能让你的代码更优雅、更可复用。

抱歉,评论功能暂时关闭!