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

发布时间:2026/8/24 5:50:12
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加载类时被读取到内存中。因此我们可以在程序运行时通过反射ReflectionAPI动态地获取注解信息并据此做出相应的处理。Spring的Controller、AutowiredJUnit的Test都是RUNTIME级别的。实操心得当你自定义一个注解时第一个要问自己的问题就是“我这个注解是给谁用的编译器某个编译期工具还是运行时的我自己/框架” 答案决定了你的Retention应该选哪个。99%的业务场景下如果你希望注解在运行时起作用那就选RUNTIME。2.2 元注解注解的注解就像类是对象的模板元注解Meta-Annotation就是注解的模板。JDK内置了5个元注解用来修饰其他注解的定义Retention上面刚讲过定义注解的生命周期。Target指定注解可以应用在哪些Java元素上。这是个ElementType数组常用值有TYPE类、接口、枚举FIELD字段包括枚举常量METHOD方法PARAMETER方法参数CONSTRUCTOR构造器LOCAL_VARIABLE局部变量ANNOTATION_TYPE注解类型用于元注解PACKAGE包 例如Autowired的Target是{ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}意味着它可以用在这四个地方。Documented被它修饰的注解会被javadoc工具提取到API文档中。如果你希望自定义的注解出现在文档里就加上它。Inherited这是一个容易被忽略但有时很有用的元注解。它表示注解是否具有继承性。如果一个类用了被Inherited修饰的注解那么它的子类会自动继承这个注解前提是子类没有被其他注解覆盖。注意这只对Target(ElementType.TYPE)的类注解有效对方法、字段注解无效。RepeatableJDK 1.8引入允许同一个注解在同一个地方重复使用。在JDK 1.8之前同一个注解在一个地方只能用一次。有了Repeatable你需要为注解定义一个“容器注解”。例如你可以定义一个Schedule注解并用Repeatable(Schedules.class)修饰这样你就可以在同一个方法上写多个Schedule了。3. JDK内置核心注解深度解析别看JDK自带的注解不多但每一个都是精华理解透了能避免很多低级错误。3.1Override不仅仅是“重写”检查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安全网如上例它能帮你第一时间发现拼写错误或签名不匹配避免你以为重写了父类方法实际上却定义了一个新方法导致多态行为不符合预期。这种Bug非常隐蔽。提高可读性明确告诉阅读代码的人这是一个重写方法不是类自己新加的方法。应对父类变更如果父类将来删除了这个方法所有加了Override的子类方法会立刻编译报错迫使你同步修改保证了代码的同步性。注意事项从JDK 1.6开始Override不仅可以用于重写父类方法还可以用于实现接口方法。但在1.5及以前用于实现接口方法会编译报错。如果你的项目还在用很老的JDK虽然不推荐需要注意这一点。3.2Deprecated优雅的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.3SuppressWarnings压制编译器警告用来有选择地关闭编译器警告。警告通常是有用的但在某些确知安全的场景下警告信息会干扰视线。SuppressWarnings(unchecked) // 我知道这个强制转换是安全的 public ListString getRawList() { List rawList someLegacyCode(); return (ListString) rawList; // 未经检查的转换会产生警告 } // 可以同时压制多种警告 SuppressWarnings({unchecked, rawtypes, deprecation}) public void someMethod() { // ... }常用参数值unchecked抑制未经检查的转换警告如使用泛型集合。rawtypes抑制使用原始类型的警告。deprecation抑制使用已废弃API的警告。all抑制所有警告慎用。重要原则SuppressWarnings的作用范围应尽可能小。不要把它加在整个类上最好只加在方法上甚至只加在局部变量声明语句上。并且一定要写注释说明为什么这里可以安全地忽略警告。3.4FunctionalInterface函数式接口的契约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.5SafeVarargs泛型可变参数的安全承诺这个注解有点冷门但涉及泛型和可变参数时很重要。在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(ListT... lists) { Object[] array lists; // 泛型数组向上转型为Object[]这是允许的 array[0] Arrays.asList(42); // 污染将Integer的List放入原本是ListString的数组中 // 如果外部调用 unsafeMerge(listOfStrings, listOfIntegers); 就会在后续操作中抛出ClassCastException } }使用条件SafeVarargs只能用在static、final或private的构造器或方法上。因为非static、非final、非private的方法可能被重写无法保证其安全性。4. Lombok注解告别样板代码的“魔法”Lombok通过注解在编译期自动生成字节码极大减少了getter、setter、构造器等样板代码。但“魔法”用不好也会带来困惑。4.1 基础注解Data,Getter/Setter,ToString等Data一个复合注解相当于GetterSetterToStringEqualsAndHashCodeRequiredArgsConstructor。非常方便但要注意它默认使用所有非static字段生成equals和hashCode如果实体有关联对象如ListOrder直接使用可能导致栈溢出。这时需要用EqualsAndHashCode.Exclude排除某些字段或者不用Data自己组合需要的注解。AllArgsConstructor/NoArgsConstructor/RequiredArgsConstructorRequiredArgsConstructor只为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 易错点与排查技巧问题1RequiredArgsConstructor注解后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。解决方案放弃RequiredArgsConstructor手动写构造器并在参数上加LazyComponent public class ServiceA { private final ServiceB serviceB; public ServiceA(Lazy ServiceB serviceB) { // 手动写参数加Lazy this.serviceB serviceB; } }使用字段注入或setter注入不推荐破坏了不可变性Component public class ServiceA { Lazy Autowired private ServiceB serviceB; }重新设计消除循环依赖最推荐循环依赖通常是设计有问题的信号。问题2java: 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。问题3java: jps incremental annotation process is disabled. Compilation results on partial recompilations may be inaccurate.这是一个警告不是错误。意思是JPSJava 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等使用。RestControllerControllerResponseBody。用于RESTful Web服务直接返回JSON/XML数据不找视图解析器。Configuration标记一个类为配置类替代XML配置文件。其内部使用Bean注解的方法用于显式声明Bean。Bean在Configuration或Component类的方法上声明一个Bean。用于导入第三方库的类或需要复杂初始化逻辑的Bean。方法名默认即为Bean名称。依赖注入注解AutowiredSpring提供的按类型自动装配。可以用在字段、构造器、setter方法、普通方法上。如果找到多个同类型Bean需配合Qualifier指定名称。Resource(JSR-250)Java标准注解默认按名称装配名称找不到再按类型。name属性指定Bean名。Inject(JSR-330)另一个Java标准功能类似Autowired但无required属性。Value注入外部配置如application.properties的值或SpEL表达式。面试高频题Resource与构造函数注入哪个效率高这个问题有点“关公战秦琼”。Resource是字段/方法注入的一种形式。而构造函数注入是另一种注入方式。从运行时效率看微乎其微的差异可以忽略不计。依赖注入在容器启动时完成对运行时性能无影响。从设计模式与代码质量看构造函数注入被广泛推荐。不可变性依赖字段可以用final修饰确保Bean在构造后不可变线程安全。明确依赖一眼就能看出这个类需要哪些依赖没有隐藏的依赖。易于测试不需要Spring容器可以直接在单元测试中用new创建对象并传入mock依赖。避免循环依赖Spring官方文档指出构造函数注入能暴露循环依赖问题会直接报错而字段注入可能掩盖问题导致运行时出现不可预知的行为。ResourcevsAutowired两者效率无差别。Resource是JSR标准Autowired是Spring特有但功能更丰富如requiredfalse。在纯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是ControllerAdviceResponseBody。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无论如何都新建一个事务如果当前有事务则将其挂起。适用于需要独立提交/回滚的子操作比如记录日志即使主业务失败日志也要保存。其他如SUPPORTS、MANDATORY、NOT_SUPPORTED、NEVER、NESTED根据具体业务场景选用。isolation事务隔离级别。解决脏读、不可重复读、幻读问题。默认是数据库的默认级别通常是READ_COMMITTED。高隔离级别如SERIALIZABLE性能差慎用。readOnly是否只读事务。设置为true可以给数据库一个优化提示如MySQL会将事务设置为只读。对于纯查询方法建议加上。Transactional失效的常见场景避坑指南方法非publicTransactional基于AOP动态代理或CGLIB非public方法代理无法切入。自调用问题在同一个类中一个非事务方法A调用同一个类的事务方法B事务不会生效。因为代理对象调用AA内部调用B走的是this.B()而不是代理对象的B()绕过了代理。Service public class MyService { public void outer() { this.inner(); // 事务不生效 } Transactional public void inner() { // ... } }解决方案将inner()方法移到另一个Service中或者通过AopContext.currentProxy()获取当前代理对象再调用需要开启exposeProxy true或者不推荐使用AspectJ的编译时织入。异常被捕获如果在方法内用try-catch吞掉了异常事务管理器感知不到异常就不会回滚。Transactional public void method() { try { // 可能出错的业务 int i 1 / 0; } catch (Exception e) { e.printStackTrace(); // 异常被捕获并处理事务不会回滚 } }解决方案在catch块中手动抛出运行时异常或者将异常原样抛出。数据库引擎不支持比如MySQL的MyISAM引擎不支持事务只有InnoDB支持。未启用事务管理在Spring Boot中通常会自动配置。但在纯Spring项目中需要在配置类上添加EnableTransactionManagement。5.4 条件化装配与配置注解Conditional及其衍生注解Spring Boot自动配置的灵魂。根据特定条件如类路径下是否存在某个类、某个Bean是否存在、配置文件属性等决定是否装配一个Bean或配置类。ConditionalOnClass类路径下存在指定类时生效。ConditionalOnMissingBean容器中不存在指定Bean时生效。ConditionalOnProperty配置文件中存在指定属性且为特定值时生效。Profile根据激活的Spring Profile来条件化地装配Bean。用于区分开发、测试、生产环境。ConfigurationProperties将配置文件如application.yml中的属性批量绑定到一个Java对象的字段上类型安全支持嵌套、验证、宽松绑定kebab-case转camelCase。PostConstruct和PreDestroy(JSR-250)生命周期回调注解。分别标记在Bean初始化后和销毁前执行的方法。常用于资源初始化如缓存预热和清理如关闭连接池。6. 测试相关注解RunWith(JUnit 4) /ExtendWith(JUnit 5)指定测试运行器。Spring Boot测试通常用RunWith(SpringRunner.class)或ExtendWith(SpringExtension.class)来启动Spring测试上下文。SpringBootTestSpring Boot提供的集成测试注解会加载完整的应用程序上下文。可以指定web环境如WebEnvironment.MOCK或RANDOM_PORT。DataJpaTest,WebMvcTest,JsonTest等Spring Boot的“切片测试”Slice Test注解只加载应用程序的一部分测试速度更快。MockBean/SpyBean在Spring的测试上下文中注入Mockito的mock或spy对象用于模拟依赖。Test标记测试方法。JUnit 5的Test在org.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 AOPimport 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、IdJackson的JsonIgnoreValidation的NotNull等等。关键在于理解其核心思想注解是元数据它本身不做任何事但配合特定的处理器编译器、框架、AOP、注解处理器就能产生强大的声明式编程效果。希望这份笔记能帮助你构建起自己的注解知识体系在编码时更加得心应手。