废话不多说,咱们直接上干货,聊聊 Spring 核心注解到底是如何干活的。别整那些虚头巴脑的教科书味儿,我就说点真事儿。 Spring 能如此牛逼,核心就在于一个词:依赖注入(DI)。 它干了啥?就是把那个本来应当是你自己去写、自己去管理的对象,比如一个数据库连接、一个配置类的参数,硬塞进去,让它自己去找,就连去瞎猜。你只管告诉它“你要找哪位”,剩下的操作,比如如何弄、如何连、如何传参数,都交给 Spring 自己搞定。 这就像你去找茅房。

那会儿你得亲自拍板去哪个方向、走哪条路,就连还得自己搭梯子。目前你用 Spring,你只需求喊一声“茅房在哪”,Spring 那个专门的调度程序就会负责帮你打开通往茅房的路,就连帮你挑最便宜、最快的那条路。 最典型的例子就是 `@Service` 和 `@Controller`。 你说我说,这俩注解有啥用?没事,干活了。`@Service` 负责那些业务逻辑,像个勤劳的管家,帮你处理订单插入、用户余额扣减这些事儿。到了 `@Controller`,那就不管了,它只管接收 HTTP 请求,然后找到对应的 Service 去干活。 你写个 Controller 类,包名写成了 `com.example.user`,然后在这个类上贴了 `@RestController`。Spring 启动时,扫描发现这个类,直接拿过来,发现上面贴了 `@RequestMapping("/user")`,那它就知道:“嘿,我收到请求时,就写个 `/user` 的路由,然后去调用刚刚那个 `@Service` 里的方式。” 整个过程里,你连数据库连接池的代码都写不了。出于 Spring 已经帮你把 `DataSource`(数据源)给找出来了。

哪怕你明天把配置类改个参数,你不用动一行代码,Spring 启动时会自动重新扫描,重新找脉博,自动更新那个你贴上的注解,对它来说,这就像它是动态生成的代码,改配置就改,改源码就改。 再聊聊 `@Component` 和 `@Autowired`,这俩时常一起出现。 `@Component` 是个偷懒的标签,啥意思就是“随意找个类当组件用”。哪位敢贴它,哪位就是组件。它有个“智慧”的地方,它会自动扫描你整个项目标包结构,简历面试的时候全给你发了。 举个例子,假设你有个 `UserService.java`,里面有个 `getOrder` 方式。你不用揪心它能不能被找到,也不用揪心它能不能被依赖注入。你直接把 `@Component` 贴上去就行。 这时候,要是你想在 `UserController` 里用这个服务,你就贴 `@Autowired`。 这玩意儿最绝的地方在于,它不光能帮你搞定 `Service`,还能帮你搞定接口。 看下面这段代码,我是用的 `@Autowired(required = true)`。 ```java @Autowired(required = true) private UserService userService; ``` `required = true` 这个参数有点意思。它的意思是:“嘿,我要这个 `UserService` 对象,要是找不到,直接给我报错,别让我持续跑。” 为啥如此写?出于有时候咱们不想让别的业务逻辑去硬塞进来。

比如你可能不想让你的 Controller 依赖另一个 Controller 里的对象。

这时候,你能够自定义一个 `Авто注入器`,专门用来处理这个场景。 比如你想在 A 类里注入 C 类,但又不想在 A 类依赖 B 类(出于 B 类忒复杂了,不能一起塞进来),你肯定要在 C 类上贴 `@Autowired`。 有时候你就连不想自己写 `@Autowired` 注解了。你能够把整个 `UserService` 和 `UserController` 都贴 `@Component`,然后让 Spring 自动把它们连起来。Spring 会扫描这两个类,发现它们都想找 `UserService`,便它就把它们连上了。 这就好比你在装修房子。你不想买现成的柜子,你想自己定制。你先把图纸(接口)贴在墙上,然后让你最懂木工的人(Spring 的自动注入器)去现场,把对应的柜子(实现类)搬过来。 但这里有个坑,要是两个类都想找同一个东西,Spring 会把它们都塞进同一个包,害得同一个对象有两个引用。

这时候就得小心了,一般建议只让一个类贴 `@Autowired`,另一个贴 `@Component`,要么用 `@Autowired(required = false)` 来区分。 还有,`@Autowired` 有时候会找个代名词。 比如你想注入一个 `OrderService`,但 Spring 里没这个类。它可能找到了一个叫 `OrderServiceImpl` 的类,并且它的字段名是 `userService`。 这时候,要是用户类上贴了 `@Autowired`,Spring 会认定:“嘿,这个字段名不对,是不是我把 `OrderService` 给贴错位置了?”它会通知你:“检查你的代码,`userService` 字段是不是该贴 `@Autowired(required = true)` 的?” 而 `@Component` 就不吃这一套。

