抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

注解(Annotation)是Java的元数据机制,Spring、MyBatis、JUnit等框架大量使用注解简化配置。本文从基础到自定义,系统讲解Java注解。

什么是注解

注解是一种元数据,用于为代码提供额外信息。它本身不影响程序逻辑,但可以被编译器、框架、工具读取和处理。

1
2
3
4
5
6
7
8
9
10
@Override                    // 告诉编译器这是重写方法
public String toString() {
return "Hello";
}

@Deprecated // 标记方法已过时
public void oldMethod() {}

@SuppressWarnings("unchecked") // 抑制编译器警告
public void suppressWarning() {}

内置注解

作用于代码的注解

注解 作用
@Override 检查方法是否正确重写父类方法
@Deprecated 标记已过时,使用时产生警告
@SuppressWarnings 抑制编译器警告
@FunctionalInterface 标记函数式接口(只能有一个抽象方法)

元注解(用于定义注解的注解)

元注解 作用
@Retention 指定注解的保留策略
@Target 指定注解可以用在哪里
@Documented 注解会出现在Javadoc中
@Inherited 子类自动继承父类的注解
@Repeatable 允许同一位置重复使用注解(Java 8+)

注解的保留策略

@Retention 决定注解在什么阶段可用:

1
2
3
@Retention(RetentionPolicy.SOURCE)   // 只在源码中,编译后丢弃
@Retention(RetentionPolicy.CLASS) // 保留到class文件,运行时不可见(默认)
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射读取
策略 编译后保留 运行时可见 典型用途
SOURCE @Override、Lombok
CLASS 字节码工具处理
RUNTIME 框架注解、反射处理

注解的作用目标

@Target 指定注解可以标注在哪里:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)            // 类、接口、枚举
@Target(ElementType.FIELD) // 字段
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解类型
@Target(ElementType.PACKAGE) // 包
@Target(ElementType.TYPE_PARAMETER) // 泛型参数(Java 8+)
@Target(ElementType.TYPE_USE) // 任何类型使用处(Java 8+)

// 可以指定多个目标
@Target({ElementType.FIELD, ElementType.METHOD})

自定义注解

基本语法

1
2
3
4
5
6
7
8
9
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
// 注解元素(类似方法声明)
String value(); // 必填元素
int count() default 1; // 可选元素,有默认值
String[] tags() default {}; // 数组类型
Class<?> targetClass() default Void.class; // Class类型
}

使用自定义注解

1
2
3
4
5
6
7
public class Demo {
@MyAnnotation(value = "test", count = 5, tags = {"a", "b"})
public void method1() {}

@MyAnnotation("simple") // 只有value时可以省略"value="
public void method2() {}
}

注解元素的类型限制

注解元素只能是以下类型:

  • 基本类型(int, long, double等)
  • String
  • Class
  • 枚举
  • 其他注解
  • 以上类型的数组

不能是普通对象、集合等复杂类型。

通过反射读取注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Log {
String value() default "";
}

class Service {
@Log("用户登录")
public void login(String username) {
System.out.println(username + " logged in");
}
}

