注解(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) @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) @Target(ElementType.TYPE_USE)
@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; }
|
使用自定义注解
1 2 3 4 5 6 7
| public class Demo { @MyAnnotation(value = "test", count = 5, tags = {"a", "b"}) public void method1() {}
@MyAnnotation("simple") 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;
}
|
校验器实现
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);
if (field.isAnnotationPresent(NotNull.class)) { NotNull notNull = field.getAnnotation(NotNull.class); if (value == null) { throw new IllegalArgumentException(notNull.message()); } }
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()); } } }
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); calc.multiply(3, 4); }
|
可重复注解(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 * * ?") @Schedule(cron = "0 0 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等注解帮助发现错误