本文最后更新于 2025-11-27,文章内容可能已经过时。

🌟 什么是Java注解?

Java注解(Annotation)是JDK 1.5引入的元数据机制,用于向代码添加补充信息,不影响程序执行逻辑。它就像给代码贴的"便利贴",告诉编译器、框架或工具:"这个方法很重要"、"这个类需要特殊处理"。

💡 关键区别:注释(// 或 /* */)是给程序员看的,编译器会忽略;注解是给程序(编译器、框架、工具)看的,程序可以通过代码读取注解信息,从而做出相应处理。

🔍 一、内置注解:你每天都在用

1. @Override:我重写了父类方法

class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃小鱼干");
    }
}

作用:告诉编译器这个方法是重写父类的方法。如果方法名写错了(比如eatt()),编译器会报错提醒。

2. @Deprecated:我过时了,别用我

class OldCalculator {
    @Deprecated
    public int add(int a, int b) {
        return a + b;
    }
    
    public int plus(int a, int b) {
        return a + b;
    }
}

public class Test {
    public static void main(String[] args) {
        OldCalculator calc = new OldCalculator();
        calc.add(1, 2); // IDE会提示:add()已过时,建议用plus()
        calc.plus(1, 2); // 推荐使用
    }
}

作用:标记方法/类已过时,提醒开发人员不要使用。

3. @SuppressWarnings:忽略警告

public class Test {
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        int a = 10; // 忽略"未使用变量"警告
    }
}

作用:抑制特定的编译器警告,比如unchecked、deprecation等。

🧩 二、元注解:定义注解的注解

元注解是用于定义注解的注解,就像"给标签贴标签"。

元注解作用常见值
@Retention注解保留策略SOURCE、CLASS、RUNTIME
@Target注解作用范围TYPE、METHOD、FIELD等
@Inherited注解是否可继承true/false
@Documented是否包含在Javadoc中true/false
@Retention(RetentionPolicy.RUNTIME)  // 运行时保留
@Target(ElementType.METHOD)          // 作用于方法
@Inherited                           // 可继承
@Documented                          // 包含在Javadoc中
public @interface MyAnnotation {
    String value() default "默认值";
}

💡 重要提示:如果要通过反射读取注解,必须设置@Retention(RetentionPolicy.RUNTIME)

🛠️ 三、自定义注解:打造专属标签

1. 基本语法

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Important {
    int level() default 1; // 1-3级,1最不重要,3最重要
    String desc() default "未描述";
}

2. 使用自定义注解

public class UserService {
    
    @Important(level = 3, desc = "核心业务方法")
    public User getUserById(Long id) {
        // 业务逻辑
        return new User(id, "John Doe");
    }
    
    @Important(level = 1)
    public void log(String message) {
        System.out.println("日志: " + message);
    }
}

3. 通过反射读取注解

public class AnnotationReader {
    public static void main(String[] args) {
        // 获取类上的注解
        Class<UserService> clazz = UserService.class;
        Important classAnnotation = clazz.getAnnotation(Important.class);
        System.out.println("类上的重要性级别: " + (classAnnotation != null ? classAnnotation.level() : "无"));
        
        // 获取方法上的注解
        try {
            Method method = clazz.getMethod("getUserById", Long.class);
            Important methodAnnotation = method.getAnnotation(Important.class);
            System.out.println("方法上的重要性级别: " + methodAnnotation.level());
            System.out.println("方法描述: " + methodAnnotation.desc());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

💡 四、实际应用场景与详细示例

1. 框架配置:Spring的依赖注入

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User findUserById(Long id) {
        return userRepository.findById(id);
    }
}

说明:@Service和@Autowired是Spring框架的注解,用于简化配置和依赖注入,无需XML配置。

2. 单元测试:JUnit的测试标记

import org.junit.Test;
import static org.junit.Assert.*;

public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(4, calc.add(2, 2));
    }
    
    @Test
    public void testSubtract() {
        Calculator calc = new Calculator();
        assertEquals(2, calc.subtract(5, 3));
    }
}

说明:@Test标记测试方法,测试运行器会自动执行这些方法并生成测试报告。

3. 持久化:JPA的实体映射

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username")
    private String username;
    
    @Column(name = "email")
    private String email;
    
    // getters and setters
}

说明:@Entity、@Table、@Id、@Column等注解描述数据库映射关系,框架可以自动生成SQL语句。

4. 日志记录:自定义注解 + AOP

// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
    String value() default "方法执行";
}

// 使用注解
@Service
public class UserService {
    @LogExecution("获取用户信息")
    public User getUserById(Long id) {
        // 业务逻辑
        return new User(id, "John Doe");
    }
}

