1.介绍
在 Spring 框架中,BeanFactory
是 Spring IoC 容器的核心接口,负责管理 bean 的创建、配置和装配。它是 Spring IoC 容器的基础。BeanFactory
接口定义了一系列方法,用于管理和访问容器中的 bean 对象。
BeanFactoryAware
用于在 Spring 容器实例化 Bean 的过程中通知 Bean 实例关联的 BeanFactory 实例。实现了 BeanFactoryAware
接口的 Bean 可以获取对应的 BeanFactory 实例,从而可以在需要时与容器进行交互。
BeanFactoryAware
接口是一个标记接口,如果一个 bean 类实现了 BeanFactoryAware
接口,那么在该 bean 被实例化后,Spring IoC 容器会调用该 bean 的 setBeanFactory()
方法,并传递一个 BeanFactory
实例给这个方法。
换句话说,当一个 bean 实现了 BeanFactoryAware
接口时,它表明它想要获取对 Spring IoC 容器的引用,以便在需要时与容器进行交互。这样的交互可能包括:
- 😕 获取其他 bean 实例:通过
BeanFactory
引用,这个 bean 可以动态地获取容器中的其他 bean 实例。 - 😕 访问容器的配置信息:通过
BeanFactory
,这个 bean 可以访问容器的配置信息,例如属性文件、环境变量等。 - 😕 控制 bean 的生命周期:通过
BeanFactory
,这个 bean 可以在需要时自定义自己的初始化逻辑,或者在销毁时执行一些清理操作。
因此,BeanFactoryAware
接口为那些需要与 Spring IoC 容器进行交互的 bean 提供了一种标准化的方式,并允许它们在需要时获取对容器的引用,以实现更灵活和可定制的行为。
2.接口定义
[!NOTE]
setBeanFactory(BeanFactory beanFactory) throws BeansException
:Spring 容器在实例化 Bean 的过程中会调用该方法,将对应的 BeanFactory 实例传递给实现了BeanFactoryAware
接口的 Bean。通过该方法,Bean 可以获取并持有对应的 BeanFactory 实例。
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
public interface BeanFactoryAware extends Aware {
void setBeanFactory(BeanFactory var1) throws BeansException;
}
3.基础使用
在开发中,经常会遇到需要动态加载并管理某些 Bean 的情况,例如根据配置文件、数据库数据或者其他外部条件动态地加载不同的 Bean 实例。这种情况下,可以使用 Spring 的 BeanFactoryAware 接口来实现动态加载 Bean,并将加载的 Bean 实例注册到 Spring 容器中。
3.1 动态加载Bean
通过利用
BeanFactoryAware
接口,使得DynamicBeanLoader
能够获取到BeanFactory
实例。然后,在BusinessService
中,通过DynamicBeanLoader
将动态创建的DynamicComponent
注册为 Spring 容器的 Bean。这样,动态加载的 Bean 就能够被 Spring 容器完全管理,
3.1.1 创建动态加载的Bean类
/**
* 1.创建动态加载的Bean类
* @author 13723
* @version 1.0
* 2024/2/24 0:22
*/
@Component
public class DynamicComponent {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public void getConnection(){
logger.error("*********** PostgresSQL数据库连接成功!***********");
}
}
3.1.2 创建动态加载器类
/**
* 创建动态Bean加载配置器类
* @author 13723
* @version 1.0
* 2024/2/24 0:25
*/
@Component
public class DynamicBeanLoader implements BeanFactoryAware {
/**
* 这里选择 ConfigurableListableBeanFactory 作为BeanFactory的实现类
* 不选择BeanFactory的原因是因为ConfigurableListableBeanFactory是BeanFactory的子接口
* 且ConfigurableListableBeanFactory接口中包含了BeanFactory接口中的所有方法
* 且ConfigurableListableBeanFactory接口中还包含了一些扩展方法
* 例如:registerSingleton()、registerBeanDefinition()等方法
* 通过这些方法可以动态的向Spring容器中注册Bean
*/
@Resource
private ConfigurableListableBeanFactory beanFactory;
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
/**
* 动态加载Bean
* @param beanName Bean的名称
* @param beanClass Bean的类型
*/
public void loadBean(String beanName,Class<?> beanClass){
beanFactory.registerSingleton(beanName,beanClass);
logger.error("*********** 动态加载Bean成功!***********");
}
}
3.1.3 在业务类中使用动态加载器
@Service
public class DynamicBeanLoadService {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Autowired
private DynamicBeanLoader dynamicBeanLoader;
/**
* 通过业务类加载Bean
*/
public void loadDynamicBean(){
logger.error("*********** 通过业务类加载Bean!***********");
dynamicBeanLoader.loadBean("dynamicComponentBean",DynamicComponent.class);
}
}
3.1.4 测试
@SpringBootTest
public class DynamicComponentTest {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Resource
private DynamicBeanLoadService dynamicBeanLoadService;
@Resource
private ApplicationContext context;
@Test
@DisplayName("测试BeanFactoryAware动态加载Bean")
public void getConnection(){
// 动态加载Bean
dynamicBeanLoadService.loadDynamicBean();
// 获取并且使用动态Bean
DynamicComponent dynamicComponent = context.getBean(DynamicComponent.class);
// 调用Bean的连接方法
dynamicComponent.getConnection();
}
}
3.2 加载特定目录下的类
在企业级应用中,存在一种需求:需要在系统启动时
自动扫描特定目录下的类文件
,并将这些类文件加载为Spring Bean
进行管理。这种情况下,可以使用BeanFactoryAware
接口来实现自定义的 Bean 加载器,以便动态加载并注册这些类文件对应的 Bean 到 Spring 容器中。
3.2.1 创建自定义Bean加载器类
@Component
public class DynamicLoadClass implements BeanFactoryAware {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
// 自定义方法,用于加载指定目录下的类文件并注册为 Spring Bean
public void loadBeansFromDirectory(String directoryPath) {
File directory = new File(directoryPath);
if (!directory.exists() || !directory.isDirectory()) {
throw new IllegalArgumentException("Directory path is invalid: " + directoryPath);
}
File[] files = directory.listFiles();
if (files != null) {
Arrays.stream(files)
.filter(file -> file.isFile() && file.getName().endsWith(".java"))
.forEach(file -> {
try {
//将路径中的 "\\" 替换为包名中的 "."
String packageName = file.getPath().substring(file.getPath().indexOf("com"), file.getPath().lastIndexOf("\\"))
.replace("\\", ".");
// 替换掉 xxx.java 中的java 作为类型 用于反射 例如 TUser1.java -> TUser1
String className = file.getName().replace(".java", "");
className = packageName + "." + className;
// 因为Bean是需要遵循驼峰命名的 所以首字母应该小写 第一个字符小写 后面的进行截断
className = Character.toLowerCase(className.charAt(0)) + className.substring(1);
Class<?> beanClass = Class.forName(className);
Object beanInstance = beanClass.getDeclaredConstructor().newInstance();
beanFactory.registerSingleton(beanClass.getSimpleName(), beanInstance);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
3.2.2 测试类
@Test
@DisplayName("测试扫描指定包下的文件加载到Bean")
public void loadPage(){
String path = "D:\\dcjet\\java_base_study\\src\\main\\java\\com\\hrfan\\java_se_base\\spring_boot\\ss\\bean_factory_avare\\load\\test_bean";
// 动态加载Bean
dynamicLoadClass.loadBeansFromDirectory(path);
// 测试指定包的Bean是否加载成功
TUser1 bean1 = context.getBean(TUser1.class);
TUser2 bean2 = context.getBean(TUser2.class);
TUser3 bean3 = context.getBean(TUser3.class);
logger.error("bean1:{}",bean1.getClass().getName());
logger.error("bean2:{}",bean2.getClass().getName());
logger.error("bean3:{}",bean3.getClass().getName());
}
我们利用了 BeanFactoryAware
接口实现了自定义的 Bean 加载器 CustomBeanLoader
,它能够从指定目录加载类文件并注册为 Spring Bean。在 Spring Boot 的启动类中,我们通过实现 CommandLineRunner
接口,在系统启动时调用 CustomBeanLoader
加载指定目录下的类文件,并将其注册为 Spring Bean。
4.使用场景
- 动态配置管理: 当需要根据外部条件或者运行时状态动态加载和管理一些 Bean 实例时,可以使用
BeanFactoryAware
接口结合动态加载器的方式。 - 灵活性要求高: 如果系统要求灵活性高,需要根据用户配置或者运行时条件来动态加载和管理 Bean,这种情况下也适合使用
BeanFactoryAware
接口。 - 定制化需求: 在某些特定的场景下,需要定制化的 Bean 加载和管理方式,
BeanFactoryAware
接口可以提供更多的灵活性和定制化。
5.优点缺点
5.1 优点:
- 灵活性高: 可以根据实际需求动态加载和管理 Bean,提高系统的灵活性和可配置性。
- 容器完全管理: 结合 Spring 容器,使用
BeanFactoryAware
接口可以确保动态加载的 Bean 实例被完全纳入 Spring 容器的管理范围。 - 解耦合: 使用
BeanFactoryAware
接口可以解耦动态加载逻辑和应用程序的其他部分,提高系统的可维护性和可扩展性。
5.2 缺点:
- 复杂性增加: 动态加载 Bean 的逻辑可能会增加系统的复杂性,特别是需要处理动态加载和卸载 Bean 的逻辑时。
- 容错性差: 动态加载的 Bean 实例可能会带来一些潜在的安全风险和容错性问题,需要仔细考虑和处理。
- 性能影响: 动态加载和管理大量的 Bean 实例可能会对系统的性能产生一定的影响,需要合理评估和处理。
总的来说,使用 BeanFactoryAware
接口实现动态加载 Bean 可以在一定程度上提高系统的灵活性和可配置性,但是也需要谨慎考虑和评估其带来的复杂性和性能影响。适合在对灵活性要求较高、定制化程度较高的场景下使用。