【从零构建Spring|第八节】 Aware感知容器

【从零构建Spring|第八节】 Aware感知容器

前言

为什么需要 Aware?

Aware 能将业务代码和框架功能解耦,本质上就是自定义 Bean 后置处理器,在框架类代码中可以用来暴露自己的内部成员。类似这样的设计在很多框架中都有体现,用于给外部做扩展

Spring 依赖注入的最大特点就是所有Bean 对象 对于 Spring 容器的存在是无感知的。但是在实际项目中,我们还是要用到 Spring 容器内部所提供的资源(通过 Bean 调用 Spring 容器组件资源)

举个实际案例,以下内容是我询问 chatgpt 所得到的回答。

Q:

请举一个使用 bean 需要知道 Spring 容器的案例,以证明 Aware 接口的必要性

A:

一个典型的案例是使用 ApplicationContextAware 接口,该接口使得我们能够在 Bean 中直接获取 Spring 容器,从而实现更高级别的操作。

举个例子,假设我们需要在一个自定义的 Bean 中使用 Spring 容器中的某些资源(如Bean、Properties文件等),如果不使用Aware接口,我们通常需要手动创建一个BeanFactory或者ApplicationContext,并通过getBean()方法获取需要的资源,如下所示:

public class MyBean {
private Resource myResource;

public void setMyResource(Resource myResource) {
this.myResource = myResource;
}

public void doSomething() throws IOException {
InputStream is = myResource.getInputStream();
// 处理 InputStream
...
}
}

// 在配置文件中配置 MyBean
<bean id="myBean" class="com.example.MyBean">
<property name="myResource" value="classpath:/myFile.txt" />
</bean>

// 在代码中手动获取 Spring 容器和 MyBean
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
myBean.doSomething();

但是,如果我们使用 ApplicationContextAware 接口,则可以在 Bean 中直接注入 Spring 容器,如下所示:

public class MyBean implements ApplicationContextAware {
private Resource myResource;
private ApplicationContext applicationContext;

public void setMyResource(Resource myResource) {
this.myResource = myResource;
}

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

public void doSomething() throws IOException {
InputStream is = myResource.getInputStream();
// 处理 InputStream
...
// 从 Spring 容器中获取其他 Bean
OtherBean otherBean = applicationContext.getBean(OtherBean.class);
...
}
}

// 在配置文件中配置 MyBean
<bean id="myBean" class="com.example.MyBean">
<property name="myResource" value="classpath:/myFile.txt" />
</bean>

// 在代码中只需要获取 MyBean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = (MyBean) applicationContext.getBean("myBean");
myBean.doSomething();

由上述两个例子可以看出,使用 ApplicationContextAware 接口可以避免手动创建 BeanFactory 或 ApplicationContext 的繁琐操作,并且可以在 Bean 中直接获取 Spring 容器中的其他资源,从而简化了代码的编写和维护。因此,Aware 接口的存在是必要的。

其实 Spring Aware 是 Spring 设计为框架内部使用的,Aware 标记性注解的出现大大提供了 Spring 的扩展性,不过如果使用了 Bean 就会与 Spring 框架耦合,所以不建议用户单独使用

在本节中,我们通过 ApplicationContextAwareProcessor 这个 Spring 内置 Bean 来实现应用上下文的注入(注意在 Spring IoC 中万物皆是 Bean 对象)

日志

想获得 Spring 框架提供的 BeanFactory、ApplicationContext、BeanClassLoader等这些能力做一些扩展框架的使用时该怎么操作呢。所以我们本章节希望在 Spring 框架中提供一种能感知容器操作的接口,如果谁实现了这样的一个接口,就可以获取接口入参中的各类能力。

image-20230210203928746

设计

手写Spring-Aware

工程

Aware 接口

我们都是从 Spring 容器中获取 Bean,反过来,通过 Bean 获取 Spring 组件(BeanFactory、ClassLoader、BeanName、ApplicationContext)就是 Aware 接口的功能

它是空实现,属于一种感知标记性接口(有了标记的存在更方便类的操作和具体判断实现),具体的子类定义和定义实现能够感知容器中相关对象。通过这个桥梁,向具体实现类中提供容器服务

下面列举一些常见的 Aware 接口及其作用:

  1. ApplicationContextAware:实现该接口的 Bean 可以获取 Spring 容器的实例,从而可以在应用程序中直接使用容器中的 Bean。
  2. BeanNameAware:实现该接口的 Bean 可以获取自身在容器中的 Bean 名称,从而可以在应用程序中获取 Bean 的名称。
  3. BeanClassLoaderAware:实现该接口的 Bean 可以获取加载自身类的类加载器,从而可以在应用程序中获取类加载器的信息。
  4. BeanFactoryAware:实现该接口的 Bean 可以获取 BeanFactory 的实例,从而可以在应用程序中直接使用 BeanFactory 中的 Bean。
  5. EnvironmentAware:实现该接口的 Bean 可以获取应用程序的环境配置,从而可以在应用程序中直接使用环境变量的值。
  6. MessageSourceAware:实现该接口的 Bean 可以获取消息源,从而可以在应用程序中直接使用消息资源。
  7. ResourceLoaderAware:实现该接口的 Bean 可以获取 ResourceLoader 的实例,从而可以在应用程序中直接使用资源加载器。
  8. ServletContextAware:实现该接口的 Bean 可以获取 ServletContext 的实例,从而可以在 Web 应用程序中直接使用 ServletContext。

img

因为应用上下文不能直接获取,因为用户层没法去接触应用上下文。面向于用户的 ClassPathXmlApplicationContext 从功能角度而言不能添加感知接口,所以新建一个后置器去包装感知方法,通过抽象实现 AbstractApplicationContext 中进入 refresh 核心方法中添加后置处理器时AbstractApplicationContext 本身存入后置处理器中

// 3. Spring 添加 ApplicationContextAwareProcessor,让继承了 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

1
2

简单的来说,就是通过添加后置处理器方法,感知的 ApplicationContext 应用上下文存入后置处理器中,其中如何感知呢?就是通过 ApplicationContextAware 接口,将当前应用上下文注入到上下文设置里。

image-20230213191825295

Spring 里的 Aware

image-20230605103632554

想要自定义 Bean 拥有哪个 spring 组件,只要继承对应 XxxAware 接口即可,组件自动装配到自定义 Bean 里。

本质上利用回调机制将 ioc 容器传递给自定义 Bean