当然这只是一个理论,在实际开发中,我们往往要用到 Spring 容器为我们提供的诸多资源,例如想要获取到容器中的配置、获取到容器中的 Bean 等等。在这种情况下,就需要 Spring 容器中的 Bean 真正的意识到 Spring 容器的存在,才能要到这些东西,那么如何让一个 Bean 意识到 Spring 容器的存在呢?
这就依赖于 Spring 容器给我们提供的各种 Aware 接口了。
public interface Aware {
}
从这个接口的注释中,我们也能大概看出来,这个接口的子类,主要是提供了一些只有一个参数的 set 方法,通过这些方法可以让 Spring 容器感知到某一件事情。
Aware 的实现有很多,大的方向来说主要有如下一些:
每一个 Aware 的作用如下:
- ApplicationEventPublisherAware:实现该接口的对象可以获取事件发布的能力。
- ServletContextAware:实现该接口的对象可以获取到 ServletContext 对象。
- MessageSourceAware:实现该接口的对象可以获取到 MessageSource 对象,MessageSource 支持多消息源,主要用于主要用于国际化。
- ResourceLoaderAware:实现该接口的对象可以获取到一个 ResourceLoader,Spring ResourceLoader 则为我们提供了一个统一的 getResource() 方法来通过资源路径检索外部资源,例如文本文件、XML 文件、属性文件或图像文件等。
- ApplicationStartupAware:实现该接口的对象可以获取到一个 ApplicationStartup 对象,这个比较新,是 Spring 5.3 中新推出的,通过 ApplicationStartup 可以标记应用程序启动期间的步骤,并收集有关执行上下文或其处理时间的数据。
- NotificationPublisherAware:实现该接的对象可以获取到一个 NotificationPublisher 对象,通过该对象可以实现通知的发送。
- EnvironmentAware:实现该接口的对象可以获取到一个 Environment 对象,通过 Environment 可以获取到容器的环境信息。
- BeanFactoryAware:实现该接口的对象可以获取到一个 BeanFactory 对象,通过 BeanFactory 可以完成 Bean 的查询等操作。
- ImportAware:实现该接口的对象可以获取到一个 AnnotationMetadata 对象,ImportAware 接口是需要和 @Import 注解一起使用的。在 @Import 作为元注解使用时,通过 @Import 导入的配置类如果实现了 ImportAware 接口就可以获取到导入该配置类接口的数据配置。
- EmbeddedValueResolverAware:实现该接口的对象可以获取到一个 StringValueResolver 对象,通过 StringValueResolver 对象,可以读取到 Spring 容器中的 properties 配置的值(YAML 配置也可以)。
- ServletConfigAware:实现该接口的对象可以获取到一个 ServletConfig 对象,不过这个似乎没什么用,我们很少自己去配置 ServletConfig。
- LoadTimeWeaverAware:实现该接口的对象可以获取到一个 LoadTimeWeaver 对象,通过该对象可以获取加载 Spring Bean 时织入的第三方模块,如 AspectJ 等。
- BeanClassLoaderAware:实现该接口的对象可以获取到一个 ClassLoader 对象,ClassLoader 能干嘛不需要我多说了吧。
- BeanNameAware:实现该接口的对象可以获取到一个当前 Bean 的名称。
- ApplicationContextAware:实现该接口的对象可以获取到一个 ApplicationContext 对象,通过 ApplicationContext 可以获取容器中的 Bean、环境等信息。
这是 Spring 中提供的一堆 Aware。
接下来松哥随便写个例子大家来看下 Aware 的用法。
2. BeanFactoryAware
实现该接口的对象可以获取到一个 BeanFactory 对象,通过 BeanFactory 可以完成 Bean 的查询等操作。这算是一个比较常见的 Aware 了,我们一起来看下。
这里为了省事,我就在 Spring Boot 中来和大家演示。
首先我们来定义一个简单的 UserService:
@Service
public class UserService {
public void hello() {
System.out.println("hello javaboy!");
}
}
然后提供一个工具类:
@Component
public class BeanUtils implements BeanFactoryAware {
private static BeanFactory beanFactory = null;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
BeanUtils.beanFactory = beanFactory;
}
public static <T> T getBean(String beanName) {
return (T) beanFactory.getBean(beanName);
}
}
有了这个工具类,接下来我们就可以在一个非 Spring 管理的 Bean 中,随时随地的查询 Bean 了,像下面这样:
UserService userService = BeanUtils.getBean("userService");
userService.hello();
3. TienChin 项目实践
为什么会有今天这篇文章呢?主要是在松哥最近做的 TienChin 项目中,有一个地方涉及到这块知识点了,但是有的小伙伴不熟悉,因此就拎出来和大家梳理下。
在 TienChin 项目中,在记录日志的时候,因为日志是一个延迟任务,所以提前准备好了相关的 Bean 已经注册到 Spring 容器中了,像下面这样:
@Configuration
public class ThreadPoolConfig {
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
Threads.printException(r, t);
}
};
}
}
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtils.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.applicationContext = applicationContext;
}
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = (T) beanFactory.getBean(clz);
return result;
}
}
而写日志的异步任务工具类,并非一个容器,所以要通过这个工具类获取相应的 Bean,如下:
public class AsyncManager {
private final int OPERATE_DELAY_TIME = 10;
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
private AsyncManager() {
}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me() {
return me;
}
public void execute(TimerTask task) {
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
}
有了 SpringUtils 我们就可以在一个非 Spring 容器所管理的 Bean 中,获取到 Spring 容器中的 Bean 了。