不管如何贴,只要贴上去,Spring 就知道你把它当对象用,它不会去检查字段名对不对,更不会去通知你检查。 这就挺灵活,适合不同场景。 再说说 `@Valid`。 大量人用它,但我不认定它最先进的。 `@Valid` 的功能挺好办:验证。它告诉 Spring,“看我这个参数,得是个合法的、可信的。” 比如你在做表单提交,要么你在做参数传递。你贴 `@Valid` 上去。 这时候,整个参数的值、就连方式里的回值,都得经过校验。 比如你有个 `User` 类,你有一个 `setPassword` 方式。你把这个方式贴了 `@Valid`。 那你传进去的参数,不仅要是字符串,还得得是 `@NotBlank` 校验过的,并且长度不能忒短。 要是校验黄了了,整个方式就不执行,直接回 `false` 要么抛异常。 这就像是个安检员。你对一个包裹贴了 `@Valid`,安检员就会检查里面的东西是不是合规。

要是不合格,整个运输过程就取消了。 缺点就是忒费事了。

有时候你认定这个参数在某些特定场景下没必要验证,但贴了 `@Valid` 赶明儿,整个接口要么整个方式的调用都被冻结了,没法灵活处理。 这时候,更高级的做法是用 `@Conditional`。 比如你可能有个 `@Valid` 的接口,但你只想在某些工夫用。

比如我只想在周一、周二用这个验证规则。 你能够贴 `@Valid`,然后让它依赖 `@DayOfWeek` 要么 `@Scheduled` 之类的注解

这就像你贴了装修标志,但只有到了周末才能进场施工。 要么,你想只在某些特定的 Controller 上加载这个验证规则。

这时候 `@ConditionalOnInstanceOf` 就派上用场了。它让验证逻辑只在你指定的类存有时才生效。 再聊聊 `@Transactional`。 这事儿最让人头疼,特别是负面的。 你想让一个方式里的数据库事务回滚。你贴了 `@Transactional`。 但要是这个方式用了 `@Async`,也就是异步执行(比如去发个通知,不需求等回结局),`@Transactional` 就没用了。

这就是著名的“事务地狱”。 要么,你想让一个实例变量加锁。你贴了 `@Transactional`。 但这又是个坑,出于 `@Transactional` 是运行在 Spring 容器里的,线程池是共享的。

要是你在一个事务里操作了数据库,然后另一个线程又进来操作同样的数据库,它们可能在同一行代码与此同时读取同一个数据,要么修改同一个对象,害得数据不一致。 如何解决? 一般做法是在 `@Transactional` 上面再贴 `@EnableAspectJTransactionSupport`,然后挂个 `@Aspect` 注解

这个 `@Aspect` 专门负责监控,发现有线程池内的比较悬的操作,就自动加锁。 这就好比在繁忙的十字路口贴了个交通灯标志 `@Transactional`。但要是是救护车去救人(异步事务),要么消防车灭火,交通灯可能就不负责这茬了。

这时候你就要靠 `@Aspect` 那个“自动监管者”来管。 还有 `@Retry`。 时常遇到网络抖动,要么对方系统挂了,你的请求就黄了了。你贴了 `@Retry`,重试 3 次。 这玩意儿实际上挺好办的,就是好办的“复读机”。 比如你在一个接口上贴了 `@Retry`。 第一次调用黄了了,就回滚。

然后立马重试。 到了第三次,要是还是黄了,那就彻底黄了,不再重试。 这比人工重试可靠多了,并且不会把同一个接口重复调用 N 次,害得性能难题。 再说说 `@Cacheable` 和 `@CacheEvict`。 Spring 有个缓存,叫 `Caching` 模块。

你想让方式执行完,把结局存到本地缓存里,下次再访问就直接从缓存拿,不用重新算。 你贴 `@Cacheable`。 参数不一样(比如 ID),缓存就不更新。你能够把这个参数叫 `cacheKey`。 要是参数变了,比如 ID 变了,那就执行,然后执行完刷新缓存。 这就像你查天气预报。你今天查 2024 年的,明天查 2025 年的,不会自动更新到昨天。 可是,要是今天查了,明天又想查 2024 年的,那就把今天的缓存“踢”掉(Evict),重新算,再存回去。 `@CacheEvict` 就是那个“踢人”的。 比如你不想让某个缓存覆盖,你贴 `@CacheEvict`。 这就有点尴尬了。

要是缓存里有数据,它会被清空?还是说,要是你只想清空某个特定的值,如何写? 这就涉及到底层的一些技巧了。

