news 2026/10/11 6:35:23

Spring注解解析真相:无需一对一解析类,掌握生命周期即可

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring注解解析真相:无需一对一解析类,掌握生命周期即可

前阵子在 Spring 技术群里看到一个特别典型的提问:"Spring 中每一个注解都需要有一个对应的解析的类吗?"

问这个问题的人,多半是被网上那些源码解析文章搞怕了——今天剖析@Autowired的内部处理器,明天拆解@Configuration的后置处理器,看起来好像每个注解背后都站着一个专属的"解析天团",于是忍不住怀疑是不是漏看了某个注解的解析类,导致自己的代码不生效。但实际接触过 Spring 源码的人都知道,这里面的真实情况和直觉不太一样。

先说结论:没有必要给每个注解都配一个专属解析类,Spring 内部也几乎不存在"一个注解一个解析类"的一一对应关系。真正重要的是搞清楚这个注解挂在容器生命周期的哪个环节、由哪段通用逻辑去解释她的语义。这篇文章我就把这件事彻底讲透:什么时候确实有"对应解析类",为什么多数注解没有,以及遇到自定义注解时,你应该怎么设计它的解析路径。

1. 这个问题的正确问法:先分清"注解本身的解析"和"业务动作的执行"

1.1 注解在 JVM 层面到底是什么

要回答这个问题,必须先回到最底层:注解的本质是什么?

在 JVM 看来,注解不过是一段元数据(metadata),附着在类、方法、字段、参数、包等程序元素上。注解本身不携带任何行为,它不会"主动做"任何事。真正让它产生效果的是某个外部读取者,通过反射拿到注解对象后,再根据注解里的值去执行相应的逻辑。

这里要注意区分两个层面的"解析":

  • 编译期解析(APT):像 Lombok 的@Getter、@Slf4j,确实有对应的AbstractProcessor实现类,在javac编译过程中扫描注解并生成代码。这种模式接近"一个注解一个处理类"的直觉。
  • 运行时解析(反射):Spring 的容器注解走的是这一条路。它不做代码生成,而是在容器启动、Bean 实例化、方法调用等阶段,通过反射读取注解的值,然后走通用逻辑。

Spring 的绝大多数注解走的是运行时解析这条路。@Transactional的注解值是在代理拦截时才被读取的,@Autowired是在 Bean 属性填充阶段被反射发现的,@RequestMapping是在容器启动后的映射注册阶段被扫描到的。它们都没有像 APT 那样"一个注解对应一个编译器处理类"的结构。

所以,正确的问法不是"每个注解都要有一个对应的解析类吗",而应该是:这个注解的语义,该由谁在哪个阶段解释,解释完之后要去触发什么扩展动作?

1.2 为什么你会有"一对一解析"的错觉

这种错觉不是凭空来的。Spring 早期确实存在"一个 XML 标签对应一个解析器"的阶段,比如<context:annotation-config>有AnnotationConfigBeanDefinitionParser,<mvc:annotation-driven>有MvcNamespaceHandler。XML 是结构化的,标签之间有父子层级,加上命名空间不同,分开写解析器很合理。

但注解不是 XML。注解没有层级结构,它只是一种扁平标记。如果每发明一个注解就写一个解析类,Spring 现在几百个注解就要写几百个解析类,运维和维护成本完全不可控。Spring 的设计者反而在努力做反方向的收敛:用少量几个核心处理器,同时接纳大量注解的语义。

提示:了解这个概念之后,你再去看源码时就不会被"某个注解没有专属解析器"吓到。比如@RestController没有叫RestControllerParser的类,它是作为"由@Controller衍生的组合注解"被RequestMappingHandlerMapping认识到的。

2. 那些有"对应解析类"的注解:BeanPostProcessor 阵营的编排逻辑

2.1 确实存在几个知名的"专属处理器"

尽管整体上不是一一对应,但如果你打开 Spring 的容器基础模块,会看到几个名字和注解语义高度绑定的类,这就是"有对应解析类"的那部分:

