news 2026/8/24 5:50:12

Java注解深度解析:从核心原理到Spring实战与自定义注解开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java注解深度解析:从核心原理到Spring实战与自定义注解开发

1. 项目缘起:为什么我要整理这份Java注解大全

干了十多年Java开发,从早期的SSH框架到现在的Spring Boot微服务,注解(Annotation)这东西真是无处不在。我记得刚入行那会儿,看到代码里一堆@Override@Deprecated还觉得挺新鲜,后来项目里到处都是@Controller@Autowired@Transactional,再到现在各种自定义注解满天飞。说实话,注解用好了是真方便,能让代码简洁得像首诗;但要是没吃透,踩起坑来也真是要命,比如那个经典的@Transactional不生效问题,我猜不少人都遇到过。

这个“Java注解大全”项目,其实是我给自己搞的一个复习笔记。市面上资料很多,但要么太散,要么太旧,要么就是光讲语法不讲“为什么”。我寻思着,不如自己动手整理一份,把工作中常用的、面试常问的、以及那些容易让人迷糊的注解都捋清楚。这不是一份死板的API文档,而是融合了我个人实战经验和深度思考的“内功心法”。我会持续往里面加东西,所以叫“不断增加”。希望这份笔记不仅能帮我巩固知识,也能给正在学习或复习Java的你,提供一个有温度、有深度的参考。

2. 注解的本质:超越标记的元编程利器

很多人把注解简单地理解为“标签”或者“标记”,这没错,但只看到了第一层。在我看来,注解是Java元编程(Metaprogramming)能力的一种体现。所谓元编程,就是“编写能操作程序的程序”。注解本身不直接参与业务逻辑,但它能向编译器、JVM或者各种框架工具(如Spring、Lombok)传递信息,从而影响它们的行为,间接地改变了程序的编译过程、运行时状态甚至生成新的代码。

2.1 注解的底层机制与生命周期

理解注解,必须从它的生命周期(RetentionPolicy)说起。这是注解定义时@Retention元注解指定的,它决定了注解信息能“活”多久:

  • SOURCE:仅存在于源代码阶段。编译成.class文件后就被丢弃了。最典型的例子是@Override@Deprecated。它们的作用就是给编译器看的。@Override告诉编译器:“喂,检查一下我这个方法是不是真的重写了父类方法,别让我写错了。”@Deprecated则警告开发者:“这个方法过时了,别再用啦,我有更好的替代品。” 一旦编译完成,它们的任务就结束了。

  • CLASS:注解信息会被保留在.class文件中,但不会被加载到JVM里。这是默认的保留策略。这个策略比较尴尬,用得不多。一些在编译后还需要被其他工具(如字节码分析工具)处理,但运行时不需要的注解可能会用这个。

  • RUNTIME:这是最常用、最强大的策略。注解信息不仅存在于.class文件中,还会在JVM加载类时被读取到内存中。因此,我们可以在程序运行时,通过反射(Reflection)API动态地获取注解信息,并据此做出相应的处理。Spring的@Controller@Autowired,JUnit的@Test,都是RUNTIME级别的。

实操心得:当你自定义一个注解时,第一个要问自己的问题就是:“我这个注解是给谁用的?编译器?某个编译期工具?还是运行时的我自己/框架?” 答案决定了你的@Retention应该选哪个。99%的业务场景下,如果你希望注解在运行时起作用,那就选RUNTIME

2.2 元注解:注解的注解

就像类是对象的模板,元注解(Meta-Annotation)就是注解的模板。JDK内置了5个元注解,用来修饰其他注解的定义:

  1. @Retention:上面刚讲过,定义注解的生命周期。
  2. @Target:指定注解可以应用在哪些Java元素上。这是个ElementType数组,常用值有:
    • TYPE:类、接口、枚举
    • FIELD:字段(包括枚举常量)
    • METHOD:方法
    • PARAMETER:方法参数
    • CONSTRUCTOR:构造器
    • LOCAL_VARIABLE:局部变量
    • ANNOTATION_TYPE:注解类型(用于元注解)
    • PACKAGE:包 例如,@Autowired@Target{ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD},意味着它可以用在这四个地方。
  3. @Documented:被它修饰的注解,会被javadoc工具提取到API文档中。如果你希望自定义的注解出现在文档里,就加上它。
  4. @Inherited:这是一个容易被忽略但有时很有用的元注解。它表示注解是否具有继承性。如果一个类用了被@Inherited修饰的注解,那么它的子类会自动继承这个注解(前提是子类没有被其他注解覆盖)。注意,这只对@Target(ElementType.TYPE)的类注解有效,对方法、字段注解无效。
  5. @Repeatable(JDK 1.8引入):允许同一个注解在同一个地方重复使用。在JDK 1.8之前,同一个注解在一个地方只能用一次。有了@Repeatable,你需要为注解定义一个“容器注解”。例如,你可以定义一个@Schedule注解,并用@Repeatable(Schedules.class)修饰,这样你就可以在同一个方法上写多个@Schedule了。