一般我们默认 `@CacheEvict` 的 key 是方式名,故此要是你贴了方式名,它就把整个方式的缓存都清空了。 要是你想清空某个参数的缓存,你一般得自己写代码,要么用 `@MethodQualifier` 这种高级注解(别看我认定这玩意儿还是有点老派)。 再说说 `@PersistenceContext`。 这玩意儿跟 `@Autowired` 挺像,都是用来注入的。 区别在哪? `@Autowired` 是你自己写的代码,Spring 帮你找对象。 `@PersistenceContext` 是官方给的,别看底层也是让 Spring 帮你找,但它是专门针对数据库的。 它的功能是把你的对象,和数据库里的数据同步起来。 比如你要存一个 `User` 对象到数据库里,然后从数据库里拿一个 `User` 对象出来。 你先写个实体类,比如 `User.java`。

然后贴 `@Entity`。 接着,在 `get` 和 `set` 方式上贴 `@PersistenceContext`。 这时候,Spring 一启动,扫描到 `@Entity`,就会去连接数据库。 然后,它会把数据库里的数据,异步地加载到你贴的字段里。 比如你贴了 `@PersistenceContext`,但你没贴 `@Transactional`。 那时候,数据加载回来,能够保证线程保险。它会在主线程里加载,不会和其他业务逻辑冲突。 这就像你刚出门,把手机(实体)里装好了资料。

然后你走到镜子前(持久层),把手机里的照片(数据)拍下来。

这时候,照片里的数据是保险的,不会和你在路上看的那张照片冲突。 最终聊聊 `@Configuration`。 这玩意儿你别总贴,贴多了代码会挺乱。 它的功能挺单一:告诉 Spring,“嘿,我下面写的这些类,别去扫描别的包了,也别去自动装配别的类了。” 你贴了 `@Configuration`,下面就能够贴 `@Bean`。 比如,你写一个 `config1.java`,贴了 `@Configuration`。 再写一个 `config2.java`,里面贴了 `@Bean("name1")`。 这时候,Spring 只会扫描这两个类,忽略你项目里所有的其他类。 这就像你在整理房间。你贴了个“书房”标签,让 Spring 只找你书房里的书,别去书架上的书里翻找。 要么,你想在特定的包下配置东西,比如 `com.example.config`,其他地方不需求。你贴 `@Configuration` 进去,Spring 就知道:“好,我不管包外了,只关切这个包。” 还有 `@EnableListener`。 这玩意儿是 Spring 容器启动时才生效的。 比如你想注册一个监听器,比如让你的代码里有个 `EventListener`,你想让它在 Spring 启动后生效。 你贴 `@EnableListener`,然后贴 `@EventListener`。 这就像你贴了一个贴纸,说“从这一刻起,我要启动听歌了”。 但有时候你想在特定类里生效,比如“只有 `UserController` 这个类里贴了 `@EventListener` 的监听器,才生效”。 这时候,你又能够贴 `@EnableListener`,要么贴 `@Conditional`。 比如 `@ConditionalOnClass`。

这玩意儿挺准,它只会在你定义的类存有时才生效。 比如你是用 `Spring Boot` 的,这玩意儿就自动生效。你是用 `Spring Cloud` 的,可能就不生效。

故此你得贴 `@EnableListener`,让它变得“通用”。 还有 `@EnableAutoConfiguration`。 这玩意儿忒关键了。 Spring Boot 启动的时候,要是系统里没配置 `application.properties`,会自动扫描,自动配置那些东西。 比如加载数据库驱动,加载 jar 包,加载 Maven 依赖,加载 Redis 相关的配置。 这时候,要是你贴了 `@EnableAutoConfiguration`,它就告诉 Spring:“嘿,别自己干这些活了,我启动的时候已经帮你调好了,你只管伺候我。” 这就像有个管家,你贴了个“别自己做饭”的牌子。 再说说 `@Import` 和 `@ImportResource`。 这俩都是告诉 Spring,“别去扫描别的包了,我自己来。” 比如你想导入一个 XML 配置文件,比如 `myconfig.xml`。 你贴 `@ImportResource`。 这时候,Spring 就不去扫描 `com.example.config` 下的所有类了,它只扫描你贴的这个 XML 文件的内容。 这就像你贴了个“别找隔壁”的牌子。 要么你想导入一个接口,比如 `myService.java`。 你贴 `@Import`。 这时候,Spring 就不会去扫描 `com.example` 下的所有类了。 这俩有点像 `@Configuration`,都是用来管住扫描范围的。 `@Import` 是点式的,一般用于导入单个 interface 或类。 `@ImportResource` 是针对文件的,一般用于 XML 或 JSON 文件。 最终,说说 `@CacheEvict` 的某些高级用法。 比如,你想在缓存失效的时候触发某个动作,比如邮件通知。 你不能好办贴 `@CacheEvict`,出于 Spring 不知道缓存失效了,你得去查缓存没,要是你去查,缓存就没,那就没意义了。 这时候,你得用 `@CacheEvict` 的“内部参数”。 你会贴 `@CacheEvict`,然后把失效的缘由指定掉。