注解核心处理器处理阶段
@Autowired/@Value/@Inject/@LookupAutowiredAnnotationBeanPostProcessorBean 实例化后、属性填充阶段
@Resource/@PostConstruct/@PreDestroyCommonAnnotationBeanPostProcessor属性填充、初始化前后、销毁前
@EventListenerEventListenerMethodProcessor容器启动早期收集监听器,事件发布时触发
@ScheduledScheduledAnnotationBeanPostProcessorBean 初始化后注册定时任务
@ConfigurationPropertiesConfigurationPropertiesBindingPostProcessorBean 初始化后绑定属性

这些类确实和注解名字强相关,但注意看,它们的共性都是实现了BeanPostProcessor或其子接口。也就是说:在 Spring 的设计里,"解析注解"这个动作,默认的载体就是 Bean 的后置处理器体系。

2.2 一个处理器处理多个注解,才是常态

以AutowiredAnnotationBeanPostProcessor为例,你在源码里能看到它内部维护了一个AutowiredAnnotationType的集合,构造方法里就把@Autowired、@Value、@Inject(JSR-330)都注册进去了。字段注入、Setter 注入、构造器注入,加上@Value的占位符解析、@Lookup的方法覆盖,全都由这一个类负责。

这意味着什么?如果你带着"一个注解一个解析类"的思维去看源码,很多地方是解释不通的:为什么@Value的处理器不单独叫ValueAnnotationBeanPostProcessor?因为@Value和@Autowired在注入逻辑上是高度一致的,都需要走AutowireCapableBeanFactory做类型匹配、依赖解析、懒加载处理,硬拆成两个类只会造成重复代码和状态同步问题。

CommonAnnotationBeanPostProcessor更是典型,它一口气处理了@Resource、@PostConstruct、@PreDestroy三个注解。这三个注解分别对应依赖注入、初始化回调、销毁回调,阶段完全不同,但 Spring 依然把它们放在一个类里。因为它们的共同点是都来自javax.annotation包,都是"通用注解",统一管理更方便。

注意:这些处理器本身也是 Spring 内部的基础设施 Bean(infrastructure bean),Spring 在registerBeanPostProcessors阶段会优先注册它们,自定义的BeanPostProcessor排在后面。这个顺序很关键,后面讲排查时会再次提到。

2.3 解析类是可替换、可扩展的,不是硬绑定

还有一个容易忽略的细节:这些处理器和注解之间,不是"注解依赖处理类"的关系,而是"处理类主动声明自己接收哪些注解"的关系。

AutowiredAnnotationBeanPostProcessor提供了一个setAutowiredAnnotationTypes方法,你可以通过修改它的配置来让它识别你自定义的注解。CommonAnnotationBeanPostProcessor的init方法里可以通过setResourceFactory等方式调整行为。也就是说,Spring 只是约定了一些默认的"解析类",你完全可以注册一个全自动的BeanPostProcessor来处理你自己的注解,Spring 根本不在乎你处理的是哪个注解。

所以,如果说"每一个注解都需要有一个对应的解析类",那 Spring 的回答其实是:容器只需要注册一批解析器,至于它们各自认领哪些注解,完全由解析器内部的代码决定。

3. 绝大多数注解没有"专属解析类":它们活在通用扫描器和代理工厂里

3.1@Component一族的处理:扫描器的元注解匹配

先看一个最颠覆直觉的例子:@Component是 Spring 最基础的注解,但它没有一个叫ComponentAnnotationParser的类。

@Component及相关派生注解(@Service、@Repository、@Controller)是在ConfigurationClassPostProcessor的执行链路中被处理的。这个处理器实现了BeanDefinitionRegistryPostProcessor,在容器刷新早期触发。它的核心逻辑是ConfigurationClassParser解析各个配置类,当读到@ComponentScan注解时,调用ClassPathScanningCandidateComponentProvider去指定包下扫描候选类。

扫描器判断一个类是否够资格成为候选 Bean 时,用的是isCandidateComponent方法,内部通过@Component这个元注解去匹配。Spring 提供了一个递归查找元注解的工具,不管你是@Service还是自创的@MyComponent,只要类上有一个注解被@Component标注,或者被标注了@Component的注解再标注,扫描器都能认出来。