3. JDK内置核心注解深度解析

别看JDK自带的注解不多,但每一个都是精华,理解透了能避免很多低级错误。

3.1@Override:不仅仅是“重写”检查

public class Parent { public void doSomething(String input) { // ... } } public class Child extends Parent { @Override // 正确:方法签名匹配 public void doSomething(String input) { // ... } @Override // 编译错误!方法名拼写错误,实际上不是重写 public void doSomethng(String input) { // ... } @Override // 编译错误!参数类型不匹配,是重载(Overload)不是重写 public void doSomething(Integer input) { // ... } }

为什么一定要用@Override

  1. 安全网:如上例,它能帮你第一时间发现拼写错误或签名不匹配,避免你以为重写了父类方法,实际上却定义了一个新方法,导致多态行为不符合预期。这种Bug非常隐蔽。
  2. 提高可读性:明确告诉阅读代码的人,这是一个重写方法,不是类自己新加的方法。
  3. 应对父类变更:如果父类将来删除了这个方法,所有加了@Override的子类方法会立刻编译报错,迫使你同步修改,保证了代码的同步性。

注意事项:从JDK 1.6开始,@Override不仅可以用于重写父类方法,还可以用于实现接口方法。但在1.5及以前,用于实现接口方法会编译报错。如果你的项目还在用很老的JDK(虽然不推荐),需要注意这一点。

3.2@Deprecated:优雅的API退役声明

标记一个类、方法或字段已过时,不建议继续使用。

/** * 老旧的连接方式,性能较差。 * 请使用新的 {@link #connectWithPool()} 方法。 * @deprecated 自 v2.0 起废弃,将在 v3.0 中移除。 */ @Deprecated(since = "2.0", forRemoval = true) // JDK 9+ 增强 public void connect() { // ... 旧逻辑 }
  • since:从哪个版本开始废弃。
  • forRemoval:为true表示计划在将来移除,警告级别更高。
  • 最佳实践务必在javadoc中使用@deprecated标签(注意小写d)说明废弃原因和替代方案。光加一个注解是极其不负责的。

3.3@SuppressWarnings:压制编译器警告

用来有选择地关闭编译器警告。警告通常是有用的,但在某些确知安全的场景下,警告信息会干扰视线。

@SuppressWarnings("unchecked") // 我知道这个强制转换是安全的 public List<String> getRawList() { List rawList = someLegacyCode(); return (List<String>) rawList; // 未经检查的转换,会产生警告 } // 可以同时压制多种警告 @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public void someMethod() { // ... }

常用参数值