// AOP切面
@Aspect
@Component
public class LogAspect {
    @Before("@annotation(logExecution)")
    public void logMethod(JoinPoint joinPoint, LogExecution logExecution) {
        System.out.println("执行方法: " + joinPoint.getSignature().getName() + " - " + logExecution.value());
    }
}

说明:通过自定义@LogExecution注解和AOP,实现方法执行日志记录,无需在每个方法中写日志代码。

5. 权限校验:自定义注解 + 拦截器

// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequireRole {
    String[] roles() default {};
}

// 使用注解
@RestController
public class UserController {
    @RequireRole(roles = {"ADMIN", "MANAGER"})
    @GetMapping("/users")
    public List<User> getAllUsers() {
        // 业务逻辑
        return userService.getAllUsers();
    }
}

// 拦截器
@Component
public class RoleInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            RequireRole requireRole = method.getMethodAnnotation(RequireRole.class);
            if (requireRole != null) {
                // 检查用户角色
                String[] roles = requireRole.roles();
                // 检查当前用户是否在角色列表中
                if (!isUserInRoles(roles)) {
                    response.sendError(HttpServletResponse.SC_FORBIDDEN, "Insufficient permissions");
                    return false;
                }
            }
        }
        return true;
    }
}

说明:通过自定义@RequireRole注解和拦截器,实现基于角色的权限控制,无需在每个方法中写权限检查代码。

🌈 五、高级应用:可重复注解(Java 8+)

Java 8引入了可重复注解,允许在同一个程序元素上多次使用相同类型的注解。

// 定义可重复注解
@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Role {
    String roleName();
}

// 定义容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Roles {
    Role[] value();
}

// 使用
public class RoleTest {
    @Role(roleName = "admin")
    @Role(roleName = "user")
    public void doSomething() {
        // 业务逻辑
    }
}

说明:Java 8之前,需要使用注解容器来实现重复注解,现在可以直接使用@Repeatable。

💎 六、总结:为什么注解这么重要?

  1. 减少样板代码:如Spring的@Service替代XML配置
  2. 提高代码可读性:通过注解直接表达设计意图
  3. 实现声明式编程:如AOP、权限控制等
  4. 框架集成:Spring、Hibernate等框架大量使用注解
  5. 元数据管理:为代码添加丰富的元数据信息

一句话总结:注解不是魔法,但用好它,代码会像被施了魔法一样简洁优雅。


💎Java注解完整自定义注解示例

🌟 示例目标

创建一个@LogExecution注解,用于:

  • 记录方法执行时间
  • 标记方法重要级别(info/warning/error)
  • 在控制台输出日志

📜 完整代码示例

package com.nn3n;

import java.lang.annotation.*;

// ================== 1. 定义自定义注解 ==================
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface LogExecution {
    String description() default "方法执行";

    int level() default 1;

    boolean enable() default true;
}
package com.nn3n;

public interface UserService {
    User getUserDetails(Long userId);
    User createUser(String name);
    void deleteUser(Long userId);
}
package com.nn3n;

import java.util.concurrent.TimeUnit;

// ================== 3. 实现类(实现接口) ==================
public class UserServiceImpl implements UserService {

    @LogExecution(description = "获取用户详情", level = 2)
    @Override
    public User getUserDetails(Long userId) {
        System.out.println("正在获取用户详情... 参数: " + userId);
        simulateDelay(150);
        return new User(userId, "John Doe");
    }

    @LogExecution(description = "创建新用户", level = 1, enable = true)
    @Override
    public User createUser(String name) {
        System.out.println("正在创建新用户... 参数: " + name);
        simulateDelay(80);
        return new User(1L, name);
    }

    @LogExecution(description = "删除用户", level = 3, enable = false) // 已禁用日志
    @Override
    public void deleteUser(Long userId) {
        System.out.println("正在删除用户... 参数: " + userId);
        simulateDelay(200);
    }