这个设计直接论证了"不需要每个注解一个解析类":Spring 用元注解的递归匹配,让一个扫描器通吃一族注解。你新增一个@MyComponent,什么都不用配置,它自动就被扫描器接纳了。

3.2 Web 注解的真相:组合注解与统一映射扫描

再看 Web 层。@GetMapping、@PostMapping、@PutMapping这些注解,在源码里其实都是由@RequestMapping通过@AliasFor派生出来的组合注解。Spring MVC 的RequestMappingHandlerMapping继承自AbstractHandlerMethodMapping,它在afterPropertiesSet阶段会对容器里的所有 Bean 做遍历,逐个检查类的@Controller、@RestController标记和方法上的@RequestMapping前缀。

看源码你会发现,RequestMappingHandlerMapping使用AnnotatedElementUtils.hasAnnotation来判断方法是否具备@RequestMapping语义。这个方法会自动将组合注解拆开,所以@GetMapping会被视为"映射路径=xxx,请求方法=GET"的@RequestMapping。你根本不需要为每个派生注解写一个解析器,它们全都被折叠到@RequestMapping的统一扫描逻辑里了。

顺带一提,@RestController和@Controller的关系也是类似的。RestController被@Controller和@ResponseBody标注,RequestMappingHandlerMapping扫描到@RestController时,本质上是识别到了它的元注解@Controller,从而决定把它纳入 handler 方法的扫描范围。

3.3@Transactional/@Cacheable/@Async:AOP 拦截链上的注解

这类业务语义最重的注解,才是最容易让人产生"必须有解析类"错觉的地方。以@Transactional为例,它的完整生效链路其实涉及五个角色:

  1. ProxyTransactionManagementConfiguration:通过@Import导入的配置类,负责装配事务基础设施。
  2. BeanFactoryTransactionAttributeSourceAdvisor:AOP 通知器,判断 Bean 是否需要事务代理。
  3. TransactionInterceptor:真正的拦截器,负责开启、提交、回滚事务。
  4. AnnotationTransactionAttributeSource:负责解析@Transactional注解上的属性(传播行为、隔离级别、超时等)。
  5. InfrastructureAdvisorAutoProxyCreator:一个AbstractAutoProxyCreator,在 Bean 初始化完成后检查所有 Advisor,决定是否创建代理。

这里面出现了一个"解析类"——AnnotationTransactionAttributeSource。但它不是为@Transactional量身定制的。它的构造方法允许你传入一组TransactionAnnotationParser,默认注册了SpringTransactionAnnotationParser(对应@Transactional)和Ejb3TransactionAnnotationParser(对应jakarta.ejb的@TransactionAttribute)。也就是说,它是一套可切换的解析器集合,支持"自定义注解"层面的事务属性解析,直接打破"一个注解一个解析类"的说法。

真正决定"要不要给这个 Bean 做代理"的,是 Advisor 的matches方法,它对所有 Bean 统一做匹配,命中则包装。@Cacheable、@Async的路径也和它几乎一模一样:@EnableCaching导入AutoProxyRegistrar注册基础设施,BeanFactoryCacheOperationSourceAdvisor匹配方法,CacheInterceptor拦截执行。

注意:@Enable*这一族注解本身就是靠@Import工作的。ConfigurationClassParser在解析配置类时看到@Import,就把导入的类注册成 BeanDefinition,这个机制由ImportBeanDefinitionRegistrar/ImportSelector完成。你自定义一个@EnableMyFeature注解,只需要在注解上标@Import(MyRegistrar.class),Spring 就会在解析配置类时自动调用你的 registrar,不需要任何专属解析类。

4. 注解生效时机决定了解析器设计:从容器生命周期看注解处理的三个阶段

4.1 解析不是一步完成的,而是分布到 refresh() 的多个节点

搞明白上面的问题后,你会意识到一个关键点:注解的解析时机,比解析类本身更重要。同一个注解的注解值可能在不同阶段被读取,同一个 Bean 也会先后经历多轮注解检查。我把 Spring 容器refresh()方法里几个关键节点梳理一下:

生命周期节点触发的注解处理核心组件
1. 配置类解析阶段@ComponentScan、@Import、@PropertySource、@Enable*ConfigurationClassPostProcessor、ConfigurationClassParser
2. BeanDefinition 后处理作用域修饰、@Lazy标记等ConfigurationClassBeanDefinitionReader、各种BeanDefinitionRegistryPostProcessor
3. Bean 实例化后属性填充@Autowired、@Value、@ResourceAutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor
4. 初始化前后回调@PostConstruct、@PreDestroy、@Scheduled各BeanPostProcessor
5. 初始化后代理包装@Transactional、@Async、@CacheableAbstractAutoProxyCreator、各种 Advisor
6. 事件发布阶段@EventListenerEventListenerMethodProcessor

阶段 1 发生在容器刚刷新时,它改变的是 Bean 的定义。阶段 3 和 4 发生在 Bean 实例化过程中,它们改变的是 Bean 的实例状态。阶段 5 发生在 Bean 初始化之后,直接决定你要不要返回一个代理对象。

4.2 为什么@Transactional有时在自调用时失效:时机问题的典型案例

理解了阶段分布之后,一个高频面试和排错问题就迎刃而解了:为什么同类内a()方法调用b()方法,b()上的@Transactional不生效?

因为事务代理是在阶段 5 创建的。调用方持有的 Bean 实际上已经是代理对象,拦截器只在"外部调用进入代理方法"时生效。当你在a()方法内部直接写this.b()时,这个this指向的是代理对象的内部目标对象,根本没有经过TransactionInterceptor。这是"代理时机"导致的经典失效场景。

有趣的是,如果你把@Transactional放在@Configuration类里,自调用反而可能被拦截。因为@Configuration类需要ConfigurationClassEnhancer做 CGLIB 增强,保证@Bean方法单例语义,这种增强发生在阶段 1 的解析过程中,CGLIB 子类在@Bean方法调用时会做检查。两个代理机制叠加在一起,才会产生"配置类内部自调用也过拦截器"的行为。这类细节光看"有没有解析类"是永远想不通的。

4.3 三级缓存和注解解析:一次时机上的联动

很多人在面经里看到 Spring 三级缓存原理,不明白它和注解解析有什么关系。实际上关系主要体现在@Autowired的解析时机上:

三级缓存的第三级singletonFactories里存的是ObjectFactory,它的存在目的是让 Bean 在"实例化完成但还没完成属性填充"时就能被提前引用。当 A 依赖 B、B 依赖 A 时,A 先实例化完成,把ObjectFactory放进三级缓存。B 创建时去填充依赖,通过DefaultSingletonBeanRegistry.getSingleton拿到 A 的提前引用。而 B 属性填充阶段对 A 的依赖注入,正是AutowiredAnnotationBeanPostProcessor在阶段 3 干的事。

也就是说:@Autowired的解析发生在 Bean 实例化之后、属性填充阶段,它需要从单例缓存里拿依赖;三级缓存的设计保证了即使某个 Bean 尚未完全初始化,也能先给出一个"半成品引用",让注解解析不卡死在循环依赖上。如果一个解析器想提前处理@Autowired,比如在 BeanDefinition 阶段就去做注入,反而会因为依赖尚未实例化而没有办法拿到目标对象。注解的解析时机选择,不是随意的,而是跟着 Bean 生命周期一步一步走的。

5. 遇到一个注解不知道怎么处理?用三个问题定位它的解析路径

5.1 问题一:这个注解的 Target 是什么