  • "unchecked":抑制未经检查的转换警告(如使用泛型集合)。
  • "rawtypes":抑制使用原始类型的警告。
  • "deprecation":抑制使用已废弃API的警告。
  • "all":抑制所有警告(慎用!)。

重要原则@SuppressWarnings的作用范围应尽可能小。不要把它加在整个类上,最好只加在方法上,甚至只加在局部变量声明语句上。并且,一定要写注释说明为什么这里可以安全地忽略警告。

3.4@FunctionalInterface:函数式接口的契约

JDK 1.8引入,标记一个接口是函数式接口(Functional Interface)。函数式接口指有且仅有一个抽象方法的接口(不包括default方法和从Object类继承的方法)。

@FunctionalInterface // 加上它,编译器会帮你检查是否符合函数式接口定义 public interface MyCalculator { int calculate(int a, int b); // 唯一的抽象方法 // default方法不影响 default void printResult(int result) { System.out.println("Result: " + result); } // 从Object继承的方法不影响 @Override boolean equals(Object obj); } // 如果没有@FunctionalInterface,但符合定义,它依然是函数式接口,可以用Lambda。 // 但加上注解是更好的实践,相当于一份契约和文档,防止他人意外添加第二个抽象方法。

为什么需要它?它是一种编译期的“契约检查”和“文档说明”。告诉编译器和代码阅读者:“我设计这个接口就是为了给Lambda表达式或方法引用用的,你别乱加抽象方法破坏这个约定。”

3.5@SafeVarargs:泛型可变参数的安全承诺

这个注解有点冷门,但涉及泛型和可变参数时很重要。在Java中,泛型信息在运行时会被擦除。当可变参数遇到泛型时,容易产生“堆污染”(Heap Pollution)警告。

public class SafeVarargsDemo { // 没有 @SafeVarargs,编译器会警告:Possible heap pollution from parameterized vararg type T @SafeVarargs // 加上它,告诉编译器:“我保证这个方法体内部不会对泛型数组进行不安全的操作。” public static <T> void printAll(T... elements) { for (T element : elements) { System.out.println(element); } } // 不安全的例子:将外部传入的泛型数组暴露出去或进行错误赋值 @SafeVarargs // 错误使用!这里实际上不安全。 public static <T> void unsafeMerge(List<T>... lists) { Object[] array = lists; // 泛型数组向上转型为Object[],这是允许的 array[0] = Arrays.asList(42); // 污染!将Integer的List放入原本是List<String>的数组中 // 如果外部调用 unsafeMerge(listOfStrings, listOfIntegers); 就会在后续操作中抛出ClassCastException } }

使用条件@SafeVarargs只能用在staticfinalprivate的构造器或方法上。因为非static、非final、非private的方法可能被重写,无法保证其安全性。

4. Lombok注解:告别样板代码的“魔法”

Lombok通过注解在编译期自动生成字节码,极大减少了getter、setter、构造器等样板代码。但“魔法”用不好,也会带来困惑。

4.1 基础注解:@Data,@Getter/@Setter,@ToString

  • @Data:一个复合注解,相当于@Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor。非常方便,但要注意它默认使用所有非static字段生成equalshashCode,如果实体有关联对象(如List<Order>),直接使用可能导致栈溢出。这时需要用@EqualsAndHashCode.Exclude排除某些字段,或者不用@Data,自己组合需要的注解。
  • @AllArgsConstructor/@NoArgsConstructor/@RequiredArgsConstructor
    • @RequiredArgsConstructor:只为final字段和标记了@NonNull的字段生成构造器。在Spring的构造函数注入场景下非常有用。
    @Service @RequiredArgsConstructor // 为 final 修饰的依赖生成构造器 public class MyService { private final UserRepository userRepository; // 通过构造器注入 private final OrderService orderService; // 不需要写构造器,Lombok会生成:MyService(UserRepository ur, OrderService os){...} }

4.2 易错点与排查技巧

问题1:@RequiredArgsConstructor注解后@Lazy注解没用了?这是一个经典的Spring和Lombok结合使用的坑。场景是:你有循环依赖,想用@Lazy来延迟注入打破循环。

@Component @RequiredArgsConstructor public class ServiceA { private final @Lazy ServiceB serviceB; // 期望延迟注入 // Lombok生成的构造器:ServiceA(ServiceB serviceB) { this.serviceB = serviceB; } // 问题:Spring在调用这个构造器创建Bean时,会直接传入一个真实的ServiceB代理吗?不会! }

原因@Lazy是Spring的注解,它的生效依赖于Spring的注入方式(如字段注入@Autowired、setter注入)。而@RequiredArgsConstructor生成的是普通的构造器,Spring在通过构造器注入时,会先去容器里找ServiceB的Bean。如果ServiceB还没初始化(可能正等着ServiceA),就会触发循环依赖错误。@Lazy注解在字段上,但注入是通过构造器参数进行的,Spring对构造器参数的@Lazy支持需要特殊处理(在构造器参数上直接加@Lazy),而Lombok不会在生成的构造器参数上自动添加@Lazy

解决方案

  1. 放弃@RequiredArgsConstructor,手动写构造器,并在参数上加@Lazy
    @Component public class ServiceA { private final ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { // 手动写,参数加@Lazy this.serviceB = serviceB; } }
  2. 使用字段注入或setter注入(不推荐,破坏了不可变性):
    @Component public class ServiceA { @Lazy @Autowired private ServiceB serviceB; }
  3. 重新设计,消除循环依赖(最推荐):循环依赖通常是设计有问题的信号。

问题2:java: You aren‘t using a compiler supported by lombok, so lombok will not work这个错误通常出现在IDE中。Lombok需要在编译期工作,所以IDE(如IntelliJ IDEA、Eclipse)需要安装对应的Lombok插件并启用注解处理(Annotation Processing)。

