臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

一、💡 一句话理解

核心结论

注解就是贴在代码上的标签。它用来说明“这段代码有什么特点或规则”,但标签本身通常不会执行任何事情。

例如,@Override 表示“这是重写的方法”。自定义注解则是我们自己设计一种标签,再由程序读取标签并做对应处理。

二、🧭 注解是什么

2.1 Java 自带的注解

@Override
public String toString() {
    return "示例";
}
 
@Deprecated
public void oldMethod() {
    // 表示这个方法不建议继续使用
}
注解含义
@Override要求编译器检查该方法是否真的重写了父类或接口的方法。
@Deprecated提醒调用者:这段代码已经过时,不建议再使用。
@SuppressWarnings让编译器忽略指定类型的警告。

可以把注解看作“附在代码上的说明书”。它让编译器、框架或我们自己写的程序知道该如何对待这段代码。

2.2 注解本身不等于功能

下面的 @NeedLogin 是本篇示例自行定义的自定义注解,不是 Java 自带注解。它标在方法上:

@NeedLogin
public void viewProfile() {
    System.out.println("查看个人资料");
}

这行代码不会自动完成登录校验。@NeedLogin 只是一个标签;还需要一个专门的类读取它,并在调用方法前检查用户是否已登录。

写上 @NeedLogin  →  程序读取 @NeedLogin  →  程序决定是否校验登录
      标签                读取标签               执行规则

三、⚙️ 怎样定义一个自定义注解

3.1 最简单的写法

创建文件 NeedLogin.java:

// public @interface 表示:定义一个名为 NeedLogin 的注解类型。
public @interface NeedLogin {
}

使用它:

public class UserService {
 
    @NeedLogin
    public void viewProfile() {
        System.out.println("查看个人资料");
    }
}

3.2 用 @Target 限制标签贴在哪里

注解可以写在类、方法、字段等不同位置。@Target 用来限制位置。

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
 
// 只允许 @NeedLogin 标在方法上。
@Target(ElementType.METHOD)
public @interface NeedLogin {
}

常用位置:

写法可标注的位置
ElementType.TYPE类、接口、枚举。
ElementType.METHOD方法。
ElementType.FIELD字段。
ElementType.PARAMETER方法参数。

3.3 用 @Retention 决定何时还能读到标签

如果希望程序在运行时读取注解,要使用 RetentionPolicy.RUNTIME。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // 程序运行时仍可通过反射读取
public @interface NeedLogin {
}
保留策略适用情况
SOURCE只在源码阶段使用,例如编译器检查。
CLASS写入编译后的 class 文件;这是默认值。
RUNTIME程序运行时可读取,常用于框架和自定义规则。

四、🚀 完整小例子:读取注解并执行规则

下面用三个类演示完整过程:标签声明规则,LoginChecker 读取标签,Demo 验证结果。

4.1 前置准备

新建一个空目录,将以下三个文件放在同一目录。需要 JDK 8 或更高版本。

4.2 定义标签和业务类

文件 NeedLogin.java:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
// 这个标签只能用于方法,并且运行时可以被读取。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {
}

文件 UserService.java:

public class UserService {
 
    // 这表示:调用该方法前,检查程序应要求用户已登录。
    @NeedLogin
    public void viewProfile() {
        System.out.println("正在查看个人资料");
    }
 
    // 没有标签的方法,不需要登录校验。
    public void viewHelp() {
        System.out.println("正在查看帮助页面");
    }
}

4.3 写一个读取标签的类

文件 LoginChecker.java:

import java.lang.reflect.Method;
 
public class LoginChecker {
 
    /**
     * 调用指定方法前,检查该方法是否有 @NeedLogin 标签。
     *
     * @param target 业务对象
     * @param methodName 要调用的方法名
     * @param loggedIn 当前用户是否已登录
     */
    public void invoke(Object target, String methodName, boolean loggedIn)
            throws ReflectiveOperationException {
 
        // 从业务对象中找到对应的公开、无参数方法。
        Method method = target.getClass().getMethod(methodName);
 
        // isAnnotationPresent 用来判断方法是否标了指定注解。
        boolean needLogin = method.isAnnotationPresent(NeedLogin.class);
 
        // 标了标签且未登录:直接拒绝,不执行业务方法。
        if (needLogin && !loggedIn) {
            System.out.println("请先登录");
            return;
        }
 
        // 其他情况:通过反射执行真实业务方法。
        method.invoke(target);
    }
}

4.4 启动和验证

文件 Demo.java:

public class Demo {
 
    public static void main(String[] args) throws ReflectiveOperationException {
        UserService userService = new UserService();
        LoginChecker checker = new LoginChecker();
 
        // 未登录访问需要登录的方法:被拦截。
        checker.invoke(userService, "viewProfile", false);
 
        // 已登录访问同一方法:允许执行。
        checker.invoke(userService, "viewProfile", true);
 
        // 未登录访问没有标签的方法:直接执行。
        checker.invoke(userService, "viewHelp", false);
    }
}

在该目录运行:

javac *.java
java Demo

预期输出:

请先登录
正在查看个人资料
正在查看帮助页面

这里真正规定“@NeedLogin 有什么效果”的,是 LoginChecker 中的判断代码,而不是注解定义本身。

五、📌 注解一般用于什么

用途常见例子说明
编译检查@Override让编译器帮忙发现错误。
框架配置@Component、@Service告诉框架如何管理一个类。
接口映射@RequestMapping声明一个方法对应哪个请求地址。
参数校验@NotNull、@NotBlank声明参数不能为空。
自定义规则@NeedLogin、@Audit标记需要登录、记录日志等规则。

六、📝 总结

  • 注解是标签,不是自动执行的代码。
  • 用 public @interface 定义自定义注解。
  • @Target 限制标签能贴在哪里;@Retention(RUNTIME) 让程序运行时能读取标签。
  • 注解的具体含义由读取它的类决定。

记忆句:注解负责说明,程序负责执行。

七、📚 官方资料