Java注解

一、注解的核心作用:不止是 "代码标签"

注解(Annotation)是 Java 的元数据机制,附加在类、方法、字段等程序元素上,为编译器、框架或工具提供额外信息。它不直接参与业务逻辑执行,但能通过解析工具转化为实际功能,核心作用可概括为 3 点:

  1. 简化配置,解耦逻辑

替代传统 XML 或硬编码配置,让配置与代码紧密结合。例如 Java Web 的@WebServlet("/hello")直接绑定 URL 与 Servlet,无需在web.xml中配置;Spring 的@Autowired自动注入依赖,省去手动获取 Bean 的繁琐步骤。

  1. 实现通用功能复用

将日志记录、权限校验、参数校验等通用功能封装为注解,一次定义可全局复用。例如通过@LogRecord注解,无需在每个接口手动写日志代码,即可自动记录调用信息。

  1. 编译时校验与提示

辅助编译器检查代码逻辑,减少错误。例如@Override强制校验方法是否真的重写父类方法,@Deprecated标记过时元素,提醒开发者避免使用。

注解与注释的核心区别

特性 注解(Annotation) 注释(Comment)
阅读对象 程序(编译器 / 框架 / 工具) 开发者
作用 提供配置 / 校验 / 标记信息 解释代码逻辑
生命周期 可保留到运行时 编译时被忽略

二、元注解:自定义注解的 "规则基石"

元注解是修饰注解的注解,定义自定义注解的使用范围、生命周期等规则,Java 内置 4 种核心元注解,是自定义注解的必备基础:

  1. @Target:限制注解使用范围

指定注解能修饰的程序元素,常用值:

  • ElementType.METHOD:仅用于方法(如日志、权限注解);

  • ElementType.TYPE:用于类、接口、枚举(如 Spring 的@Controller);

  • ElementType.FIELD:用于字段(如参数校验注解);

  • 支持多范围配置:@Target({ElementType.TYPE, ElementType.METHOD})

  • @Retention:指定注解生命周期​

决定注解保留阶段,核心值:

  • RetentionPolicy.RUNTIME:保留到运行时,可通过反射解析(自定义注解常用);

  • RetentionPolicy.SOURCE:仅保留在源文件,编译时删除(如@Override);

  • RetentionPolicy.CLASS:保留到字节码,运行时不可反射(默认值)。

  • @Documented:纳入 API 文档​

让注解信息能被javadoc工具提取到文档中,方便开发者查阅注解用途。

  1. @Inherited:允许子类继承

子类可自动继承父类的注解(仅对类注解有效),例如父类用@Service注解,子类无需重复声明即可被 Spring 识别。

三、自定义注解实战:从 0 到 1 实现

掌握元注解后,即可动手实现自定义注解。以下以 "接口日志记录" 为例,完整演示注解定义、使用、解析全流程。

步骤 1:定义自定义注解

java 复制代码
import java.lang.annotation.*;

/**
 * 接口日志记录注解
 */
@Target(ElementType.METHOD) // 仅用于方法
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,支持反射
@Documented // 纳入API文档
public @interface LogRecord {
    // 接口功能描述(必填)
    String value();

    // 是否记录请求参数(默认true)
    boolean recordParams() default true;

    // 是否记录返回结果(默认true)
    boolean recordResult() default true;
}
  • 注解参数格式:返回值类型 参数名() [default 默认值];

  • 无默认值的参数(如value()),使用时必须显式赋值;

  • value()为特殊参数,赋值时可省略 "value="(如@LogRecord("用户查询"))。

步骤 2:使用自定义注解

在目标接口方法上添加注解,标记需要记录日志的方法:

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    // 标记日志记录:记录参数和结果
    @GetMapping("/user/query")
    @LogRecord(value = "用户查询接口")
    public User queryUser(@RequestParam Long userId) {
        // 业务逻辑:查询用户信息
        User user = new User();
        user.setUserId(userId);
        user.setUserName("张三");
        return user;
    }

    // 标记日志记录:不记录返回结果
    @GetMapping("/user/count")
    @LogRecord(value = "用户数量统计", recordResult = false)
    public Integer countUser() {
        return 100; // 模拟用户总数
    }
}

// 简单用户实体类
class User {
    private Long userId;
    private String userName;

    // getter/setter省略
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }

    @Override
    public String toString() {
        return "User{userId=" + userId + ", userName='" + userName + "'}";
    }
}

