一、注解基础概念
1.1 什么是注解
专业解释:注解(Annotation)是Java 5引入的一种元数据形式,它提供了一种向代码添加结构化元数据的方法,这些元数据可以被编译器、开发工具或运行时环境读取和处理。
通俗理解:注解就像是代码的"便利贴",你可以在代码上贴这些"便利贴"来告诉编译器、框架或其他工具一些额外的信息,这些信息可以用来改变它们处理代码的方式。
1.2 注解的作用
作用领域 | 说明 | 示例 |
---|---|---|
编译检查 | 告诉编译器进行特定检查 | @Override |
代码生成 | 在编译时生成额外代码 | Lombok的@Data |
运行时处理 | 在运行时通过反射读取并处理 | Spring的@Autowired |
文档生成 | 为代码生成文档 | @Deprecated |
1.3 元注解(Meta-Annotation)
专业解释:元注解是用来注解其他注解的注解,Java提供了5种标准元注解(@Target, @Retention, @Documented, @Inherited, @Repeatable)以及Spring等框架扩展的元注解。
关键特点:
- 定义注解的基本行为
- 控制注解的使用范围和生命周期
- 为自定义注解提供元数据
通俗理解:元注解就像是注解的"说明书",它告诉Java这个注解可以用在什么地方(类/方法/字段等)、什么时候有效(源码/编译时/运行时)、是否出现在文档中、能否被继承等。
1.4 自定义注解(Custom Annotation)
专业解释:开发者根据需求定义的注解类型,使用@interface关键字声明,可以包含成员变量和默认值。
关键特点:
- 使用@interface关键字定义
- 可以包含基本类型、String、Class、枚举、注解及数组类型的元素
- 通过元注解配置其行为
通俗理解:自定义注解就像是你自己设计的标签,你可以决定这个标签能贴在什么地方(类/方法等)、包含什么信息(定义的元素)、以及这些信息怎么被使用。
1.5 注解处理器(Annotation Processor)
专业解释:处理注解的工具或程序,可以在编译时(通过AbstractProcessor)或运行时(通过反射API)读取和处理注解信息。
关键特点:
- 编译时处理生成额外代码或资源
- 运行时处理通过反射机制
- Lombok、MapStruct等工具的核心实现机制
通俗理解:注解处理器就像是注解的"解析器",它知道怎么读取注解上的信息,并根据这些信息做出相应的动作(如生成代码、配置行为等)。
1.6 四者关系图解
[元注解] → 定义 [自定义注解] 的行为
↓
[自定义注解] 标注在代码元素上
↓
[注解处理器] 在编译时/运行时处理这些注解
↓
[动态代理] 可能被注解处理器用来创建代理对象实现AOP等功能
二、Java内置注解
2.1 基本内置注解
@Override
- 作用:指示方法覆盖了父类中的方法
- 使用场景:当子类重写父类方法时使用
- 好处:
- 帮助编译器检查是否确实正确地重写了父类方法
- 提高代码可读性
public class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
public class Dog extends Animal {
@Override // 明确表示这是重写父类方法
public void eat() {
System.out.println("Dog is eating");
}
}
@Deprecated
- 作用:标记某个程序元素(类、方法、字段等)已过时
- 使用场景:当某个元素不再推荐使用,但为了向后兼容仍保留时
- 效果:
- 使用被标记的元素时编译器会产生警告
- 通常配合JavaDoc的
@deprecated
标签使用
public class OldTechnology {
@Deprecated
public void floppyDisk() {
System.out.println("This is outdated technology");
}
public void usbDrive() {
System.out.println("This is modern technology");
}
}
public class Main {
public static void main(String[] args) {
OldTechnology tech = new OldTechnology();
tech.floppyDisk(); // 编译器会显示警告:方法已过时
tech.usbDrive(); // 正常使用
}
}
@SuppressWarnings
- 作用:抑制编译器警告
- 常用参数值:
"unchecked"
- 抑制未经检查的类型转换警告"deprecation"
- 抑制使用过时API的警告"all"
- 抑制所有警告
public class WarningExample {
@SuppressWarnings("unchecked") // 抑制未经检查的转换警告
public List<String> getStrings() {
return new ArrayList(); // 这里没有指定泛型类型,通常会产生警告
}
}
2.2 元注解
(用于注解其他注解的注解)
@Target - 指定注解可以应用的位置
@Target(ElementType.METHOD) // 这个注解只能用在方法上
public @interface MethodOnlyAnnotation {
}
@Retention - 指定注解的保留策略
@Retention(RetentionPolicy.RUNTIME) // 这个注解在运行时可用
public @interface RuntimeAnnotation {
}
@Documented - 是否包含在JavaDoc中
@Documented // 这个注解会出现在JavaDoc中
public @interface DocumentedAnnotation {
}
@Inherited - 是否允许子类继承
@Inherited // 这个注解会被子类继承
public @interface InheritableAnnotation {
}
三、自定义注解
3.1 创建自定义注解
// 定义一个可以用于方法和类上的注解,在运行时可用
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProgrammerInfo {
String name(); // 必须提供的属性
String level() default "Junior"; // 可选属性,默认值为"Junior"
String[] languages(); // 数组属性
}
3.2 使用自定义注解
@ProgrammerInfo(
name = "张三",
level = "Senior",
languages = {"Java", "Python", "JavaScript"}
)
public class SoftwareEngineer {
@ProgrammerInfo(
name = "张三",
languages = {"Java"}
)
public void writeCode() {
System.out.println("Writing code...");
}
}
3.3 处理自定义注解(通过反射)
public class AnnotationProcessor {
public static void processAnnotations(Object obj) {
Class<?> clazz = obj.getClass();
// 处理类上的注解
if (clazz.isAnnotationPresent(ProgrammerInfo.class)) {
ProgrammerInfo info = clazz.getAnnotation(ProgrammerInfo.class);
System.out.println("类注解信息:");
System.out.println("程序员: " + info.name());
System.out.println("级别: " + info.level());
System.out.println("语言: " + String.join(", ", info.languages()));
}
// 处理方法上的注解
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(ProgrammerInfo.class)) {
ProgrammerInfo info = method.getAnnotation(ProgrammerInfo.class);
System.out.println("\n方法 " + method.getName() + " 的注解信息:");
System.out.println("程序员: " + info.name());
System.out.println("级别: " + info.level());
System.out.println("语言: " + String.join(", ", info.languages()));
}
}
}
public static void main(String[] args) {
SoftwareEngineer engineer = new SoftwareEngineer();
processAnnotations(engineer);
}
}
四、注解的高级应用
4.1 注解处理器(编译时处理)
// 该注解指定注解处理器支持的注解类型,这里表明该处理器支持 "com.example.ProgrammerInfo" 注解
@SupportedAnnotationTypes("com.example.ProgrammerInfo")
// 该注解指定注解处理器支持的 Java 源代码版本,这里指定为 Java 8
@SupportedSourceVersion(SourceVersion.RELEASE_8)
// 定义一个名为 ProgrammerInfoProcessor 的注解处理器类,继承自 AbstractProcessor 类
public class ProgrammerInfoProcessor extends AbstractProcessor {
/**
* 该方法是注解处理器的核心处理方法,会在编译时被调用,用于处理注解。
*
* @param annotations 包含当前处理轮次中发现的所有注解元素的集合。
* @param roundEnv 提供当前编译轮次的环境信息,可用于获取被注解的元素。
* @return 如果该处理器处理了这些注解,返回 true;否则返回 false。
*/
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// 遍历所有发现的注解元素
for (TypeElement annotation : annotations) {
// 遍历被当前注解标注的所有元素
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
// 从被注解的元素上获取 ProgrammerInfo 注解的实例
ProgrammerInfo info = element.getAnnotation(ProgrammerInfo.class);
// 获取被注解元素的简单名称,并转换为字符串
String elementName = element.getSimpleName().toString();
// 检查注解中的 level 属性是否为 "Senior",并且 languages 属性中是否不包含 "Java"
if (info.level().equals("Senior") && !Arrays.asList(info.languages()).contains("Java")) {
// 如果不满足条件,使用注解处理环境的消息器打印错误消息
processingEnv.getMessager().printMessage(
// 错误消息的类型为 ERROR
Diagnostic.Kind.ERROR,
// 错误消息的内容,提示高级程序员必须掌握 Java 语言
"高级程序员 " + info.name() + " 必须掌握Java语言",
// 关联的被注解元素,方便编译器定位错误位置
element
);
}
}
}
// 表示该处理器处理了这些注解
return true;
}
}
4.2 重复注解(Java 8+)
// 1. 定义可重复注解的容器
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedules {
Schedule[] value();
}
// 2. 定义可重复的注解
@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedule {
String time();
String task();
}
// 3. 使用重复注解
public class TaskManager {
@Schedule(time = "09:00", task = "晨会")
@Schedule(time = "14:00", task = "代码评审")
public void dailyTasks() {
// ...
}
}
// 4. 处理重复注解
public class ScheduleProcessor {
public static void process(Object obj) throws Exception {
Method method = obj.getClass().getMethod("dailyTasks");
// 获取重复注解
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);
for (Schedule schedule : schedules) {
System.out.println("时间: " + schedule.time() + ", 任务: " + schedule.task());
}
// 或者通过容器注解获取
Schedules container = method.getAnnotation(Schedules.class);
if (container != null) {
for (Schedule schedule : container.value()) {
System.out.println("[容器]时间: " + schedule.time() + ", 任务: " + schedule.task());
}
}
}
}
五、注解最佳实践与常见陷阱
6.1 最佳实践
- 合理使用内置注解:优先使用Java和框架提供的内置注解
- 明确注解目标:使用
@Target
限制注解的使用范围 - 合理设置保留策略:根据需求选择
SOURCE
、CLASS
或RUNTIME
- 提供默认值:为自定义注解的属性提供合理的默认值
- 保持简洁:注解应该简单明了,避免过于复杂
6.2 常见陷阱
陷阱 | 问题 | 解决方案 |
---|---|---|
过度使用注解 | 代码可读性降低 | 只在必要时使用注解 |
运行时注解性能问题 | 反射操作影响性能 | 缓存反射结果或使用编译时处理 |
注解继承问题 | @Inherited 只对类有效 | 明确在子类/方法上重新声明注解 |
注解属性类型限制 | 只能使用基本类型、String、Class等 | 使用字符串表示复杂结构 |
注解处理顺序 | 多个注解的处理顺序不确定 | 使用@Order 或明确处理顺序 |
6.3 自定义注解实战案例:权限控制
// 1. 定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String[] value(); // 需要的权限
Logical logical() default Logical.AND; // 权限检查逻辑:AND或OR
}
public enum Logical {
AND, OR
}
// 2. 定义切面处理权限
@Aspect
@Component
public class PermissionAspect {
@Autowired
private AuthService authService;
@Around("@annotation(requirePermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint,
RequirePermission requirePermission) throws Throwable {
String[] permissions = requirePermission.value();
Logical logical = requirePermission.logical();
boolean hasPermission;
if (logical == Logical.AND) {
hasPermission = authService.hasAllPermissions(permissions);
} else {
hasPermission = authService.hasAnyPermission(permissions);
}
if (!hasPermission) {
throw new AccessDeniedException("没有访问权限");
}
return joinPoint.proceed();
}
}
// 3. 在Controller中使用
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@GetMapping("/users")
@RequirePermission({"user:read", "admin:access"}) // 需要同时拥有这两个权限
public ResponseEntity<List<User>> getAllUsers() {
// 获取所有用户
}
@PostMapping("/users")
@RequirePermission(value = {"user:write", "user:create"}, logical = Logical.OR) // 需要任一权限
public ResponseEntity<User> createUser(@RequestBody User user) {
// 创建用户
}
}
七、总结
Java注解是一个强大的工具,它从简单的标记接口发展到现在的复杂元数据处理机制。通过本文的学习,你应该已经掌握了:
- 注解的基本概念和Java内置注解
- 如何创建和处理自定义注解
- 注解的高级用法如重复注解和注解处理器
- Spring Boot中各种核心注解的应用
- 注解的最佳实践和常见陷阱
Java注解是代码世界的“秘密便签”!贴对了是开挂神器,贴错秒变“程序看不懂的谜语”,主打一个玄学操作!
悄悄说:点关注的宝,后台送你一句彩虹屁🌈。
想获取更多干货 / 精彩内容吗?微信搜索公众号 “Eric的技术杂货库” ,点击关注,第一时间解锁最新动态!