// 读取注解
public class AnnotationReader {
public static void main(String[] args) throws Exception {
Method method = Service.class.getMethod("login", String.class);

// 检查是否有注解
if (method.isAnnotationPresent(Log.class)) {
Log log = method.getAnnotation(Log.class);
System.out.println("日志描述: " + log.value());
}

// 获取所有注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}

实战:实现简单的参数校验

定义校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface NotNull {
String message() default "字段不能为空";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "长度不符合要求";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Range {
int min() default Integer.MIN_VALUE;
int max() default Integer.MAX_VALUE;
String message() default "数值不在范围内";
}

实体类使用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User {
@NotNull(message = "用户名不能为空")
@Length(min = 3, max = 20, message = "用户名长度3-20")
private String username;

@NotNull
@Length(min = 6, message = "密码至少6位")
private String password;

@Range(min = 0, max = 150, message = "年龄不合法")
private int age;

// getter/setter...
}

校验器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Validator {
public static void validate(Object obj) throws Exception {
Class<?> clazz = obj.getClass();

for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(obj);

// 检查@NotNull
if (field.isAnnotationPresent(NotNull.class)) {
NotNull notNull = field.getAnnotation(NotNull.class);
if (value == null) {
throw new IllegalArgumentException(notNull.message());
}
}

// 检查@Length
if (field.isAnnotationPresent(Length.class)) {
Length length = field.getAnnotation(Length.class);
if (value instanceof String) {
String str = (String) value;
if (str.length() < length.min() || str.length() > length.max()) {
throw new IllegalArgumentException(length.message());
}
}
}

// 检查@Range
if (field.isAnnotationPresent(Range.class)) {
Range range = field.getAnnotation(Range.class);
if (value instanceof Number) {
int num = ((Number) value).intValue();
if (num < range.min() || num > range.max()) {
throw new IllegalArgumentException(range.message());
}
}
}
}
}
}

使用校验器

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
User user = new User();
user.setUsername("ab"); // 长度不够
user.setPassword("123456");
user.setAge(200); // 超出范围

try {
Validator.validate(user);
} catch (Exception e) {
System.out.println("校验失败: " + e.getMessage());
}
}

实战:实现简单的AOP日志

定义日志注解

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface LogExecutionTime {
String value() default "";
}

使用动态代理实现AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class LogProxy implements InvocationHandler {
private final Object target;

public LogProxy(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取目标类的真实方法(代理接口的方法可能没有注解)
Method targetMethod = target.getClass().getMethod(
method.getName(), method.getParameterTypes());

if (targetMethod.isAnnotationPresent(LogExecutionTime.class)) {
LogExecutionTime annotation = targetMethod.getAnnotation(LogExecutionTime.class);
String name = annotation.value().isEmpty() ?
method.getName() : annotation.value();

long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long duration = System.currentTimeMillis() - start;

System.out.printf("[%s] 执行耗时: %dms%n", name, duration);
return result;
}

return method.invoke(target, args);
}

@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class[]{interfaceType},
new LogProxy(target)
);
}
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Calculator {
int add(int a, int b);
int multiply(int a, int b);
}

class CalculatorImpl implements Calculator {
@LogExecutionTime("加法运算")
public int add(int a, int b) {
return a + b;
}

@LogExecutionTime
public int multiply(int a, int b) {
try { Thread.sleep(100); } catch (Exception e) {}
return a * b;
}
}

public static void main(String[] args) {
Calculator calc = LogProxy.createProxy(new CalculatorImpl(), Calculator.class);
calc.add(1, 2); // [加法运算] 执行耗时: 0ms
calc.multiply(3, 4); // [multiply] 执行耗时: 102ms
}

可重复注解(Java 8+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedules {
Schedule[] value();
}

// 可重复注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Schedules.class) // 指定容器注解
@interface Schedule {
String cron();
}

// 使用:可以重复标注
class Task {
@Schedule(cron = "0 0 8 * * ?") // 每天8点
@Schedule(cron = "0 0 20 * * ?") // 每天20点
public void execute() {}
}

// 读取
Method method = Task.class.getMethod("execute");
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);
for (Schedule s : schedules) {
System.out.println(s.cron());
}

常见框架注解原理

框架 注解示例 处理方式
Spring @Autowired 运行时反射注入依赖
Spring @Transactional 动态代理实现事务
JUnit @Test 运行时扫描并执行测试方法
Lombok @Data 编译时生成getter/setter
Jackson @JsonProperty 运行时控制JSON序列化

总结

概念 说明
@interface 定义注解
@Retention 指定保留策略(SOURCE/CLASS/RUNTIME)
@Target 指定作用目标(TYPE/FIELD/METHOD等)
default 为注解元素设置默认值
isAnnotationPresent() 检查是否有某注解
getAnnotation() 获取注解实例
@Repeatable 允许重复使用注解

注解的核心价值:

  • 声明式编程:用注解描述”做什么”,框架处理”怎么做”
  • 减少样板代码:配置信息内聚在代码中,无需XML
  • 编译时检查@Override等注解帮助发现错误