比如 `@CacheEvict(reason = "dataChanged", keys = {"user:1"})`。 然后,你在 `@Cacheable` 里,把失效的缘由和 key 也指定下来。 这样,当某个 key 失效时,Spring 就会自动执行你指定的动作。 这个动作一般写在一个 method 里,比如 `notifyUser`。 这就像你贴了个通知贴,说“嘿,数据变了,请发邮件”。 但这时候,你得确认,数据确实变了。 比如,你贴了 `@CacheEvict`,但缓存里没数据。

这时候 Spring 会报错:“嘿,缓存里本来就有数据,你干嘛要告诉我呢?” 这时候你得加个 `@Cacheable` 的“前置判断”,要么你自己写个逻辑,判断一下“数据到底有没有变”。 比如,你能够在 `@Cacheable` 里加个条件:“要是 key 变了,就更新缓存,并执行动作。” 要么,你能够直接用 `@CacheEvict` 去触发那个动作。 不过,这种用法要注意,万一缓存里确实没有数据,要么你贴错了 key,那动作就执行不了了。 再聊聊 `@ConfigurationClass`。 这玩意儿是 Spring 4.3.3 之后才有的。 它的功能是把下面的类,直接作为配置类,不要扫描了。 比如,你贴了 `@ConfigurationClass`,然后下面贴了 `@Bean`。 这时候,Spring 会忽略你项目里所有的其他类,只扫描你贴的这个类。 这比贴 `@Configuration` 更保险,出于 `@Configuration` 可能会去扫描其他类。 `@ConfigurationClass` 的益处是,它不会去扫描其他类,也不会去扫描别的地方。它只管你贴的这一个类。 这就像你贴了一个“专属窗口”的牌子,让 Spring 只进这个窗口,别去别的地方乱逛。 最终,说说 `@EnableAutoConfiguration` 的一些坑。 这个注解大量,并且它内部有大量逻辑,有时候会冲突。 比如,你在 `application.properties` 里贴了 `spring.main.allow-circular-references=true`。

这玩意儿是准循环引用的。 而 `@EnableAutoConfiguration` 默认是不准循环引用的。 要是这两个一起贴,会形成啥? 这取决于你贴的顺序。 要是你先贴 `@EnableAutoConfiguration`,它可能会去提那个 `allow-circular-references` 的配置,然后去加载那些循环依赖的类。 这时候,你就会发现,本来当作没依赖的类,实际上依赖了其他类。 这可能害得你明明不需求那个类,但它却被加载了,害得内存占用变大,要么报错。 这时候,你得把 `@EnableAutoConfiguration` 的优先级调高,要么在 `application.properties` 里把那个循环依赖的类配置成 false。 要么,更好办的做法,直接用 `@EnableAutoConfiguration` 的 `spring.factories` 文件里的配置。 有时候,你贴 `@EnableAutoConfiguration`,但发现它没生效,要么效果不对。 这时候,你得去 `spring.factories` 文件里,看看有没有那些类,比如 `org.springframework.boot.autoconfigure.AutoConfiguration.imports`。 要是你发现里面有个类,你贴了 `@EnableAutoConfiguration`,但它没生效,那可能这个类就是循环依赖。 这时候,你就要调整配置,让它不依赖其他类。 要么,干脆把这个类从 `spring.factories` 里删了,让它不要去加载。 这就像你在周邮里发广告,但广告主自己也是个广告主,这广告就没意义了。 总的来说,Spring 的注解体系,实际上就是给 Spring 供给了大量“智慧”的指令。 它让你不用写一堆 `if-else` 要么 `try-catch` 来管理对象的生命周期。 它让你不用去管线程池,不用去管事务回滚,不用去管缓存的管理。 你只管贴注解,它负责剩下的所有事件。 别看有时候会有点小坑,比如循环依赖、事务地狱、缓存失效难题,但只要你理解它的设计思想,上手挺快。 就像学开车,刚启动你得看说明书,贴纸(注解),然后慢慢实践,啥时候系保险带,啥时候不系保险带,啥时候踩油门,啥时候刹车,全凭经验。 但有了弹簧,你就不用自己去造刹车片了。 希望这些,能帮你把 Spring 的注解原理搞得更明白。