拿到一个注解,先看@Target。注解能标在类上、方法上、字段上,还是参数上,直接决定了它最自然的解析方式:

  • 标在 TYPE(类/接口)上:大概率影响 BeanDefinition 或 Bean 的创建方式,走BeanFactoryPostProcessor、扫描器、@Import那条路最合适。比如自定义@MyRepository,本质就是@Component的别名,交给扫描器即可。
  • 标在 METHOD/FIELD 上:影响属性填充、初始化行为,走BeanPostProcessor最合适。比如自定义@MyConfigValue要在字段上注入配置值,模仿@Value的处理方式,写一个BeanPostProcessor在postProcessProperties里做反射赋值即可。
  • 标在 METHOD 上表示要拦截增强:比如@MyAuditLog要记录方法执行时间,走 AOP 切面最合适,因为被拦截的是"方法调用"这个行为,AOP 天生就是干这个的。

所以第一步就是问:这个注解要贴在哪里?贴的位置暗示了你要处理的程序元素类型,也就暗示了要参与的生命周期阶段。

5.2 问题二:这个注解的语义是改定义、改实例,还是拦截行为

注解的语义大概有三种,对应的解析手段完全不同:

语义类型要做的事推荐解析方式典型例子
改变 Bean 定义新增 BeanDefinition、修改作用域、注册额外 BeanBeanDefinitionRegistryPostProcessor+@Import(ImportBeanDefinitionRegistrar)@ComponentScan、@Enable*
改变 Bean 实例状态给属性赋值、执行初始化方法BeanPostProcessor/InstantiationAwareBeanPostProcessor@Autowired、@PostConstruct
拦截方法调用加日志、加缓存、加事务、加权限AOP 切面(@Aspect)或 Advisor +AbstractAutoProxyCreator@Transactional、@Cacheable

对着这个表就知道,@Component别想用 AOP 去解析,因为你无法"切入一个正在生成的 BeanDefinition";@Transactional别想用普通的BeanPostProcessor在属性填充阶段去解析,因为事务关心的不是 Bean 的内部状态,而是方法执行时的拦截。

5.3 问题三:解析产物要交给谁

最后一个问题,也是决定"要不要为这个注解单独设计一个解析链"的问题:你的解析产物是什么?

  • 如果解析产物是一批新的BeanDefinition,那应该registerBeanDefinition到容器里,交给后面的getBean流程继续处理。
  • 如果解析产物是某个字段的值,那你只需要修改目标 Bean 的实例,不需要通知容器。
  • 如果解析产物是"要织入一个拦截逻辑",那你需要创建代理对象,并把代理丢回容器;此时最优雅的做法是直接实现一个Advisor,而不是自己搞一个ProxyFactory去包。

我自己在开发中判断的标准很简单:如果这个注解的解析结果要被 Spring 容器后续大量使用,比如影响了其他 Bean 的实例化,那么优先走 BeanFactoryPostProcessor 一脉;如果只是局部增强,优先走 AOP,因为 AOP 框架帮你处理了代理的生成、排序、合并,比自己造轮子稳得多。

6. 实战推演:自定义@ApiLog注解的四种解析方案与选型

6.1 需求场景

假设我们要给某个@RestController的接口加一个@ApiLog("根据ID查询订单")注解,作用是自动记录方法入参、返回结果、执行耗时。这是个非常经典的业务需求,一类人把它做成 AOP 切面,另一类人上来就琢磨"要不要写一个 ApiLogAnnotationParser"。

6.2 方案 A:纯反射扫描 + 手动包装(不推荐)

最本能的做法,是在容器启动完成后,通过ApplicationContext.getBeansWithAnnotation或者包扫描把所有带@ApiLog的方法找出来,然后尝试包装。但这个方案在 Spring 环境里实现起来很别扭:你已经拿到的是容器里的 Bean 实例,想拦截调用就必须主动创建代理,还得考虑代理是否和已有的@Transactional代理叠加,代码很快就变成一团乱麻。所以这个方案我只在纯 JavaSE 项目里用,Spring 项目不推荐。

6.3 方案 B:AOP 注解切面(最推荐)

直接在项目里加一个@Aspect类,用切点表达式定位注解:

@Aspect @Component public class ApiLogAspect { @Around("@annotation(apiLog)") public Object around(ProceedingJoinPoint joinPoint, ApiLog apiLog) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long cost = System.currentTimeMillis() - start; // 记录入参、出参、耗时,apiLog.value() 是注解里填的日志描述 return result; } }

配合spring-boot-starter-aop,一切交给 AspectJ 的 advisor 机制处理。为什么这个方案最省心?因为 Spring AOP 框架的三件套(AnnotationMatchingPointcut负责定位注解、AspectJAroundAdvice负责拦截、AnnotationAwareAspectJAutoProxyCreator负责生成代理)已经全部为你搭好了,你只需要关心业务代码。而且@Around的切点表达式@annotation(apiLog)本身就实现了"按注解匹配方法"的能力,你不需要写任何解析类。

6.4 方案 C:BeanPostProcessor + ProxyFactory(可控但繁琐)

如果你想完全掌控代理的创建过程,可以用BeanPostProcessor:

@Component public class ApiLogBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 判断 bean 的类中是否有 @ApiLog 标注的方法 if (!hasApiLogMethod(bean.getClass())) { return bean; } ProxyFactory factory = new ProxyFactory(bean); factory.addAdvice(new ApiLogInterceptor()); return factory.getProxy(); } }

这个方案能解决"特定 Bean 才做代理"的问题,但坑也不少:你要处理 JDK 代理和 CGLIB 代理的选择、防止多次代理、保证和@Transactional代理的共存(AbstractAutoProxyCreator内部有缓存机制,而你手动创建的代理不会进缓存)。所以我的建议是:除非你确实需要控制代理创建的每一个环节,否则不要重复造轮子,AOP 切面已经足够。

6.5 方案 D:编译期注解处理器 APT(性能至上)

如果你追求零运行时反射开销,可以在编译期写一个AbstractProcessor,扫描@ApiLog并生成对应的切面代码或日志代码。但这意味着注解处理器要能拿到完整的 AST,并且把生成的代码集成到现有工程里,维护成本高。在 Spring 生态里,除非是框架底层做基础设施(比如 MapStruct、Lombok),否则业务项目不要这么干。

6.6 四种方案对比

方案运行时开销开发复杂度与 Spring 契合度适用场景
纯反射扫描高低低JavaSE 静态元数据处理
AOP 切面低(方法级代理)极低极高日志、缓存、权限等 90% 业务注解
BeanPostProcessor + ProxyFactory中等高中需要注入 Bean 实例元数据且不用表达式切点
编译期 APT零(编译期生成)极高低框架底层,性能敏感代码

看到这里你应该已经明白了,在 Spring 里为一个自定义注解选解析方案,核心并不是"给它写一个解析类",而是选择合适的处理器载体。

提示:如果你只是在手写一个简化版 Spring 的练手项目,最贴近框架原型的顺序是:配置类解析器发现@ComponentScan-> 扫描器找到@Component-> 实例化时用BeanPostProcessor处理@Autowired和@Value-> 初始化后用切点匹配处理@Transactional。这个顺序本身就是 Spring 注解语义的骨架,不用为每个注解单独造轮子。

7. 排错记录:注解不生效时,我一般按这个顺序排查

既然聊到解析类的问题,最后分享一些我踩坑后沉淀下来的排查顺序。每次有人拿代码过来说"我加了@Async但没异步执行""@Autowired注入进来是 null",我都会让他按下面这个顺序自查:

第一,看注解的Retention和Target。@Retention不是RUNTIME的注解,反射根本读不到,Spring 再强也拿它没办法。自定义注解默认RetentionPolicy.CLASS,很多人栽在这里。Target写错了位置,比如想标方法却写成了 TYPE,扫描器从AnnotatedElement上拿到的注解信息就走不到预期分支。

第二,看处理这个注解的BeanPostProcessor/ Advisor 到底有没有注册进容器。用applicationContext.getBeansOfType(BeanPostProcessor.class)打出来看一眼。很多项目引了spring-boot-starter-aop,AOP 的基础设施才注册;引了@EnableAsync,异步 BeanPostProcessor 才会存在。缺少了注册环节,你用@Async当然没效果。

