文章目录
- 0.前言
- 1.什么是Aware接口
- 2.Aware接口的设计目的
- 3.详解
- 3.1. `ApplicationContextAware`
- 我们举个例子来说明
- 3.2. `BeanFactoryAware`
- 3.3. `BeanNameAware`
- 3.4. `ServletContextAware`
- 3.5. `MessageSourceAware`
- 3.6. `ResourceLoaderAware`
- 4.参考文档
0.前言
背景: 最近有开发同学问我项目里面有很多地方实现了Aware 接口,到底是什么作用。他说这个原因是在我们的工程代码中看到有一些代码里实现了ResourceLoaderAware、BeanNameAware、ApplicationContextAware、BeanFactoryAware接口。
我没法直接笼统回答,也没有法一句话简答,只好侧面的告诉他Aware接口是Spring 为了提供拓展的一种设计思想。凡是带Aware后缀的接口。主要作用是为了让实现了该接口的Bean在Spring容器中具有回调功能。通过实现不同子接口,可以让Bean在运行过程中获取Spring容器内部的一些特定对象
。
为了进一步论证我给的答案,今天抽时间把项目里面的Aware的实现或者间接实现的类大概看了一下。包含Spring框架和第三方框架在内,大概有673个实现使用到。
1.什么是Aware接口
我们先来看一下所有Aware接口的超接口的定义org.springframework.beans.factory.Aware
根据注释我们可以了解到 org.springframework.beans.factory.Aware
标记超接口,指示一个Bean有资格通过回调方式接收Spring容器传递的特定框架对象。实际的方法签名由各个子接口决定,但通常应该只包含一个接受单一参数的返回void的方法。这个翻译有点抽象 通俗的说 这个Aware接口是Spring框架中的一个机制,允许Bean通过回调方式与Spring容器进行交互,并获取特定的框架对象,以便在Bean的逻辑中进行相应的处理
。
Spring框架中有一系列的Aware接口,这些接口是用于让实现它们的Bean获得一些Spring框架的对象或资源,这些资源包括ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher等等。
Aware接口名称通常是以Aware结尾,例如最常见的ApplicationContextAware和BeanFactoryAware,它们分别可以让Bean获得ApplicationContext和BeanFactory。
例如,如果一个Bean实现了ApplicationContextAware接口,那么在Spring创建这个Bean的时候,会自动调用setApplicationContext方法,将ApplicationContext作为参数传入,这样这个Bean就可以在运行时获取到Spring的ApplicationContext。
通常并不建议直接使用Aware接口,因为这样会导致代码与Spring框架耦合过于紧密。在大多数情况下,我们可以通过依赖注入或者其他更好的方式来获取Spring框架的资源。除非为了实现特定功能,我们才会实现对应的Aware接口来获取资源。
2.Aware接口的设计目的
这样设计的目的是为了让Bean可以与Spring框架的其他部分相互作用,而不是仅仅被动地接受由Spring容器创建和管理。
我觉得这样的设计 还有一个好处 。可以直接通过名称+Aware后缀就可以知道这个Aware接口具备的能力。可以直接获取或操作Spring框架的一些资源,换句话来讲,其实也是Spring的格局比较高 使得Spring框架“让普通Java对象能够使用框架的全功能”的一种体现。
所以,通常来说,接口名的规则是:[感知/获得的资源或环境] + Aware例如BeanFactoryAware
、ResourceLoaderAware
。
我们观察一下Aware接口及其子接口的命名规则很简单,通常是在Aware后面添加相应的功能名。这些功能名通常代表了实现该接口的类需要获得的资源或者感知的环境。例如:
-
ApplicationContextAware
:意味着实现该接口的类可以感知到应用上下文(ApplicationContext
)。 -
BeanFactoryAware
:实现该接口的类可以感知到Bean工厂(BeanFactory
)。 -
BeanNameAware
:实现该接口的类可以感知到Bean的名字。 -
ServletContextAware
意味着实现该接口的类可以感知到Servlet上下文(ServletContext
)。 -
MessageSourceAware
:实现该接口的类可以感知到消息源(MessageSource
)。
-ResourceLoaderAware
:实现该接口的类可以感知到资源加载器(ResourceLoader
)。
3.详解
我们分别来了解一下这些常见的Aware接口的使用场景和用法 ApplicationContextAware
、BeanFactoryAware
、 BeanNameAware
、 ServletContextAware
、MessageSourceAware
、 ResourceLoaderAware
。
3.1. ApplicationContextAware
这个接口我们太常见了。几乎大多数项目中都会用到。因为它能取到。它会在一个类需要访问Spring应用上下文中的所有服务(例如其他bean,生命周期事件,特定配置等)时被实现。
ApplicationContextAware
是 Spring 框架的一个接口,当一个类实现了这个接口,那么这个类就会具备访问 Spring ApplicationContext 的能力。也就是说,这个类可以直接获取到 spring 容器中的所有 bean。
这个接口主要有一个方法:setApplicationContext(ApplicationContext applicationContext)
。Spring 容器会检测所有 bean,如果发现某个 bean 实现了 ApplicationContextAware
接口,Spring 容器就会在创建该 bean 后,自动调用这个 bean 的 setApplicationContext(ApplicationContext applicationContext)
方法,调用该方法时,会将容器本身作为参数传给该 bean。这样,当 bean 需要使用容器的功能时,就可以使用这个传入的参数。
这个接口通常在需要访问容器本身的功能,或者需要访问容器中的其他 bean 时使用。例如,当 bean 需要动态地查找其他 bean,或者需要使用容器的发布事件功能时,就可以通过实现这个接口来获取到容器对象。
一般情况下,我们不需要直接实现这个接口,因为 Spring 提供了 ApplicationObjectSupport
类,这个类实现了 ApplicationContextAware
接口,我们可以通过继承这个类来简化我们的代码。
我们举个例子来说明
下面是一个具体的计算服务示例,它使用 ApplicationContextAware
接口来获取一个账户服务。
CalculationService
类实现了 ApplicationContextAware
接口,并重写了 setApplicationContext
方法。当 Spring 容器创建 CalculationService
实例之后,就会自动调用 CalculationService
的 setApplicationContext
方法,并将容器本身作为参数传入。
然后,CalculationService
就可以通过 applicationContext
成员变量来访问容器本身,从而获取到容器中的 AccountService
,并使用该服务来进行利息计算。
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CalculationService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public double calculateInterest(String accountId, double principal, double rate, double time) {
// 通过 applicationContext 获取 Spring 容器中的 AccountService bean
AccountService accountService = applicationContext.getBean(AccountService.class);
// 使用获取到的 AccountService 进行操作
double interest = accountService.calculateInterest(principal, rate, time);
return interest;
}
}
3.2. BeanFactoryAware
当一个类需要访问Spring BeanFactory,以获得对Spring框架的底层访问时,会实现这个接口。
我们举个例子来理解一下,比如我们大多项目都会发送通知,可能需要邮件,微信,短信,飞书这些,这些配置可以允许用户自定义配置,后续系统还可能扩展钉钉等其他类型通知。为了满足这个需求。我们利用 BeanFactoryAware
创建一个服务定位器(Service Locator)。服务定位器用于集中管理和查找服务。在复杂的应用中,这可以使代码更容易管理和扩展。
首先考虑我们有许多服务,如 EmailService
,SMSService
,PushNotificationService
等,都实现了一个 MessageService
接口。我们可以使用 BeanFactoryAware
来创建一个服务定位器,能够根据需要获取任何服务。
@Service
public class ServiceLocator implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public MessageService getService(String serviceName) {
return (MessageService) beanFactory.getBean(serviceName);
}
}
然后在需要服务的地方,我们可以这样使用:
@Service
public class MessageClient {
private final ServiceLocator serviceLocator;
public MessageClient(ServiceLocator serviceLocator) {
this.serviceLocator = serviceLocator;
}
public void sendMessage(String message, String serviceName) {
MessageService service = serviceLocator.getService(serviceName);
service.send(message);
}
}
ServiceLocator
通过实现 BeanFactoryAware
接口,可以获取到 Spring 容器,然后根据服务名获取相应的服务。MessageClient
通过 ServiceLocator
可以灵活地选择需要的服务,使得代码更加灵活和可测试。
剩下这四个也很常见,明天有时间再搞吧,今天太晚了就到这
3.3. BeanNameAware
当一个类需要知道在Spring配置中定义的它的bean名称时,会实现这个接口。
3.4. ServletContextAware
这个接口会在一个类需要获取到ServletContext,通常用在需要访问Servlet API的web应用中时被实现。例如,需要读取web应用中的某些real path,或者servlet context attributes等。
3.5. MessageSourceAware
当一个类需要加载一些消息资源,如一些国际化的字符串时,会实现这个接口。
3.6. ResourceLoaderAware
当一个类需要加载一些文件资源时,会实现这个接口。这可以让你更方便的加载classpath或者文件系统中的资源。
4.参考文档
- Spring 官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware
- Spring 框架 API 文档(有关
BeanFactoryAware
的部分):https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactoryAware.html - 关于服务定位器模式的解释:https://www.martinfowler.com/eaaCatalog/serviceLocator.html
- 使用 Spring 实现服务定位器模式的示例:https://www.baeldung.com/spring-service-locator