十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java注解全解析:从基础原理到Spring、MyBatis实战应用

Java注解全解析:从基础原理到Spring、MyBatis实战应用 1. 项目缘起为什么我要整理这份“活”的注解大全干了这么多年Java从刚毕业时对着Override发懵到现在能对着Spring Boot里一长串注解组合写出业务代码注解这东西可以说是从入门到“入土”都绕不开。面试八股文里它是个高频考点实际开发中它又是简化配置、提升效率的利器。但说实话注解太多了Spring的、JPA的、Lombok的、自定义的……散落在各个文档、博客里用的时候总得现查效率低下不说还容易踩坑。比如我就遇到过RequiredArgsConstructor和Lazy注解一起用结果Lazy失效的诡异问题也曾在配置MyBatis-Plus多租户时对着几个注解该用哪个、怎么配纠结半天。更别提那些因为不理解注解生命周期、作用目标而导致的运行时错误了。所以我决定自己动手整理一份属于我自己的“JAVA注解大全”。这份大全不是一次性的静态文档而是一个随着我项目经验、踩坑经历不断“生长”的活笔记。它服务于我的日常开发、面试复习也希望能给看到这篇文章的你提供一个脉络清晰、有血有肉的注解知识库。这份大全不会像教科书一样罗列所有注解的定义而是会围绕“用”和“懂”两个核心。我会结合那些热搜词里的真实问题——像Param注解报错、Lombok版本不兼容警告、事务注解使用场景、RunWith到底什么意思——来展开告诉你每个注解在什么场景下用、为什么这么用、以及用的时候有哪些“坑”需要避开。我们从最基础的Java内置注解开始逐步深入到Spring生态、持久层、工具链最后聊聊怎么创造你自己的注解。准备好了吗我们开始。2. 注解基石理解Java内置注解与元注解在冲向Spring那些花花绿绿的注解之前我们必须把地基打牢。Java语言本身提供了一套内置注解以及用于定义注解的“元注解”。这部分知识是理解一切高级注解的钥匙。2.1 内置注解编译器与开发者的约定Java内置的几个注解你可能天天见但未必都清楚它们背后的机制。Override这可能是你学会的第一个注解。它的作用远不止“告诉编译器这是重写方法”那么简单。从Java 6开始它可以用于标记对接口方法的实现。更重要的是它是一个编译期检查器。如果你标记了Override但父类或接口中并没有可重写的方法编译器会直接报错。这能有效防止你因为拼写错误或签名不一致而错误地“重载”而非“重写”方法。我见过有同事把toString()误写成tostring()如果没有Override程序能编译但行为诡异有了它这种低级错误在编译阶段就被扼杀了。Deprecated标记过时的方法、类或字段。它的价值在于平滑过渡和团队协作。当你负责的库需要升级某个API有更好的替代方案时直接删除旧方法会导致所有调用方编译失败破坏性太大。用Deprecated标记配合deprecated的JavaDoc说明说明为什么过时以及替代方案是什么可以在编译时产生警告提醒调用者迁移给出现存代码一个缓冲期。在IDE中被标记的元素通常会有一条删除线非常醒目。SuppressWarnings用于抑制编译器警告。这是一个需要谨慎使用的注解。常见的参数有“unchecked”抑制泛型未检查转换的警告、“deprecation”抑制使用过时API的警告、“rawtypes”等。我的原则是只在确切知道警告无害且无法通过修改代码消除时才在最小作用域内使用它。比如你明确知道一个List转换是安全的可以在这个方法上加SuppressWarnings(“unchecked”)而不是在整个类上使用。全局抑制警告会掩盖真正的问题。FunctionalInterfaceJava 8引入用于标记函数式接口。它也是一个编译期检查注解。一个接口如果被它标记就必须有且仅有一个抽象方法不包括Object类中的方法。这确保了它能够用于Lambda表达式和方法引用。即使不加这个注解满足条件的接口也是函数式接口但加上它可以让意图更明确并且让编译器帮你检查是否破坏了“单一抽象方法”的契约。SafeVarargs与泛型、可变参数相关用于抑制“堆污染”警告。当你有一个使用泛型可变参数的方法并且你确信该方法内部没有进行不安全的操作如将参数数组存储到可能被外部访问的变量时可以使用它来消除警告。理解它需要先理解Java泛型的擦除机制和堆污染的概念对于一般业务开发遇到相关警告时再查即可。2.2 元注解注解的注解元注解是用来定义注解的注解它们位于java.lang.annotation包中。理解它们你才能看懂别人定义的注解也才能自己定义注解。Target指定注解可以应用在哪些Java元素上。这是定义注解时第一个要考虑的问题。它的取值是一个ElementType枚举数组常见的有TYPE类、接口、枚举FIELD字段包括枚举常量METHOD方法PARAMETER形参CONSTRUCTOR构造器LOCAL_VARIABLE局部变量ANNOTATION_TYPE注解类型本身PACKAGE包例如Override的Target是ElementType.METHOD意味着它只能用在方法上。如果你尝试把它用在类上编译器会报错。Retention指定注解的保留策略即注解信息保留到哪个阶段。这是理解注解何时起作用的关键。RetentionPolicy.SOURCE仅存在于源代码中编译后就被丢弃。典型的如Override、SuppressWarnings它们只在编译期给编译器看编译完就没用了不会进入.class文件。RetentionPolicy.CLASS默认值。注解会被编译到.class文件中但在运行时不会被JVM加载。这对于一些字节码处理工具非常有用比如某些AOP框架或字节码增强库在类加载时读取这些信息。RetentionPolicy.RUNTIME注解会被编译到.class文件中并且在运行时可以通过反射机制读取。这是Spring等框架大量使用的策略因为框架需要在程序运行的时候通过反射检查类、方法上的注解来决定如何注入依赖、处理请求、管理事务等。Documented一个标记注解。被它修饰的注解在使用时其信息会被包含在生成的JavaDoc中。如果你在开发一个公共库并且希望你的注解成为API文档的一部分就应该加上它。Inherited另一个标记注解。它表示注解是否具有继承性。如果一个类使用了被Inherited修饰的注解那么这个类的子类将自动继承这个注解前提是子类没有被其他注解覆盖。注意这只对Target(ElementType.TYPE)的注解有效。Spring的Service、Controller等通常不加Inherited因为业务逻辑的继承关系复杂自动继承可能导致意外的组件扫描结果。RepeatableJava 8引入允许同一个注解在同一个地方重复使用。在这之前如果你想在一个元素上添加多个相同类型的注解需要将它们包装在一个容器注解里。Repeatable简化了这个操作。定义时需要同时定义一个容器注解其value方法返回一个被Repeatable注解的注解数组。掌握了这些基石你就有了解读任何注解定义的能力。接下来我们进入最庞大的家族——Spring注解。3. Spring核心注解从Bean管理到Web MVCSpring的注解体系极其庞大我们按功能域来梳理重点关注那些高频使用和容易混淆的。3.1 模式注解与组件扫描这是Spring的起点用于声明一个类是Spring容器管理的Bean。Component最通用的模式注解标记一个类为Spring组件。其他更具体的注解如Service、Repository、Controller都是它的特化从语义上区分了组件的角色。Service用于标记业务逻辑层的服务类。它和Component在功能上完全一样。使用它主要是为了清晰的架构分层让看代码的人一眼就知道这个类是业务服务。一些AOP切面可能会针对Service进行特定处理。Repository用于标记数据访问层DAO的类。它有一个额外的价值它会将平台特定的持久化异常如SQLException转换为Spring统一的DataAccessException层次结构中的异常。这是一个未经检查的异常这鼓励你在服务层进行更清晰的异常处理。在使用JPA或Hibernate时这个转换通常由持久化提供商完成但显式使用Repository仍是一个好习惯。Controller用于标记Web层的控制器。它是Spring MVC架构的一部分。被它标记的类其方法返回值可以映射到视图如JSP也可以配合ResponseBody直接返回数据。RestControllerSpring 4.0引入是Controller和ResponseBody的组合注解。标记这个注解的类其所有方法都默认添加了ResponseBody意味着方法返回值会通过HttpMessageConverter直接写入HTTP响应体用于构建RESTful API。这是现代Spring Web开发最常用的控制器注解。Configuration标记一个类为配置类其内部可以定义Bean方法。它本身也被Component注解所以也会被组件扫描到。配置类内部的方法调用会被CGLIB代理拦截确保多次调用Bean方法返回的是同一个单例Bean在默认的单例作用域下。组件扫描的激活光有这些注解还不够需要告诉Spring去哪里找它们。这是通过ComponentScan注解完成的通常用在Configuration类上。你可以指定basePackages或basePackageClasses来缩小扫描范围提升启动速度。在Spring Boot中默认扫描主应用类所在包及其子包。3.2 依赖注入相关注解依赖注入是Spring的核心相关注解决定了Bean如何被创建和组装。AutowiredSpring提供的依赖注入注解。它可以用于构造器、字段、Setter方法甚至普通方法上。默认按类型进行自动装配。如果找到多个同类型的Bean则需要配合Qualifier指定Bean的名称。从Spring 4.3开始如果类中只有一个构造器那么Autowired可以省略。我个人的习惯是强制依赖使用构造器注入可选依赖使用Setter方法注入。构造器注入能保证依赖不可变且便于测试。ResourceJSR-250标准提供的注解功能与Autowired类似但默认按名称进行装配。它有两个属性name和type。如果只指定name则按名称找如果只指定type则按类型找如果都指定则找到唯一匹配的Bean。在只有Spring环境时Autowired更常用。但在需要兼容其他JSR-250容器的场景Resource是标准选择。关于Resource与构造器注入的效率网络热词里有这个问题。从原理上讲构造器注入发生在Bean实例化时通过反射调用构造器完成。Resource字段注入发生在Bean实例化之后通过反射直接设置字段值。单次调用的性能差异在纳秒级对于整个应用生命周期和可维护性来说可以忽略不计。选择哪种方式应更多基于设计考量如不可变性、循环依赖避免而非微乎其微的性能差异。Spring官方推荐构造器注入。Qualifier当有多个相同类型的Bean时用于指定具体要注入哪一个。你可以给Bean定义时加上Qualifier(“名称”)在注入点也使用相同的Qualifier来匹配。Primary当有多个相同类型的Bean时标记了Primary的Bean会被优先选择无需使用Qualifier。这在定义默认实现时很有用。Value用于注入外部化配置的值如application.properties、SpEL表达式结果或默认值。例如Value(“${server.port:8080}”)会注入server.port配置的值如果没有则默认为8080。Value(“#{systemProperties[‘user.home’]}”)可以注入系统属性。Lazy用于延迟初始化Bean。标记了Lazy的Bean在Spring容器启动时不会被创建只有在第一次被请求注入或通过ApplicationContext.getBean时才会初始化。这可以加快应用启动速度特别是对于那些初始化成本高、但不一定在启动后就立即使用的Bean。但要注意这可能会将启动时的问题推迟到运行时才发现。踩坑记录RequiredArgsConstructor与Lazy的冲突Lombok的RequiredArgsConstructor会为所有final字段和标记了NonNull的字段生成一个构造器。如果你在其中一个字段上同时使用了Autowired Lazy期望注入一个懒加载的代理但构造器是在类实例化时调用的此时Spring会尝试去获取目标Bean而不是代理来完成注入这就可能导致Lazy失效或产生其他意外行为。解决方案在这种情况下避免在final字段上使用Lazy或者不使用RequiredArgsConstructor改为手动编写构造器并在构造器参数上使用Lazy。3.3 Web MVC相关注解这些注解用于处理HTTP请求和响应。RequestMapping老牌的请求映射注解可以标注在类或方法上指定URL路径和HTTP方法。它属性繁多如value/path(路径)method(请求方法)consumes(请求内容类型)produces(响应内容类型)等。在类级别定义通用路径前缀在方法级别定义具体路径是一种常见模式。GetMapping,PostMapping,PutMapping,DeleteMapping,PatchMappingSpring 4.3引入的组合注解分别是RequestMapping(method RequestMethod.XXX)的简写。它们使代码意图更清晰是当前推荐的使用方式。RequestParam用于绑定HTTP请求参数到控制器方法的参数。可以指定参数名(name或value)、是否必需(required)、默认值(defaultValue)。对于非必需参数最好总是设置defaultValue或者将方法参数类型改为Optional。PathVariable用于绑定URL模板变量。例如GetMapping(“/users/{id}”)配合方法参数PathVariable Long id可以获取URL中{id}部分的值。RequestBody用于将HTTP请求体通常是JSON或XML绑定到方法参数上。Spring会使用配置的HttpMessageConverter如MappingJackson2HttpMessageConverter来反序列化请求体。这是REST API接收复杂参数的标配。ResponseBody如前所述用于将方法返回值直接写入HTTP响应体。在RestController中默认启用。ResponseStatus可以标记在方法或异常类上用于指定HTTP响应状态码。标记在方法上时无论方法正常返回还是抛出异常只要没被ExceptionHandler处理都会返回指定的状态码。ExceptionHandler标记在Controller或ControllerAdvice类的方法上用于处理本控制器或全局范围内抛出的特定异常。这是实现统一异常处理的核心。ControllerAdvice和RestControllerAdvice用于定义全局的、跨多个控制器的增强组件。通常结合ExceptionHandler、InitBinder、ModelAttribute使用。RestControllerAdvice是ControllerAdvice和ResponseBody的组合用于REST API的全局异常处理和数据绑定。3.4 事务与AOP注解TransactionalSpring声明式事务管理的核心注解。可以标记在类或方法上。标记在类上时表示该类的所有公共方法都开启事务。关键属性解析propagation事务传播行为。这是最容易出问题的地方。常见的有REQUIRED默认如果当前存在事务则加入该事务如果当前没有事务则创建一个新的事务。REQUIRES_NEW无论当前是否存在事务都创建一个新的事务并挂起当前事务如果存在。适用于需要独立提交/回滚的子逻辑。NESTED如果当前存在事务则在嵌套事务内执行如果当前没有事务则行为同REQUIRED。嵌套事务是外部事务的一部分只在部分数据库如MySQL的InnoDB中通过保存点支持。SUPPORTS如果当前存在事务则加入该事务如果当前没有事务则以非事务方式执行。NOT_SUPPORTED以非事务方式执行如果当前存在事务则挂起该事务。MANDATORY必须在一个已有的事务中执行否则抛出异常。NEVER必须在非事务环境下执行如果当前存在事务则抛出异常。isolation事务隔离级别。默认为数据库默认级别。在并发问题严重时调整如READ_COMMITTED解决脏读、REPEATABLE_READ解决不可重复读等。readOnly是否只读事务。设置为true可以给数据库一个优化提示。对于只有查询的方法建议设置。rollbackFor/noRollbackFor指定哪些异常触发回滚或不回滚。默认只在运行时异常RuntimeException和错误Error时回滚受检异常Exception不回滚。如果你希望某些受检异常也触发回滚必须显式配置rollbackFor。踩坑记录Transactional失效的常见场景方法非publicSpring的AOP代理默认只对公共方法进行拦截。非public方法上的Transactional无效。自调用问题在同一个类中一个没有Transactional的方法A调用了有Transactional的方法B事务不会生效。因为事务管理是通过AOP代理实现的自调用绕过了代理。解决方法将方法B移到另一个Bean中或者使用AopContext.currentProxy()获取当前代理对象再调用。异常被捕获如果在方法内部用try-catch捕获了异常并且没有在catch块中重新抛出那么事务管理器就感知不到异常不会触发回滚。数据库引擎不支持例如MySQL的MyISAM引擎不支持事务。错误的传播行为例如在NOT_SUPPORTED或NEVER传播行为下事务自然不会开启。EnableAspectJAutoProxy在配置类上使用用于开启Spring对AspectJ切面编程的支持。有了它你才能使用Aspect、Before、After、Around等注解来定义切面。4. 持久层与数据访问注解与数据库打交道是后端开发的日常JPA和MyBatis及其增强版MyBatis-Plus是两大主流选择它们都有丰富的注解。4.1 JPA (Jakarta Persistence API) 核心注解JPA通过注解将Java对象映射到数据库表。Entity标记一个类为JPA实体意味着它对应数据库中的一张表。Table指定实体对应的数据库表名。如果类名与表名遵循命名约定如驼峰转下划线可以省略。Id标记实体类的主键字段。GeneratedValue指定主键的生成策略。GenerationType.IDENTITY依赖于数据库的自增主键如MySQL的AUTO_INCREMENT。GenerationType.SEQUENCE使用数据库序列如Oracle。GenerationType.TABLE使用一个特定的数据库表来模拟序列。GenerationType.AUTO默认由持久化提供商如Hibernate选择适合的生成策略。Column映射实体字段到表列。可以指定列名(name)、是否唯一(unique)、是否可为空(nullable)、长度(length)、精度(precision)等。Transient标记一个字段为非持久化字段即该字段不会映射到数据库表。Temporal在Java 8之前用于映射java.util.Date或java.util.Calendar到数据库的日期/时间类型。Java 8的java.timeAPILocalDate,LocalDateTime有更好的支持通常不需要此注解。Enumerated映射枚举类型。EnumType.ORDINAL存储枚举的序数整数EnumType.STRING存储枚举的名称字符串。强烈建议使用STRING因为ORDINAL在枚举定义顺序改变时会导致数据混乱。OneToMany,ManyToOne,OneToOne,ManyToMany定义实体间的关联关系。这是JPA复杂但也强大的部分。需要仔细配置mappedBy维护关系的反方、cascade级联操作、fetch加载策略等属性。JoinColumn配合关联注解使用指定外键列的详细信息。Spring Data JPA 的 Repository 注解通常我们不会直接使用Repository在DAO实现类上而是定义一个接口继承JpaRepositorySpring Data JPA会在运行时自动生成实现。此时接口本身不需要任何注解但包含它的包需要被组件扫描到。4.2 MyBatis与MyBatis-Plus注解MyBatis更偏向于SQL映射注解方式可以简化XML配置。Mapper标记一个接口是MyBatis的Mapper接口。Spring Boot项目中通常在主类或配置类上使用MapperScan批量扫描这样每个Mapper接口就不需要单独加Mapper了。Select,Insert,Update,Delete直接在接口方法上编写SQL语句。例如Select(“SELECT * FROM user WHERE id #{id}”) User selectUser(Long id);。参数通过#{ }占位符绑定。Param当Mapper接口方法有多个参数时用于指定每个参数在SQL中的名称。这是Param注解报错的常见原因如果你的方法有多个参数且没有使用ParamMyBatis默认会按param1, param2, …或者arg0, arg1, …来引用这容易导致混乱和错误。最佳实践是只要参数数量大于1就显式使用Param。Results与Result用于手动定义查询结果到Java对象的映射关系类似于XML中的resultMap。Results是容器Result定义单个字段的映射。MyBatis-Plus 增强注解MP在MyBatis基础上提供了更多便利注解。TableName类似JPA的Table指定实体对应的表名。TableId指定主键并可设置主键类型(type)如IdType.AUTO(数据库自增)IdType.ASSIGN_ID(MP雪花算法)IdType.INPUT(手动输入)等。TableField指定字段映射。常用属性value数据库字段名。exist是否为数据库表字段。false时类似JPA的Transient。fill字段自动填充策略。这是MP的一大亮点可以配合MetaObjectHandler接口实现创建时间、更新时间等字段的自动填充。可选值FieldFill.INSERT,FieldFill.UPDATE,FieldFill.INSERT_UPDATE。Version乐观锁注解。标记在字段上配合MP的乐观锁插件使用。EnumValue标记在枚举类的某个属性上指定该枚举存入数据库的值例如存储枚举的code属性而非name。InterceptorIgnore在Mapper方法上使用可以忽略MP的某些插件如分页插件、租户插件等。基于MyBatis-Plus的多租户注解实现MP提供了多租户插件(TenantLineInnerInterceptor)。实现多租户通常需要实现TenantLineHandler接口定义如何获取当前租户ID、哪些表需要过滤等逻辑。在实体类的字段上使用TableField(fill FieldFill.INSERT)并在MetaObjectHandler中为插入操作自动设置租户ID。或者更通用的方式是在所有需要租户隔离的SQL表对应的实体字段上使用一个特定的注解虽然MP没有提供标准注解但你可以自定义一个如TenantId的注解然后在插件中通过解析SQL和实体信息自动在查询条件中附加tenant_id ?。这需要深入理解MP的插件机制和SQL解析过程。5. 测试、配置与工具链注解高质量的代码离不开测试现代开发也离不开各种工具链的辅助。5.1 测试相关注解 (JUnit Spring Test)Test(JUnit)标记一个方法为测试方法。BeforeEach,AfterEach(JUnit 5)在每个测试方法之前和之后执行。用于准备和清理测试环境。JUnit 4的对应注解是Before,After。BeforeAll,AfterAll(JUnit 5)在所有测试方法之前和之后执行一次。方法必须是static的。用于重量级的初始化和清理。JUnit 4的是BeforeClass,AfterClass。DisplayName(JUnit 5)为测试类或测试方法设置一个易读的显示名称。Disabled(JUnit 5)禁用某个测试类或测试方法。JUnit 4的是Ignore。SpringBootTestSpring Boot提供的用于集成测试的注解。它会加载完整的应用程序上下文适合进行端到端的测试。可以通过webEnvironment属性指定Web环境如模拟Servlet环境、随机端口运行真实服务器等。DataJpaTest,WebMvcTest,JsonTest等Spring Boot提供的切片测试注解。它们只加载应用程序上下文的一部分启动速度比SpringBootTest快得多。DataJpaTest专注于JPA组件WebMvcTest专注于Web MVC控制器层JsonTest专注于JSON序列化。MockBean,SpyBean在Spring的测试上下文中注入Mockito模拟对象或间谍对象。MockBean会用一个Mock对象替换掉上下文中原有的同类型BeanSpyBean则会包装真实的Bean允许你验证其交互或对部分方法进行打桩。TestPropertySource为测试类指定额外的属性文件或内联属性用于覆盖主配置非常适合为测试环境配置独立的数据库或开关。RunWith(SpringRunner.class)(JUnit 4) /ExtendWith(SpringExtension.class)(JUnit 5)这是连接JUnit和Spring TestContext框架的桥梁。它告诉JUnit使用Spring的测试运行器这样Spring的ApplicationContext才能在测试期间被加载和管理。在Spring Boot测试中SpringBootTest等注解已经隐式包含了这个扩展所以通常不需要显式添加。但如果你在非Spring Boot的Spring测试中或者需要更精细的控制可能还需要它。5.2 Lombok项目注解Lombok通过注解在编译时生成代码极大减少了样板代码。Data一个组合注解包含了ToString,EqualsAndHashCode,Getter(对所有字段),Setter(对所有非final字段),RequiredArgsConstructor的功能。它是“数据类”的快捷方式但要小心用在有继承关系的类上因为自动生成的equals和hashCode可能不符合预期。Getter/Setter为字段生成getter/setter方法。可以放在类上对所有字段生效或单个字段上。ToString生成toString()方法。可以通过exclude排除字段或通过of指定包含的字段。EqualsAndHashCode生成equals()和hashCode()方法。同样需要注意继承问题可以使用callSuper true来包含父类的字段。NoArgsConstructor生成无参构造器。AllArgsConstructor生成包含所有字段的构造器。RequiredArgsConstructor生成包含所有final字段和标记了NonNull的字段的构造器。这是依赖注入构造器的常用选择。Builder为类生成一个建造者模式Builder Pattern的API使得对象创建更加灵活和可读。Slf4j,Log4j2等为类自动注入一个日志对象如private static final Logger log LoggerFactory.getLogger(XXX.class);可以直接使用log.info()等方法。SneakyThrows这个注解的作用是偷偷地抛出受检异常而无需在方法签名上声明throws。它通过生成一个try-catch块将受检异常包装成RuntimeException再抛出。慎用因为它破坏了Java的异常检查机制可能掩盖真正的错误。仅在明确知道异常无需调用者处理且希望代码简洁时如在Lambda表达式中使用。关于Lombok的版本兼容性警告网络热词中提到了“java: you aren‘t using a compiler supported by lombok, so lombok will not work”。这个警告通常出现在你使用的Java编译器版本与Lombok插件或注解处理器版本不兼容时。解决方法确保你的IDE如IntelliJ IDEA安装了最新版本的Lombok插件。在Maven或Gradle中使用与你的JDK版本兼容的Lombok依赖版本。查看Lombok官网的版本说明。对于Maven确保maven-compiler-plugin配置正确并且注解处理器路径包含了Lombok。有时在父POM中锁定lombok.version属性会有帮助。5.3 配置与条件注解ConfigurationProperties用于将外部配置如application.yml批量绑定到一个Bean的字段上。通常配合EnableConfigurationProperties使用或者直接在Component类上使用。它支持松散的绑定如server.port可以绑定到serverPort字段、验证通过JSR-303注解如NotNull和类型安全。ConditionalOnProperty条件注解当指定的配置属性满足条件时才注册Bean。例如ConditionalOnProperty(prefix “feature”, name “cache.enabled”, havingValue “true”)。ConditionalOnClass/ConditionalOnMissingClass当类路径下存在/不存在某个类时才注册Bean。Spring Boot的自动配置大量使用此类注解。ConditionalOnBean/ConditionalOnMissingBean当容器中存在/不存在某个Bean时才注册Bean。用于定义默认Bean并允许用户自定义Bean进行覆盖。Profile指定Bean在哪个Spring Profile激活时才被注册。用于区分开发、测试、生产等不同环境的配置。6. 自定义注解打造你的开发利器当你发现某些模式化的代码反复出现或者你想为框架添加一些元数据时自定义注解就派上用场了。6.1 定义你的注解自定义注解使用interface关键字。你需要用元注解来修饰它。import java.lang.annotation.*; Target(ElementType.METHOD) // 这个注解用在方法上 Retention(RetentionPolicy.RUNTIME) // 运行时保留这样我们才能通过反射读取 Documented // 包含在JavaDoc中 public interface OperationLog { String module() default ; // 注解的属性类似方法定义。default指定默认值。 String operation() default ; String operator() default system; // 可以设置默认值 }6.2 处理注解反射与AOP定义注解只是第一步更重要的是如何处理它。主要有两种方式1. 反射处理适用于在程序启动时或特定时机扫描和处理。// 假设我们有一个使用了OperationLog注解的Service public class UserService { OperationLog(module 用户管理, operation 创建用户) public void createUser(User user) { // ... 业务逻辑 } } // 处理注解的代码 Method method userService.getClass().getMethod(createUser, User.class); if (method.isAnnotationPresent(OperationLog.class)) { OperationLog logAnnotation method.getAnnotation(OperationLog.class); String module logAnnotation.module(); String operation logAnnotation.operation(); // 记录日志、发送消息等操作... System.out.println(“操作模块” module “, 操作类型” operation); }2. AOP处理更常用、更优雅结合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; Aspect Component public class OperationLogAspect { Around(annotation(com.yourpackage.OperationLog)) // 切点表达式拦截所有带有OperationLog注解的方法 public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature (MethodSignature) joinPoint.getSignature(); Method method signature.getMethod(); OperationLog annotation method.getAnnotation(OperationLog.class); String module annotation.module(); String operation annotation.operation(); String operator annotation.operator(); // 记录操作开始前的信息如参数、操作人、时间等 long startTime System.currentTimeMillis(); Object[] args joinPoint.getArgs(); System.out.println(“【” operator “】开始执行【” module “-” operation “】, 参数: ” Arrays.toString(args)); Object result; try { result joinPoint.proceed(); // 执行原方法 long endTime System.currentTimeMillis(); // 记录操作成功后的信息如结果、耗时 System.out.println(“操作成功耗时” (endTime - startTime) “ms, 结果: ” result); } catch (Exception e) { // 记录操作失败信息 System.out.println(“操作失败异常: ” e.getMessage()); throw e; // 重新抛出异常 } return result; } }通过AOP我们将横切关注点日志记录与核心业务逻辑完全解耦代码更加清晰。6.3 实际应用场景权限校验自定义RequiresPermission(“user:add”)注解结合AOP在方法执行前校验当前用户是否拥有指定权限。接口限流自定义RateLimit(key “api:login”, permitsPerSecond 10)注解结合AOP和Guava RateLimiter或Redis实现分布式限流。数据脱敏在DTO字段上使用SensitiveData(type SensitiveType.ID_CARD)在序列化如Jackson时通过自定义序列化器或过滤器对数据进行脱敏。缓存注解虽然Spring有Cacheable但你也可以自定义更细粒度的缓存注解比如LocalCache和DistributedCache分别走本地缓存和Redis。审计日志如上面的OperationLog记录谁在什么时间做了什么操作。自定义注解的核心在于“约定大于配置”。它通过声明式的方式为代码附加了丰富的元数据再通过统一的处理器反射或AOP来消费这些元数据从而实现通用逻辑的复用和业务代码的净化。这是框架设计的常用手法也是提升你代码设计能力的重要一步。这份“活”的注解大全我会持续更新。每一次新技术的接触每一次生产问题的排查都可能为它增加新的条目和注解。注解的世界远不止于此还有验证注解JSR-303的NotNull,Size、JSON处理注解Jackson的JsonIgnore,JsonProperty、文档生成注解Swagger的ApiOperation,ApiParam等等。但掌握了上述核心体系你已经具备了理解和运用绝大多数Java注解的能力。记住注解是工具理解其背后的原理和适用场景才能用得恰到好处避免掉入“注解森林”的陷阱。
返回列表