  • IntelliJ IDEA:确保安装了“Lombok”插件(File -> Settings -> Plugins)。然后,在 Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors 中,勾选 “Enable annotation processing”。
  • Eclipse:需要将lombok.jar作为“Java Agent”运行一次,它会自动安装到Eclipse中。
  • Maven/Gradle:确保依赖配置正确,并且IDE的构建工具配置指向了正确的JDK。

问题3:java: jps incremental annotation process is disabled. Compilation results on partial recompilations may be inaccurate.这是一个警告,不是错误。意思是JPS(Java Compiler Server)的增量注解处理被禁用了。在部分重新编译(比如只改了一个文件)时,编译结果可能不准确。这通常发生在IntelliJ IDEA中,当Lombok等注解处理器与IDEA的增量编译模式有冲突时。

  • 处理:一般不影响运行。如果担心,可以在IDEA的 Settings -> Build, Execution, Deployment -> Compiler 中,将 “Build process heap size” 调大一些(比如1024或2048),或者尝试重启IDEA。也可以尝试禁用 “Use compiler process for remote external annotations” 等选项试试。最根本的解决方式是等待IDEA或Lombok插件的更新。

5. Spring/Spring Boot注解生态全景

Spring的注解体系极其庞大,是Java后端开发的核心。我们可以将其分层理解。

5.1 核心容器与Bean管理

这一层注解负责定义和组装Bean。

注解作用核心区别与选择
@Component通用泛型组件,标记一个类为Spring容器管理的Bean。最基础的注解。其他如@Service@Repository本质都是@Component,加了特定的语义和可能额外的功能(如@Repository的异常转换)。
@Service标记业务逻辑层(Service层)的组件。语义化注解。代码可读性更好,Spring目前没有为其提供特殊处理,但未来可能会。
@Repository标记数据访问层(DAO层)的组件。关键点:它启用了Spring的数据访问异常转换,将特定持久化技术(如JPA、Hibernate)抛出的异常统一转换为Spring的DataAccessException层次结构,使异常处理与具体技术解耦。
@Controller标记Web控制层(MVC)组件。用于Spring MVC,配合@RequestMapping等使用。
@RestController@Controller+@ResponseBody用于RESTful Web服务,直接返回JSON/XML数据,不找视图解析器。
@Configuration标记一个类为配置类,替代XML配置文件。其内部使用@Bean注解的方法,用于显式声明Bean。
@Bean@Configuration@Component类的方法上,声明一个Bean。用于导入第三方库的类,或需要复杂初始化逻辑的Bean。方法名默认即为Bean名称。

依赖注入注解

  • @Autowired:Spring提供的,按类型自动装配。可以用在字段、构造器、setter方法、普通方法上。如果找到多个同类型Bean,需配合@Qualifier指定名称。
  • @Resource(JSR-250):Java标准注解,默认按名称装配,名称找不到再按类型。name属性指定Bean名。
  • @Inject(JSR-330):另一个Java标准,功能类似@Autowired,但无required属性。
  • @Value:注入外部配置(如application.properties)的值或SpEL表达式。

面试高频题:@Resource与构造函数注入哪个效率高?这个问题有点“关公战秦琼”。@Resource是字段/方法注入的一种形式。而构造函数注入是另一种注入方式。

