Java自定义注解从入门到实战(附完整案例)
📖 目录导读
- 什么是Java注解?核心概念解析
- 自定义注解的语法与规则
- 实战案例一:日志记录注解
- 实战案例二:参数校验注解
- 注解的处理器实现(反射+动态代理)
- 常见问题与避坑指南(Q&A)
- 自定义注解的最佳实践
什么是Java注解?核心概念解析
在Java世界中,注解(Annotation)是一种元数据形式,它为代码提供额外信息而不直接改变程序行为,自Java 5引入以来,注解已成为Spring、MyBatis等框架的核心基石。

关键要点:
- 注解以
@interface声明,本质是特殊接口 - 注解本身不执行逻辑,依赖处理器实现功能
- 常见内置注解:
@Override、@Deprecated、@SuppressWarnings
自定义注解的语法与规则
要创建自己的注解,需要掌握以下元注解:
| 元注解 | 作用 |
|---|---|
@Target |
指定注解适用位置(方法、字段、类等) |
@Retention |
注解生命周期(源码、编译、运行时) |
@Inherited |
是否允许子类继承 |
@Documented |
是否包含在JavaDoc中 |
示例:一个简单的自定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
String value() default "default"; // 可配置参数
}
规则:
- 注解方法不能有参数和异常声明
- 返回值类型必须是基本类型、String、Class、枚举、注解或它们的数组
- 可使用
default设置默认值
实战案例一:日志记录注解
很多开发者需要统计方法执行时间,通过自定义注解可以优雅实现。
步骤1:定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String module() default "System";
}
步骤2:编写处理器(AOP或代理)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
@Around("@annotation(logExecution)")
public Object logTime(ProceedingJoinPoint pjp, LogExecution logExecution) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long elapsed = System.currentTimeMillis() - start;
System.out.println(logExecution.module() + " 执行耗时: " + elapsed + "ms");
return result;
}
}
步骤3:使用注解
@Service
public class UserService {
@LogExecution(module = "用户服务")
public User findUser(Long id) {
// 业务逻辑...
}
}
实战案例二:参数校验注解
很多业务需要对方法参数进行空值或范围校验,手动判断非常繁琐。
定义校验注解:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String message() default "参数不能为空";
}
处理器实现:
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ValidationUtil {
public static void validate(Object[] args, Method method) {
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].isAnnotationPresent(NotNull.class) && args[i] == null) {
NotNull annotation = parameters[i].getAnnotation(NotNull.class);
throw new IllegalArgumentException(annotation.message());
}
}
}
}
结合代理使用:
public class ProxyFactory {
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
ValidationUtil.validate(args, method);
return method.invoke(target, args);
}
);
}
}
注解的处理器实现(反射+动态代理)
自定义注解的核心在于处理器,主要实现方式有:
- 运行时反射处理:最常用,通过
getAnnotation()获取注解信息 - 编译时处理:使用APT(Annotation Processing Tool)生成代码
- 字节码增强:通过ASM或Javassist在类加载时修改字节码
反射处理核心代码:
Method method = obj.getClass().getMethod("someMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// 根据注解属性执行逻辑
}
注意: 只有RetentionPolicy.RUNTIME的注解才能通过反射获取。
常见问题与避坑指南(Q&A)
Q1:自定义注解为什么需要元注解?
A:元注解定义了注解的行为特性,没有@Retention(RUNTIME),运行时反射无法获取到注解信息。
Q2:注解可以有继承关系吗?
A:Java不支持注解继承,但可以通过组合多个元注解实现类似效果,但@Inherited仅对类上的注解生效,对方法不生效。
Q3:多个相同注解如何处理?
A:Java 8开始支持重复注解,需要定义容器注解,Spring框架对此有优雅支持。
Q4:为什么我的注解处理器不生效?
A:检查三点:①@Retention是否包含RUNTIME;②代理或AOP是否配置正确;③目标类是否被Spring容器管理(若使用AOP)。
Q5:自定义注解能替代接口吗?
A:不能,注解是元数据,接口是契约,两者用途不同,但可结合使用(如标记接口+注解处理)。
最佳实践建议
- 明确用途:注解应服务于特定横切关注点(日志、权限、缓存等)
- 合理选择生命周期:常规业务用
RUNTIME,代码生成用SOURCE - 保持简洁:一个注解只做一件事,避免参数过多
- 提供默认值:为注解属性设置合理的默认值
- 配套处理器:始终编写对应的处理器文档说明
自定义注解是提升框架开发效率和代码可读性的利器,掌握它,你将能构建出类似Spring Boot Starter的灵活组件,也能在团队中制定统一的代码规范,建议从简单的日志注解开始,逐步深入AOP和编译时处理技术。