    private void simulateDelay(int milliseconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(milliseconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
package com.nn3n;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// ================== 4. 实体类 ==================
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
}
package com.nn3n;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

// ================== 2. 定义日志处理器(动态代理实现) ==================
public class LogInvocationHandler implements InvocationHandler {
    private final Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ====== 添加调试信息 ======
        Method targetMethod;
        try {
            targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("方法未找到: " + method.getName(), e);
        }

        System.out.println("DEBUG: 调用方法 [" + method.getName() + "]");
        System.out.println("DEBUG: 方法是否有@LogExecution注解? " + targetMethod.isAnnotationPresent(LogExecution.class));

        if (targetMethod.isAnnotationPresent(LogExecution.class)) {
            LogExecution logAnnotation = targetMethod.getAnnotation(LogExecution.class);
            System.out.println("logAnnotation.enable() : " + logAnnotation.enable());
            if (!logAnnotation.enable()) {
                System.out.println("DEBUG: 日志已禁用,跳过");
                return method.invoke(target, args);
            }

            System.out.println("\n" + "=".repeat(50));
            System.out.println("【日志开始】 " + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            System.out.println("方法: " + method.getName());
            System.out.println("描述: " + logAnnotation.description());
            System.out.println("级别: " + getLogLevel(logAnnotation.level()));

            long startTime = System.currentTimeMillis();

            try {
                Object result = method.invoke(target, args);

                long executionTime = System.currentTimeMillis() - startTime;
                System.out.println("执行时间: " + executionTime + "ms");
                System.out.println("【日志结束】" + "=".repeat(50));

                return result;
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            }
        }

        return method.invoke(target, args);
    }

    private String getLogLevel(int level) {
        return switch (level) {
            case 1 -> "INFO";
            case 2 -> "WARNING";
            case 3 -> "ERROR";
            default -> "UNKNOWN";
        };
    }
}
package com.nn3n;

import java.lang.reflect.Proxy;

// ================== 5. 主程序:演示自动日志记录 ==================
public class AnnotationDemo {
    public static void main(String[] args) {
        System.out.println("===== 开始演示自动日志记录 =====");
        
        // 创建原始服务对象
        UserService userService = new UserServiceImpl();
        
        // 创建代理对象(关键!自动触发日志)
        UserService proxyUserService = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),
            new Class<?>[] { UserService.class },
            new LogInvocationHandler(userService)
        );
        
        // 正常调用方法(日志自动触发!)
        proxyUserService.getUserDetails(1001L);
        proxyUserService.createUser("Jane Smith");
        proxyUserService.deleteUser(1002L); // 此方法日志已禁用
        
        System.out.println("\n===== 演示结束 =====");
    }
}

🧪 运行结果示例

===== 开始演示自动日志记录 =====
DEBUG: 调用方法 [getUserDetails]
DEBUG: 方法是否有@LogExecution注解? true
logAnnotation.enable() : true

==================================================
【日志开始】 2025-11-27 15:35:23
方法: getUserDetails
描述: 获取用户详情
级别: WARNING
正在获取用户详情... 参数: 1001
执行时间: 155ms
【日志结束】
==================================================
DEBUG: 调用方法 [createUser]
DEBUG: 方法是否有@LogExecution注解? true
logAnnotation.enable() : true

==================================================
【日志开始】 2025-11-27 15:35:23
方法: createUser
描述: 创建新用户
级别: INFO
正在创建新用户... 参数: Jane Smith
执行时间: 95ms
【日志结束】
==================================================
DEBUG: 调用方法 [deleteUser]
DEBUG: 方法是否有@LogExecution注解? true
logAnnotation.enable() : false
DEBUG: 日志已禁用,跳过
正在删除用户... 参数: 1002

===== 演示结束 =====

💡 关键点说明

  1. @LogExecution注解的enable=false让deleteUser()方法不记录日志
  2. level=2显示为WARNING,level=1显示为INFO
  3. 执行时间精确到毫秒

🔍 为什么这个示例这么实用?

实际应用场景(真实项目中常见):

  1. 性能监控:标记关键方法,记录执行时间
  2. 日志分级:根据level设置不同日志级别
  3. 功能开关:通过enable属性动态开启/关闭日志
  4. 无侵入式:业务代码无需写日志逻辑,注解自动处理

与传统日志对比

// 传统方式(侵入式,代码臃肿)
public User getUserDetails(Long userId) {
    long start = System.currentTimeMillis();
    System.out.println("[INFO] 获取用户详情开始");
    // 业务逻辑
    User user = new User(userId, "John Doe");
    System.out.println("[INFO] 获取用户详情结束,耗时: " + (System.currentTimeMillis() - start) + "ms");
    return user;
}
// 使用注解(无侵入,代码简洁)
@LogExecution(description = "获取用户详情", level = 2)
public User getUserDetails(Long userId) {
    // 业务逻辑(无日志代码!)
    return new User(userId, "John Doe");
}

🛠️ 如何在Spring Boot中实现?

在Spring Boot项目中,我们通常用AOP实现类似功能,比反射更高效:

// Spring AOP切面实现
@Aspect
@Component
public class LogAspect {
    
    @Before("@annotation(logExecution)")
    public void logBefore(JoinPoint joinPoint, LogExecution logExecution) {
        System.out.println("【AOP日志】" + joinPoint.getSignature().getName() + " - " + logExecution.description() + " (级别: " + logExecution.level() + ")");
    }
    
    @AfterReturning(pointcut = "@annotation(logExecution)", returning = "result")
    public void logAfter(JoinPoint joinPoint, LogExecution logExecution, Object result) {
        System.out.println("【AOP日志】执行完成,结果: " + result);
    }
}

Spring优势:AOP在方法执行前后自动触发,无需手动调用,性能更好