  1. 从运行时效率看:微乎其微的差异,可以忽略不计。依赖注入在容器启动时完成,对运行时性能无影响。
  2. 从设计模式与代码质量看构造函数注入被广泛推荐
    • 不可变性:依赖字段可以用final修饰,确保Bean在构造后不可变,线程安全。
    • 明确依赖:一眼就能看出这个类需要哪些依赖,没有隐藏的依赖。
    • 易于测试:不需要Spring容器,可以直接在单元测试中用new创建对象并传入mock依赖。
    • 避免循环依赖:Spring官方文档指出,构造函数注入能暴露循环依赖问题(会直接报错),而字段注入可能掩盖问题,导致运行时出现不可预知的行为。
    • @Resourcevs@Autowired:两者效率无差别。@Resource是JSR标准,@Autowired是Spring特有但功能更丰富(如required=false)。在纯Spring环境下用@Autowired更常见。结论:优先使用构造函数注入(结合Lombok的@RequiredArgsConstructor)。对于可选依赖或循环依赖(应尽量避免),可考虑setter注入。字段注入(@Autowired/@Resourceon field)因其缺点,在现代Spring开发中已不推荐作为首选。

5.2 Web MVC与RESTful注解

注解作用
@RequestMapping通用请求映射。可指定URL、HTTP方法(GET/POST等)、请求头等。
@GetMapping/ **@PostMapping**等@RequestMapping的快捷方式,分别对应特定HTTP方法。代码更简洁。
@RequestParam绑定请求参数到方法参数。可指定参数名、是否必需、默认值。
@PathVariable绑定URL模板变量到方法参数。如/users/{id}
@RequestBody将HTTP请求体(如JSON)绑定到方法参数对象上。
@ResponseBody将方法返回值直接写入HTTP响应体,不经过视图解析器。
@RestControllerAdvice(@ControllerAdvice)全局异常处理、数据绑定、数据预处理。@RestControllerAdvice@ControllerAdvice+@ResponseBody
@ExceptionHandler@ControllerAdvice或Controller内部,处理特定异常。
@CrossOrigin处理跨域请求(CORS)。

5.3 事务管理:@Transactional的深水区

这是面试和工作中的绝对重点和难点。

@Service public class OrderService { @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public void placeOrder(Order order) { // 1. 保存订单 orderRepository.save(order); // 2. 扣减库存 inventoryService.deduct(order.getProductId(), order.getQuantity()); // 如果deduct方法抛出异常,订单保存也会回滚 } }

核心属性解析

  • rollbackFor/noRollbackFor:指定哪些异常触发/不触发回滚。默认只在运行时异常(RuntimeException)和错误(Error)下回滚,受检异常(Exception)不回滚!这是一个巨坑。所以,如果你希望任何异常都回滚,通常要设置rollbackFor = Exception.class
  • propagation:事务传播行为。最常用的是REQUIRED(默认)和REQUIRES_NEW
    • REQUIRED:如果当前没有事务,就新建一个;如果已存在,就加入。这是最常用的。
    • REQUIRES_NEW:无论如何都新建一个事务,如果当前有事务,则将其挂起。适用于需要独立提交/回滚的子操作,比如记录日志,即使主业务失败,日志也要保存。
    • 其他如SUPPORTSMANDATORYNOT_SUPPORTEDNEVERNESTED,根据具体业务场景选用。
  • isolation:事务隔离级别。解决脏读、不可重复读、幻读问题。默认是数据库的默认级别(通常是READ_COMMITTED)。高隔离级别(如SERIALIZABLE)性能差,慎用。
  • readOnly:是否只读事务。设置为true可以给数据库一个优化提示(如MySQL会将事务设置为只读)。对于纯查询方法,建议加上。

@Transactional失效的常见场景(避坑指南)