步骤 3:解析注解 ------ 基于 AOP 实现无侵入

注解需通过解析工具生效,这里用 Spring AOP 切面统一处理,无需修改业务代码:

(1)引入依赖(Spring Boot 项目)

java 复制代码
<!-- Spring AOP依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 日志依赖 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
</dependency>

(2)实现 AOP 切面解析注解

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;

@Aspect // 标记为切面类
@Component // 交给Spring管理
public class LogRecordAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogRecordAspect.class);

    // 切入点:拦截所有带@LogRecord注解的方法
    @Pointcut("@annotation(com.example.annotation.LogRecord)")
    public void logPointcut() {}

    // 环绕通知:方法执行前后记录日志
    @Around("logPointcut()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 获取方法和注解信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogRecord logAnnotation = method.getAnnotation(LogRecord.class);

        // 2. 提取注解参数和请求信息
        String desc = logAnnotation.value();
        boolean recordParams = logAnnotation.recordParams();
        boolean recordResult = logAnnotation.recordResult();
        String methodName = method.getDeclaringClass().getName() + "." + method.getName();
        Object[] params = joinPoint.getArgs();

        // 3. 记录方法执行前日志
        logger.info("【{}】调用开始 - 时间:{},方法:{},参数:{}",
                desc, new Date(), methodName,
                recordParams ? Arrays.toString(params) : "不记录");

        // 4. 执行目标方法(业务逻辑)
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // 执行方法并获取结果
        long costTime = System.currentTimeMillis() - startTime;

        // 5. 记录方法执行后日志
        logger.info("【{}】调用结束 - 耗时:{}ms,结果:{}",
                desc, costTime,
                recordResult ? result.toString() : "不记录");

        return result;
    }
}

步骤 4:测试效果

启动项目后访问接口,日志输出如下:

txt 复制代码
INFO  [http-nio-8080-exec-1] c.e.annotation.LogRecordAspect: 【用户查询接口】调用开始 - 时间:Wed Nov 29 17:00:00 CST 2025,方法:com.example.controller.UserController.queryUser,参数:[1001]
INFO  [http-nio-8080-exec-1] c.e.annotation.LogRecordAspect: 【用户查询接口】调用结束 - 耗时:8ms,结果:User{userId=1001, userName='张三'}

INFO  [http-nio-8080-exec-2] c.e.annotation.LogRecordAspect: 【用户数量统计】调用开始 - 时间:Wed Nov 29 17:01:00 CST 2025,方法:com.example.controller.UserController.countUser,参数:[]
INFO  [http-nio-8080-exec-2] c.e.annotation.LogRecordAspect: 【用户数量统计】调用结束 - 耗时:3ms,结果:不记录

四、自定义注解核心要点

  1. 单一职责:一个注解只处理一件事(如日志注解仅记录日志),避免逻辑混乱;

  2. 合理设置生命周期:需运行时解析的注解,必须指定@Retention(RetentionPolicy.RUNTIME);

  3. 简化参数设计:核心参数无默认值,非核心参数提供默认值,减少使用成本;

  4. 无侵入实现:通过 AOP 或反射解析注解,不侵入业务代码,降低耦合。

五、总结

注解的核心价值是 "解耦与复用"------ 用元数据替代繁琐配置,用通用注解封装重复功能。自定义注解的关键是:通过元注解明确规则,通过反射 / AOP 实现解析逻辑。掌握注解后,能大幅简化开发流程,让代码更简洁、易维护。

相关推荐
a***13141 小时前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端
axihaihai1 小时前
maven的构建问题
java·linux·maven
武子康1 小时前
大数据-170 Elasticsearch 7.3.0 三节点集群实战:目录/参数/启动到联机
大数据·后端·elasticsearch
稚辉君.MCA_P8_Java1 小时前
DeepSeek Java 多线程打印的12种实现方法
java·linux·后端·架构·maven
代码不停1 小时前
Java栈题目练习
java·开发语言
864记忆1 小时前
在IDEA中如何使用翻译插件?
java·ide·intellij-idea
w***48821 小时前
Springboot 3项目整合Knife4j接口文档(接口分组详细教程)
java·spring boot·后端
k***45991 小时前
SpringBoot【实用篇】- 测试
java·spring boot·后端
FeiHuo565151 小时前
微信个人号API二次开发:如何提高开发效率和质量
java·开发语言·python·php