第三,看 Bean 的实际类型。打断点或者直接打印bean.getClass(),确认它不是$$EnhancerBySpringCGLIB$$或$Proxy开头。如果日志里根本没有代理类,那说明 AOP 这层压根没进。常见原因是切点表达式写错,或者@Aspect类本身没被扫描到——注意,@Aspect类必须也是一个 Spring Bean,且@EnableAspectJAutoProxy已开启。

第四,排查自调用和私有方法。@Transactional、@Async都不能标注在 private 方法上,因为代理只能拦截外面进来的调用。同类内部this调用不走代理,这是老生常谈,但依然每天都在发生。

第五,检查是否有自定义BeanPostProcessor干扰。如果你自己注册了一个和内置处理器同类的BeanPostProcessor,或者往AutowiredAnnotationBeanPostProcessor里塞了额外的注解类型,可能导致原有注解的行为发生偏移。这种情况调试起来最磨人,我一般会把自定义处理器逐个注释掉做二分法定位。

排查完这一整套,你就会发现大多数"注解不生效"的问题,根源都落在注册、时机、代理、反射读取这四个环节里,和"有没有对应的解析类"关系不大。

回头再看开头那个问题,我现在的标准回答是:Spring 里的注解解析不是靠"一注解一解析类"堆出来的,而是把少量核心处理器安插在容器的关键生命周期节点上,让它们统一解释各类注解的语义。作为使用者,你真正要做的不是数清楚每个注解背后站着谁,而是想明白一件事——你关心的注解语义,属于容器创建 Bean 的哪个阶段、该交给扫描器、BeanPostProcessor、BeanFactoryPostProcessor,还是 AOP 拦截链。把这套判断练熟了,再看任何 Spring 注解的源码,都会觉得顺理成章。

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

从Teradata与SAP HANA八年诉讼,看数据库一体机的兴衰与选型

数据库一体机这个词&#xff0c;放到今天听起来已经有点古董味了。但在2010年前后&#xff0c;它是数据仓库圈子里最硬核的一条赛道&#xff0c;而Teradata和SAP HANA就是在这条赛道上正面相撞&#xff0c;打了一场长达八年的官司&#xff0c;最终以一笔接近4.8亿美元的赔偿金收…

作者头像 李华
网站建设 2026/10/11 6:31:33

组合式非结构化数据分析实战:从文档解析到大模型抽取的采购单据处理

平时接数据分析和自动化处理的活儿&#xff0c;我大部分时间都耗在两类事情上&#xff1a;一是把 PDF、网页、图片里那些格式混乱的内容抽出来&#xff0c;二是把这些半结构化甚至完全非结构化的数据规整成能塞进数据表或数据库的样子。之前一直靠正则表达式和各种解析脚本凑合…

作者头像 李华
网站建设 2026/10/11 6:30:46

SpringBoot+微信小程序:社区医疗服务系统全栈开发实战

初识这个项目&#xff1a;社区医疗服务为什么要上小程序这两年社区医疗的需求越来越细&#xff0c;居民不再满足于“能挂个号、能拿点药”&#xff0c;而是希望在家门口解决建档、随访、慢病管理、预约接种这类高频小事。但很多社区卫生服务中心的系统还停留在PC端&#xff0c;…

作者头像 李华
网站建设 2026/10/11 6:30:30

YOLOv11目标检测:从PyTorch训练到ONNX部署全流程实战

简介&#xff1a;面向零基础学习者的目标检测实战文档&#xff0c;系统讲解YOLOv11从PyTorch训练到ONNX跨平台部署的完整链路。内容涵盖YOLOv11核心架构、环境搭建、数据准备与标注、模型训练及评估优化、ONNX转换与跨平台部署&#xff0c;并附常见问题解决方案&#xff0c;适合…

作者头像 李华
网站建设 2026/10/11 6:30:12

性能测试调优实战:从JMeter基线到存储与链路优化

做性能测试这行有个特点&#xff1a;表面看是压测、调参数、看报告&#xff0c;实际上每一次有效果的优化背后&#xff0c;都是对存储、链路、运行时的整体理解。标题里“积微成著”这四个字我越来越有体会&#xff0c;性能问题几乎很少是单一原因造成的&#xff0c;真正见效快…

作者头像 李华