  1. 方法非public@Transactional基于AOP(动态代理或CGLIB),非public方法代理无法切入。
  2. 自调用问题:在同一个类中,一个非事务方法A调用同一个类的事务方法B,事务不会生效。因为代理对象调用A,A内部调用B走的是this.B(),而不是代理对象的B(),绕过了代理。
    @Service public class MyService { public void outer() { this.inner(); // 事务不生效! } @Transactional public void inner() { // ... } }
    解决方案:将inner()方法移到另一个Service中,或者通过AopContext.currentProxy()获取当前代理对象再调用(需要开启exposeProxy = true),或者(不推荐)使用AspectJ的编译时织入。
  3. 异常被捕获:如果在方法内用try-catch吞掉了异常,事务管理器感知不到异常,就不会回滚。
    @Transactional public void method() { try { // 可能出错的业务 int i = 1 / 0; } catch (Exception e) { e.printStackTrace(); // 异常被捕获并处理,事务不会回滚! } }
    解决方案:在catch块中手动抛出运行时异常,或者将异常原样抛出。
  4. 数据库引擎不支持:比如MySQL的MyISAM引擎不支持事务,只有InnoDB支持。
  5. 未启用事务管理:在Spring Boot中,通常会自动配置。但在纯Spring项目中,需要在配置类上添加@EnableTransactionManagement

5.4 条件化装配与配置注解

  • @Conditional及其衍生注解:Spring Boot自动配置的灵魂。根据特定条件(如类路径下是否存在某个类、某个Bean是否存在、配置文件属性等)决定是否装配一个Bean或配置类。
    • @ConditionalOnClass:类路径下存在指定类时生效。
    • @ConditionalOnMissingBean:容器中不存在指定Bean时生效。
    • @ConditionalOnProperty:配置文件中存在指定属性且为特定值时生效。
  • @Profile:根据激活的Spring Profile来条件化地装配Bean。用于区分开发、测试、生产环境。
  • @ConfigurationProperties:将配置文件(如application.yml)中的属性批量绑定到一个Java对象的字段上,类型安全,支持嵌套、验证、宽松绑定(kebab-casecamelCase)。
  • @PostConstruct@PreDestroy(JSR-250):生命周期回调注解。分别标记在Bean初始化后和销毁前执行的方法。常用于资源初始化(如缓存预热)和清理(如关闭连接池)。

6. 测试相关注解

  • @RunWith(JUnit 4) /@ExtendWith(JUnit 5):指定测试运行器。Spring Boot测试通常用@RunWith(SpringRunner.class)@ExtendWith(SpringExtension.class)来启动Spring测试上下文。
  • @SpringBootTest:Spring Boot提供的集成测试注解,会加载完整的应用程序上下文。可以指定web环境(如WebEnvironment.MOCKRANDOM_PORT)。
  • @DataJpaTest,@WebMvcTest,@JsonTest等:Spring Boot的“切片测试”(Slice Test)注解,只加载应用程序的一部分,测试速度更快。
  • @MockBean/@SpyBean:在Spring的测试上下文中注入Mockito的mock或spy对象,用于模拟依赖。
  • @Test:标记测试方法。JUnit 5的@Testorg.junit.jupiter.api包下,与JUnit 4不同。

7. 自定义注解实战:打造你的元数据工具

当内置注解无法满足需求时,就需要自定义注解。步骤通常分为三步:定义注解使用注解处理注解

7.1 案例:基于注解的API接口耗时监控

1. 定义注解

import java.lang.annotation.*; /** * 用于监控方法执行耗时 */ @Target(ElementType.METHOD) // 只能用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时保留,因为我们要在AOP中处理 @Documented public @interface MonitorExecutionTime { /** * 监控的名称,默认用方法名 */ String value() default ""; /** * 时间单位,默认毫秒 */ TimeUnit unit() default TimeUnit.MILLISECONDS; }

2. 使用注解

@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") @MonitorExecutionTime("根据ID查询用户") // 使用自定义注解 public User getUser(@PathVariable Long id) { // ... 业务逻辑 return userService.findById(id); } }

3. 处理注解(通过Spring AOP)

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; @Aspect @Component public class MonitorExecutionTimeAspect { // 定义切点:所有被@MonitorExecutionTime注解的方法 @Around("@annotation(com.yourpackage.annotation.MonitorExecutionTime)") public Object monitorTime(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); MonitorExecutionTime annotation = method.getAnnotation(MonitorExecutionTime.class); String methodName = annotation.value().isEmpty() ? method.getName() : annotation.value(); TimeUnit unit = annotation.unit(); long startTime = System.nanoTime(); Object result; try { result = joinPoint.proceed(); // 执行原方法 } finally { long endTime = System.nanoTime(); long duration = endTime - startTime; long convertedDuration = unit.convert(duration, TimeUnit.NANOSECONDS); // 这里可以输出到日志、发送到监控系统等 System.out.printf("[监控] 方法 %s 执行耗时: %d %s%n", methodName, convertedDuration, unit.name().toLowerCase()); } return result; } }

通过这个简单的例子,你可以看到自定义注解结合AOP的强大之处:无侵入式地给系统添加横切关注点功能,如日志、监控、鉴权、缓存等。

7.2 注解处理器(Annotation Processor)简介

除了运行时通过反射处理注解,还可以在编译期处理注解,生成额外的源代码或资源文件。Lombok、MapStruct都是这么做的。这需要实现javax.annotation.processing.AbstractProcessor接口。编译期处理性能更好,但更复杂。对于大多数业务场景,运行时处理(AOP)已经足够。

8. 其他实用注解与疑难杂症

  • @SneakyThrows(Lombok):这个注解很“狡猾”。它用在方法上,可以让你在方法中抛出受检异常,而无需在方法签名上声明throws。Lombok会在编译时生成一个try-catch块,将受检异常包装成RuntimeException再抛出。慎用,因为它破坏了Java的受检异常机制,可能掩盖真正的错误。仅在你确信异常应该作为运行时异常处理,或者与某些框架(如Lambda表达式,它不允许抛出受检异常)交互时使用。
  • @NonNull(Lombok):生成空值检查。用在字段、参数或方法上,Lombok会生成相应的if (param == null) throw new NullPointerException(...);代码。有助于提前暴露NPE问题。
  • MyBatis-Plus多租户注解:通过@Interceptor和自定义注解(如@TenantId)结合,在SQL解析层面自动添加租户ID过滤条件,实现数据隔离。这需要深入理解MyBatis-Plus的插件机制。
  • Java虚拟线程(Virtual Threads)与注解:JDK 19+引入了虚拟线程(轻量级线程)。目前还没有直接相关的特定注解,但像@Async这样的异步注解,其背后的线程池执行器(TaskExecutor)可以配置为使用虚拟线程,从而获得极高的并发性能。这是未来高并发编程的一个重要方向。

这份“Java注解大全”就像我的技术地图,随着Java生态的演进和我自身经验的积累,它会不断被修订和扩充。注解的世界远不止于此,还有JPA的@Entity@Id,Jackson的@JsonIgnore,Validation的@NotNull等等。关键在于理解其核心思想:注解是元数据,它本身不做任何事,但配合特定的处理器(编译器、框架、AOP、注解处理器),就能产生强大的声明式编程效果。希望这份笔记能帮助你构建起自己的注解知识体系,在编码时更加得心应手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 5:50:00

单调递增数字问题的贪心算法解析与面试应用

1. 单调递增数字的面试场景解析 在技术面试中&#xff0c;单调递增数字问题频繁出现在算法考察环节。这个问题看似简单&#xff0c;却能够全面检验候选人对贪心算法、字符串处理以及边界条件处理的掌握程度。我曾在某次大厂终面中遇到这个问题的变种&#xff0c;面试官要求我在…

作者头像 李华
网站建设 2026/8/24 5:45:55

异步FIFO设计原理与实战:格雷码指针+多级同步器解决跨时钟域问题

1. 项目概述&#xff1a;为什么跨时钟域信号处理是FPGA工程师绕不开的硬骨头“跨时钟域信号处理方法&异步FIFO”——这八个字&#xff0c;几乎刻在每个FPGA工程师的工位贴纸上。我刚入行那会儿&#xff0c;在一家做视频采集卡的公司实习&#xff0c;调试一块带HDMI输入和DD…

作者头像 李华
网站建设 2026/8/24 5:44:12

6.28华为OD机试真题 新系统 - 统计不重叠区间的个数 (JavaPyCC++JsGo)

统计不重叠区间的个数 2026 华为OD机试真题 6月28日华为OD上机新系统考试真题 100 分题型 点击查看华为 OD 机试真题完整目录&#xff1a;2026最新华为OD机试新系统卷 双机位C卷 真题题库目录&#xff5c;全覆盖题库 逐点算法考点详解 题目描述 给定一个以二维数组 interva…

作者头像 李华
网站建设 2026/8/24 5:42:13

Bambu Studio:免费开源3D打印切片软件快速上手指南

Bambu Studio&#xff1a;免费开源3D打印切片软件快速上手指南 【免费下载链接】BambuStudio PC Software for BambuLab and other 3D printers 项目地址: https://gitcode.com/GitHub_Trending/ba/BambuStudio Bambu Studio 是一款免费的开源 3D 打印切片软件&#xff…

作者头像 李华
网站建设 2026/8/24 5:41:42

2026应届生简历诊断工具Top3评测与使用指南

1. 项目背景与核心价值 2026年春节后的招聘季即将到来&#xff0c;对于应届毕业生而言&#xff0c;一份优质的简历往往是敲开职场大门的第一块砖。但现实情况是&#xff0c;超过78%的应届生简历存在格式混乱、重点模糊、与岗位匹配度低等典型问题。这个现象催生了简历诊断工具的…

作者头像 李华
网站建设 2026/8/24 5:41:30

开源文档系统MinDoc:为IT团队打造轻量级知识管理解决方案

1. 项目概述&#xff1a;为什么IT团队需要一个专属的文档系统&#xff1f;在IT团队里&#xff0c;文档和笔记的混乱程度&#xff0c;往往和项目的复杂度成正比。你肯定经历过这些场景&#xff1a;一个关键接口的调用方式&#xff0c;散落在三个不同同事的本地Markdown文件里&am…

作者头像 李华