简介
在 java 中,注解(annotation) 是一种特殊的元数据(metadata),可以标记在类、方法、字段、参数等元素上,用于传递额外信息。自定义注解允许开发者根据业务需求定义自己的注解,结合反射、aop 等技术实现灵活的功能(如日志记录、权限校验、参数校验等)。
一、注解的核心概念
1. 注解的本质
注解本质是一个继承了 java.lang.annotation.annotation 接口的特殊接口,编译器会自动为其生成实现类。
2. 元注解(meta-annotation)
元注解是用于修饰注解的注解,定义了自定义注解的生命周期、作用范围等属性。java 内置 4 个核心元注解:
| 元注解 | 作用说明 |
|---|---|
@retention | 指定注解的生命周期(必须声明) |
@target | 指定注解可作用的元素类型(如类、方法、字段等,必须声明) |
@documented | 标记注解会被 javadoc 工具提取到文档中(可选) |
@inherited | 标记注解可被子类继承(仅对类注解有效,可选) |
@repeatable(java8+) | 允许注解在同一元素上重复使用(可选) |
二、自定义注解的步骤
1. 定义注解(使用@interface关键字)
格式:
// 元注解
元注解1
元注解2
...
public @interface 注解名 {
// 注解属性(本质是接口的抽象方法)
类型 属性名() default 默认值; // 可选默认值,无默认值则使用时必须指定
}关键说明:
- 注解属性的类型只能是:基本类型(
int/string/boolean等)、枚举、注解、数组(上述类型的数组)。 - 若属性名是
value,且只有一个属性时,使用注解可省略属性名(直接写值)。 - 数组类型的属性赋值时,若只有一个元素,可省略
{}。
2. 常用元注解详解
(1)@retention:指定生命周期
取值(retentionpolicy 枚举):
source:仅保留在源代码中,编译时删除(如@override)。class:保留到编译后的.class文件中,但 jvm 运行时不加载(默认值)。runtime:保留到 jvm 运行时,可通过反射获取(最常用,如日志、校验注解)。
(2)@target:指定作用范围
取值(elementtype 枚举):
type:作用于类、接口、枚举。method:作用于方法。field:作用于字段(成员变量)。parameter:作用于方法参数。constructor:作用于构造器。annotation_type:作用于注解本身。local_variable:作用于局部变量。module:作用于模块(java9+)。
三、自定义注解示例
下面通过 3 个常见场景,演示自定义注解的定义与使用。
示例 1:基础注解(无属性)
定义一个标记型注解(仅用于标记,无额外属性)。
1. 定义注解
import java.lang.annotation.*;
// 生命周期:运行时(可通过反射获取)
@retention(retentionpolicy.runtime)
// 作用范围:类、方法
@target({elementtype.type, elementtype.method})
// 生成 javadoc 文档
@documented
public @interface logannotation {
// 无属性(标记型注解)
}2. 使用注解
// 作用于类
@logannotation
public class userservice {
// 作用于方法
@logannotation
public void adduser(string username) {
system.out.println("添加用户:" + username);
}
}示例 2:带属性的注解(参数校验)
定义一个用于字段非空校验的注解,支持自定义提示信息。
1. 定义注解
import java.lang.annotation.*;
@retention(retentionpolicy.runtime)
@target(elementtype.field) // 仅作用于字段
@documented
public @interface notnull {
// 注解属性:提示信息,默认值为 "字段不能为空"
string message() default "字段不能为空";
}2. 使用注解(标记实体类字段)
public class user {
@notnull(message = "用户名不能为空")
private string username;
@notnull(message = "年龄不能为空")
private integer age;
// getter/setter/构造器省略
}3. 解析注解(通过反射实现校验逻辑)
import java.lang.reflect.field;
public class validationutil {
// 校验对象的注解字段
public static void validate(object obj) throws illegalaccessexception {
// 获取对象的类
class<?> clazz = obj.getclass();
// 获取所有字段(包括私有字段)
field[] fields = clazz.getdeclaredfields();
for (field field : fields) {
// 允许访问私有字段
field.setaccessible(true);
// 判断字段是否有 @notnull 注解
if (field.isannotationpresent(notnull.class)) {
// 获取注解实例
notnull notnull = field.getannotation(notnull.class);
// 获取字段值
object value = field.get(obj);
// 校验逻辑:值为 null 则抛出异常
if (value == null) {
throw new illegalargumentexception(notnull.message());
}
}
}
}
// 测试
public static void main(string[] args) throws illegalaccessexception {
user user1 = new user(null, 20); // 用户名 null
try {
validate(user1);
} catch (illegalargumentexception e) {
system.out.println(e.getmessage()); // 输出:用户名不能为空
}
user user2 = new user("张三", null); // 年龄 null
try {
validate(user2);
} catch (illegalargumentexception e) {
system.out.println(e.getmessage()); // 输出:年龄不能为空
}
}
}示例 3:重复注解(java8+)
允许同一元素上重复使用注解(如多角色权限校验)。
1. 定义 “容器注解”(用于存放重复的注解)
import java.lang.annotation.*;
@retention(retentionpolicy.runtime)
@target(elementtype.method)
public @interface roles {
// 容器注解必须包含一个返回目标注解数组的属性(名称固定为 value)
role[] value();
}2. 定义可重复注解(使用@repeatable关联容器注解)
import java.lang.annotation.*;
@retention(retentionpolicy.runtime)
@target(elementtype.method)
@repeatable(roles.class) // 指定容器注解
public @interface role {
// 注解属性:角色名称
string value();
}3. 使用重复注解
public class permissionservice {
// 重复使用 @role 注解(等价于 @roles({@role("admin"), @role("manager")}))
@role("admin")
@role("manager")
public void deletedata() {
system.out.println("删除数据成功");
}
}4. 解析重复注解
import java.lang.reflect.method;
public class permissionutil {
public static void checkrole(method method, string userrole) {
// 判断方法是否有 @roles 注解(重复注解会被包装为容器注解)
if (method.isannotationpresent(roles.class)) {
roles roles = method.getannotation(roles.class);
role[] rolearr = roles.value();
// 校验用户角色是否在允许范围内
boolean haspermission = false;
for (role role : rolearr) {
if (role.value().equals(userrole)) {
haspermission = true;
break;
}
}
if (!haspermission) {
throw new securityexception("无权限执行该操作");
}
}
}
// 测试
public static void main(string[] args) throws nosuchmethodexception {
method method = permissionservice.class.getmethod("deletedata");
// 测试有权限(admin)
checkrole(method, "admin");
system.out.println("admin 执行成功");
// 测试无权限(user)
try {
checkrole(method, "user");
} catch (securityexception e) {
system.out.println(e.getmessage()); // 输出:无权限执行该操作
}
}
}四、自定义注解的常见应用场景
- 日志记录:标记需要打印日志的方法,通过 aop 拦截并记录请求参数、返回值、执行时间等。
- 权限校验:标记需要权限的接口 / 方法,通过拦截器或 aop 校验用户角色。
- 参数校验:标记实体类字段,校验非空、长度、格式等(如 spring 的
@notnull、@size)。 - 事务管理:标记需要事务的方法(如 spring 的
@transactional)。 - 代码生成:通过注解标记类 / 字段,结合代码生成工具(如 mybatis generator)生成模板代码。
五、注意事项
- 注解本身不包含业务逻辑,需通过反射或aop 解析注解并执行相应逻辑。
@retention(retentionpolicy.runtime)是运行时解析注解的前提,若仅用于编译时检查(如@override),可设为source。- 注解属性的默认值不能是
null,需指定合理的默认值(如空字符串、默认枚举值)。 - 重复注解需配合容器注解使用(java8+ 特性),低版本需手动包装为容器注解。
通过自定义注解,开发者可以将通用逻辑(如日志、校验)与业务逻辑解耦,提高代码的复用性和可读性。实际开发中,spring、mybatis 等框架大量使用自定义注解简化配置(如 @controller、@mapper),核心原理与本文示例一致。
到此这篇关于java自定义注解的文章就介绍到这了,更多相关java自定义注解内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论