1. 先搞懂Aware是什么
1.1 一个让人困惑的单词
第一次看到Spring里那个叫Aware的东西,估计不少人都跟我一样愣了半天。Aware翻译过来是“感知、知道”的意思,但在Spring框架里它并不是一个具体的功能类,而是一系列以Aware结尾的接口的总称。比如BeanNameAware、ApplicationContextAware、BeanFactoryAware,这些名字看着相似,用起来却各有各的门道。
简单说,Aware就是Spring容器给Bean开的一扇窗。窗口打开之后,Bean就能拿到容器内部的资源,比如自己的Bean名字、当前的ApplicationContext、BeanFactory、类加载器、环境变量配置等等。很多人在刚学Spring的时候会想:我要拿容器对象,直接@Autowired不就完事了吗?为什么还要搞出一堆Aware接口?这个问题我后面会专门讲,你先记住一个结论:Aware不是多余的,它在Spring内部扮演着非常底层的角色,很多框架级的功能都依赖它。
1.2 Aware到底解决了什么问题
要理解Aware,得先从Bean的创建过程聊起。Spring容器在启动的时候,负责创建和管理Bean,但Bean本身并不知道自己活在哪个容器里,也不知道自己叫什么名字。这就像住酒店里的客人,房间服务再周到,客人不会主动知道保洁阿姨几点来打扫,除非酒店通过床头提示牌主动告诉你。
Aware就是那张床头提示牌。Spring在初始化Bean的过程中,检测到这个Bean实现了某个Aware接口,就会在合适的时机调用对应的方法,把容器内部的资源主动塞进去。这样Bean就能在后续的业务代码里使用这些资源了。
所以Aware解决的核心问题是:让业务代码能够安全地拿到Spring容器内部的回调资源,同时又不破坏Spring本身的管理规则。这里有个很关键的点——Aware是Spring框架自己定义的回调机制,它发生在Bean的生命周期早期,早于@PostConstruct、早于InitializingBean,更早于业务方法调用。这种时机上的优势,决定了它在很多底层组件里无可替代。
2. 常用的Aware接口全家桶
2.1 认识几个高频Aware
Spring内置的Aware接口数量不少,但日常开发真正高频用到的就那么几个。我按使用频率给你排个序:
- ApplicationContextAware:最常用的一个。实现后可以拿到ApplicationContext,进而获取任意Bean、发布事件、访问资源等。
- BeanNameAware:让Bean知道自己注册到容器时的名字。注意是Bean的id,不是类名。
- BeanFactoryAware:拿到BeanFactory,比ApplicationContext更底层。一般写框架代码才用。
- EnvironmentAware:拿到Environment对象,用来读取配置属性,比如@Value的来源之一。
- ResourceLoaderAware/ResourcePatternResolverAware:拿到资源加载器,用于加载classpath下的文件或通配符路径的资源。
- ApplicationEventPublisherAware:拿到事件发布器,发布Spring事件。
- MessageSourceAware:拿到国际化资源。
- EmbeddedValueResolverAware:拿到字符串解析器,可以解析占位符${...}。
这些接口在Spring中都是标记接口加一个setter方法。比如ApplicationContextAware只有一个方法:
public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }实现类只需要写上这个方法,Spring容器就会在初始化阶段帮你把对应的对象传进来。记住这个模式:Aware接口 = 一个setter方法。
2.2 什么时候用哪个Aware
很多初学者容易搞混这几个Aware的使用场景。我的建议是记住下面这个判断逻辑:
如果你只是想在业务代码里换一种方式获取ApplicationContext,首选ApplicationContextAware。网上很多工具类用静态工厂拿ApplicationContext,通常就是这么干的。
如果你需要判断当前Bean在容器中的注册id,比如要根据BeanName去查配置文件或做条件逻辑,用BeanNameAware。
如果你在做Spring扩展开发,比如自定义标签解析、注册BeanDefinition、或者想绕过ApplicationContext的封装直接操作BeanFactory,那就要用BeanFactoryAware。
如果你只是需要读取配置文件里的一个值,其实没必要用Aware,用@Value注解就够了。但如果你的代码运行在非Spring管理环境中,需要动态解析占位符,那EmbeddedValueResolverAware会更合适。
拿我自己的经验来说,写业务代码时用ApplicationContextAware最多,写框架代码或中间件时BeanFactoryAware和EnvironmentAware最有用。其余的Aware接口平时很少单独打交道,但看Spring源码时都会看到它们的身影。
3. 源码级拆解Aware的实现原理
3.1 从BeanPostProcessor说起
很多初学者以为Aware的注入是Spring用反射直接调用的,其实没那么简单。整个Aware机制的核心,是一个叫做ApplicationContextAwareProcessor的内部类。这个类本身是一个BeanPostProcessor,而BeanPostProcessor是Spring容器暴露给外界的扩展点,会在每个Bean初始化前后插入回调逻辑。
Spring容器在刷新时,会调用prepareBeanFactory方法,在这个方法里注册一堆专门处理Aware的BeanPostProcessor。其中最关键的一段逻辑,大致是这样:
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));注册之后,每当容器创建任意一个Bean,都会经过这个Processor的postProcessBeforeInitialization阶段。这个阶段的作用,就是检查当前Bean是否实现了相关Aware接口,如果实现了,就调用相应的setter方法把资源传进去。
这解释了为什么Aware能拿到容器对象——它不是业务代码主动调用的,而是容器在Bean初始化流程中“顺便”完成的被动注入。
3.2 ApplicationContextAwareProcessor的工作流程
我们看一下ApplicationContextAwareProcessor内部对ApplicationContextAware的处理逻辑。它的核心是invokeAwareInterfaces方法。这个方法会按顺序判断当前Bean的类型,依次调用不同的setter方法:
- 如果是EnvironmentAware,调用setEnvironment,传入容器当前的Environment对象。
- 如果是EmbeddedValueResolverAware,调用setEmbeddedValueResolver,传入字符串解析器。
- 如果是ResourceLoaderAware,调用setResourceLoader,传入ApplicationContext自身(因为它实现了ResourceLoader)。
- 如果是ApplicationEventPublisherAware,调用setApplicationEventPublisher,同样传入ApplicationContext自身。
- 如果是MessageSourceAware,调用setMessageSource。
- 如果是ApplicationContextAware,最后调用setApplicationContext。
注意这个顺序。为什么ApplicationContextAware被放在最后?因为前面几个接口能拿到的对象都相对“轻量”,而ApplicationContext全局信息最多,放在最后处理可以减少重复判断的干扰。这个顺序在自定义扩展时需要关心,否则可能会有意外覆盖。
还有一个重要细节:ApplicationContextAwareProcessor只有在Bean实现了相关Aware接口时才会调用对应方法,没有实现就不会执行任何操作,所以即使所有Bean都经过这个处理器,也不会带来明显的性能损耗。
3.3 Aware注入的时机和顺序
整个Bean的生命周期大致是这样的:实例化 -> 属性填充 -> Aware回调 -> BeanPostProcessor前置处理 -> 初始化方法 -> BeanPostProcessor后置处理。
严格来说,Aware回调发生在属性填充之后。也就是说@Autowired和@Resource的依赖注入先完成,然后才会执行Aware方法。但这个说法不够精确,因为ApplicationContextAwareProcessor本身是一个BeanPostProcessor,它的postProcessBeforeInitialization方法在Bean属性填充之后、初始化方法之前执行,所以你可以理解为:
- 属性注入阶段:@Autowired、@Value等注解被处理。
- Aware回调节点:Spring调用aware接口的setter方法。
- 初始化阶段:@PostConstruct注解方法、InitializingBean的afterPropertiesSet方法被调用。
因此,如果你在Aware方法里访问@Autowired注入的属性,是可以拿到的;但如果在@PostConstruct方法里依赖Aware已经注入完成,也同样是成立的。所以它们的先后顺序是:注入属性 -> Aware -> PostConstruct。
这个顺序在实际开发中很有用。比如你想在Aware方法里做点基于容器的准备工作,但需要依赖某个Service,这时@Autowired已经完成,可以放心调用。
4. 亲手实现一个自定义Aware
4.1 自定义Aware接口
理解了内置Aware的原理,自己实现一个Aware就很简单了。你只需要做两件事:定义一个继承Aware接口的扩展接口,再写一个BeanPostProcessor来识别并注入对象。
我先展示一个实际场景。假设我们要做一个日志追踪系统,希望每个业务Bean都能拿到一个全局的唯一请求ID生成器,并且这个生成器不需要业务代码通过构造器传入。那我就可以定义一个自定义Aware:
public interface RequestIdAware extends Aware { void setRequestIdGenerator(RequestIdGenerator generator); }接口名随意,但继承Aware就行了。这里故意没加@Component,因为Aware机制的实现不需要接口本身注册到容器。
4.2 实现BeanPostProcessor完成注入
然后写一个BeanPostProcessor,在postProcessBeforeInitialization阶段检查当前Bean是否实现了RequestIdAware。如果实现了,就调用setRequestIdGenerator,把RequestIdGenerator的实例传进去。
@Component public class RequestIdAwareProcessor implements BeanPostProcessor { private final RequestIdGenerator requestIdGenerator; public RequestIdAwareProcessor(RequestIdGenerator requestIdGenerator) { this.requestIdGenerator = requestIdGenerator; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RequestIdAware) { ((RequestIdAware) bean).setRequestIdGenerator(requestIdGenerator); } return bean; } }注意两个坑:第一,postProcessBeforeInitialization方法一定要返回原Bean,不能返回null;第二,如果你想让这个Processor自己也使用Spring容器管理的RequestIdGenerator,那直接通过构造器注入即可,但这里会产生一个鸡生蛋的问题:RequestIdAwareProcessor本身是一个Bean,它也会经过Aware回调和BeanPostProcessor处理。但只要这个Processor不实现自己处理的接口类型,就没有问题。
使用的时候,业务类只需要这样写:
@Component public class OrderService implements RequestIdAware { private RequestIdGenerator requestIdGenerator; @Override public void setRequestIdGenerator(RequestIdGenerator generator) { this.requestIdGenerator = generator; } public void createOrder() { String requestId = requestIdGenerator.generate(); // 业务逻辑... } }对比一下,如果直接用@Autowired注入RequestIdGenerator,效果是一样的。那为什么还要费劲自定义Aware?
4.3 使用@Autowired和Aware的区别
这个区别是面试常问,也是理解Aware价值的关键。
@Autowired是Spring在Bean属性填充阶段完成的依赖注入。它必须要求依赖对象已经在容器中准备好,并且注入的类型是明确的。而Aware机制是通过BeanPostProcessor在Bean实例化之后统一回调的,它的优势在于不依赖注解解析器,完全通过类型判断接口来实现。这意味着我们可以用Aware把某些只能通过程序化方式获取的对象(比如非Bean对象、动态代理对象、自定义上下文对象)注入到所有Bean中,而不需要每个Bean都写一个@Autowired字段。
另外还有一个细微差别。@Autowired可以被用在字段、构造器、Setter方法上,而Aware方法必须是public的。如果你开发的是给第三方使用的框架,提供Aware接口比依赖用户写@Autowired更稳妥,因为用户可能忘了加注解,或者框架对象根本不在Spring容器中。
我做过的几个内部组件都倾向使用自定义Aware,原因很简单:它能让我在封装组件时,把内部核心对象“定向”注入给需要的人,而且调用时机恒定,方便统一处理。对于很多组件使用者来说,只需要实现一个接口,不像@Autowired那样要去理解依赖注入的完整规则。
5. 常见问题与排查技巧实录
5.1 为什么@Autowired有时拿不到ApplicationContext
经常有人问:既然ApplicationContext可以这样获取,那是不是可以在普通工具类里直接@Autowired一个ApplicationContext?答案是可以,但有个前提:工具类本身得是Spring管理的Bean。如果工具类是自己new出来的,或者从静态上下文获取的,那么@Autowired根本不会执行,拿到的一定是null。
这时候悍然改用ApplicationContextAware,把ApplicationContext存到一个静态变量里,反而能正常工作。实现方式非常经典:
@Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextHolder.applicationContext = applicationContext; } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } }这里有一个极其常见的坑:SpringContextHolder本身必须是Spring管理的Bean。如果你忘了加@Component,setApplicationContext永远不被调用,静态变量自然是null。另外在单元测试里如果没启动Spring容器,也会遇到同样的问题。我的习惯是写一个启动检查,在setApplicationContext里判断是否重复调用,避免容器刷新多次导致覆盖。
5.2 Aware方法执行顺序的坑
之前讲过,多个Aware方法的执行顺序是固定的。但如果你在同一个类里实现了多个Aware接口,比如同时实现EnvironmentAware和ApplicationContextAware,千万不要假设ApplicationContext一定在Environment之后或之前。这个顺序在Spring源码里是写死的,但你如果依赖了这个顺序,将来升级Spring版本时可能有风险。
我的教训是:一个类尽量不要实现多个Aware接口,除非你能接受框架内部顺序的变数。如果确实需要多个资源,可以考虑拆分为不同类型的处理器,或者把其中一个资源封装到另一个资源中。例如ApplicationContext本身就可以获取Environment,那你只需要实现ApplicationContextAware就够了。
还有一个顺序问题是自定义BeanPostProcessor造成的。如果你自己写了一个BeanPostProcessor,也做得是Aware类似的事情,那么你的Processor和Spring内置的ApplicationContextAwareProcessor谁先执行?这取决于PostProcessorRegistrationDelegate的排序规则。默认情况下,Spring内部注册的Processor优先级最高,自定义Processor除非实现了PriorityOrdered或Ordered接口,否则排在内置之后。所以你的自定义Aware方法会在Spring内置Aware之后被调用。如果你在自定义Aware中希望内置Aware已经完成,这个顺序是有保障的。
5.3 静态方法里怎么用Aware
这是最常被问到的一个问题。Spring容器创建的Bean是单例的,但方法调用有可能发生在静态上下文中。虽然我们一般不建议用静态方法操作实例Bean,但如果确实要这样做,利用静态版SpringContextHolder是最常见的方案。
一个更稳妥的做法是:在Aware方法被调用的瞬间,就把实例引用赋给静态字段。因为静态字段属于类级别,不依赖实例生命周期,所以后续在静态方法中也能访问。但你要注意线程安全问题。比如上面写的SpringContextHolder,它保存的是Spring初始化过程中还没完全确定的对象?不是,ApplicationContext在setApplicationContext调用时已经完成了refresh的核心阶段,这个对象可以使用。真正会出问题的是你保存业务Bean到静态字段,如果该Bean是原型作用域的,多个线程就会互相覆盖。
所以我的建议是:尽量只把容器级对象如ApplicationContext、Environment保存到静态字段,业务Bean不要这样搞,因为作用域和生命周期无法保证。
5.4 关于Aware和InitializingBean、@PostConstruct的组合使用
如果你在同一个Bean里既有Aware方法、又有@PostConstruct、还实现了InitializingBean,那么执行顺序是:Aware回调的setter方法 -> afterPropertiesSet -> @PostConstruct。这个顺序是我亲自验证过的,也建议你在自己的项目中打日志确认。很多搞不清的开发者喜欢在@PostConstruct里使用Aware注入的对象,实际上这时已经可用了,因为Aware先执行。
反过来,如果你需要在Aware方法里依赖于@PostConstruct中初始化好的状态,那就有问题了,因为@PostConstruct还没执行。遇到这种情况,应该把状态初始化的逻辑挪到@PostConstruct之前,或者直接放到Aware方法里统一处理。我比较推荐的原则是:Bean内部的初始化逻辑尽量收敛到一个方法里,不要分散在多个回调中,否则排查问题的难度会指数上升。
最后再分享一个小技巧:自定义Aware处理器的时候,尽量不要在BeanPostProcessor里做太重的业务逻辑,因为每个Bean初始化时都会经过这个方法。如果你在postProcessBeforeInitialization里执行了数据库查询或远程调用,那么整个Spring容器启动速度会被拖慢。我见过一个项目,就因为在自定义Aware处理逻辑里加载了权限配置,导致启动耗时从3秒变成20多秒。后来改成懒加载才解决。这个坑希望你别踩。