title: 03.创建BeanDefinition
tag: 笔记 手写SSM IOC
使用反射读取扫描到的Class信息,保存到到Map中。
读取Bean的定义信息
在完成之前两个步骤后,现在我们可以用ResourceResolver
扫描Class,用PropertyResolver
获取配置,下面,我们开始可以实现IoC容器。
在Spring的IOC容器中每个Bean都有一个唯一标识的名字,Spring还允许为一个Bean定义多个别名,我们简化每个Bean都只有一个名字,我们可以使用一个Map<String, Object>
保存所有的Bean:
Map<String, Object> beans;
这样做虽然也可以实现大致的效果,但丢失了大量的Bean定义的信息,不便于我们创建Bean以及解析依赖关系。因此我们应当定义BeanDefinition
保存从注解中获取到的信息,方便后续创建Bean、设置依赖、调用初始化方法等。
public class BeanDefinition {
// 全局唯一的Bean Name:
String name;
// Bean的声明类型:
Class<?> beanClass;
// Bean的实例:
Object instance = null;
// 构造方法/null:
Constructor<?> constructor;
// 工厂方法名称/null:
String factoryName;
// 工厂方法/null:
Method factoryMethod;
// Bean的顺序:
int order;
// 是否标识@Primary:
boolean primary;
// init/destroy方法名称:
String initMethodName;
String destroyMethodName;
// init/destroy方法:
Method initMethod;
Method destroyMethod;
}
需要加入容器的类
有了保存BeanDefinition的类之后,我们需要知道哪些类型的类需要加入容器。
@Component
对于自己定义的带@Component
注解的Bean,我们需要获取Class类型,获取构造方法来创建Bean,然后收集@PostConstruct
和@PreDestroy
标注的初始化与销毁的方法,以及其他信息,如@Order
定义Bean的内部排序顺序,@Primary
定义存在多个相同类型时返回哪个“主要”Bean。一个典型的定义如下:
@Component
public class Hello {
@PostConstruct
void init() {}
@PreDestroy
void destroy() {}
}
@Configuration
对于@Configuration
定义的@Bean
方法,我们把它看作Bean的工厂方法,我们需要获取方法返回值作为Class类型,方法本身作为创建Bean的factoryMethod
,然后收集@Bean
定义的initMethod
和destroyMethod
标识的初始化于销毁的方法名,以及其他@Order
、@Primary
等信息。一个典型的定义如下:
@Configuration
public class AppConfig {
@Bean(initMethod="init", destroyMethod="close")
DataSource createDataSource() {
return new HikariDataSource(...);
}
}
工厂方法:FactoryBean
注册到容器中获取的Bean
不是工厂本身,而是工厂中定义的工厂方法(FactoryBeanMethod
)返回的值。
实现BeanDefinitionRegistry
在知道了哪些类需要加入容器后,我们就需要一个类来保存BeanDefination
。为了避免依赖具体的类,我们首先创建一个接口来定义一个BeanDefinitionRegistry
需要的方法:
/**
* @author 白日
* @create 2023/12/12 16:30
* @description 注册BeanDefinition的接口
*/
public interface BeanDefinitionRegistry {
/**
* 注册BeanDefinition
* @param beanName bean名称
* @param beanDefinition bean定义
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
/**
* 拿到BeanDefinition
* @param beanName bean名称
* @return bean定义
*/
BeanDefinition findBeanDefinition(String beanName);
/**
* 根据类型获取BeanDefinition
* @param type bean类型
* @return bean定义
*/
BeanDefinition findBeanDefinition(Class<?> type);
/**
* 通过bean名称删除BeanDefinition
* @param beanName bean名称
*/
void removeBeanDefinition(String beanName);
/**
* 判断是否包含bean
* @param beanName bean名称
* @return 是否包含
*/
boolean containsBeanDefinition(String beanName);
/**
* 获取所有的bean名称
* @return bean名称集合
*/
Set<String> getBeanDefinitionNames();
}
我们将ApplicationContext
也当作一个BeanDefinitionRegistry
,这样我们就可以在读取BeanDefination
时将容器作为属性传入,因此我们使用ApplicationContext
实现该类:
public class GenericApplicationContext extends ApplicationContextImpl implements BeanDefinitionRegistry{
protected final Logger logger = LoggerFactory.getLogger(getClass());
PropertyResolver propertyResolver = new PropertyResolver();
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
if (beans.put(beanDefinition.getName(), beanDefinition) != null) {
throw new BeanDefinitionException("Duplicate bean name: " + beanDefinition.getName());
}
}
@Override
public BeanDefinition findBeanDefinition(String beanName) {
return beans.get(beanName);
}
@Override
public BeanDefinition findBeanDefinition(Class<?> type) {
List<BeanDefinition> defs = this.beans.values().stream()
// 过滤不在type继承体系中的中BeanDefinition
.filter(def -> type.isAssignableFrom(def.getBeanClass()))
// 排序:
.sorted().toList();
if (defs.isEmpty()) {
return null;
}
if (defs.size() == 1) {
return defs.get(0);
}
// more than 1 beans, require @Primary:
List<BeanDefinition> primaryDefs = defs.stream().filter(BeanDefinition::isPrimary).toList();
if (primaryDefs.size() == 1) {
return primaryDefs.get(0);
}
if (primaryDefs.isEmpty()) {
throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, but no @Primary specified.", type.getName()));
} else {
throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, and multiple @Primary specified.", type.getName()));
}
}
@Nullable
public BeanDefinition findBeanDefinition(String name, Class<?> requiredType) {
BeanDefinition def = findBeanDefinition(name);
if (def == null) {
return null;
}
if (!requiredType.isAssignableFrom(def.getBeanClass())) {
throw new BeanNotOfRequiredTypeException(String.format("Autowire required type '%s' but bean '%s' has actual type '%s'.", requiredType.getName(),
name, def.getBeanClass().getName()));
}
return def;
}
@Override
public void removeBeanDefinition(String beanName) {
this.beans.remove(beanName);
}
@Override
public boolean containsBeanDefinition(String beanName) {
return beans.containsKey(beanName);
}
@Override
public Set<String> getBeanDefinitionNames() {
return beans.keySet();
}
}
注意:
- 我们通过类型找
BeanDifinition
时可能会找到多个满足条件的BeanDifinition
,此时我们需要返回标注了@isPrimary
的BeanDifinition
,否则抛出异常。
实现BeanDefinitionReader
现在有BeanDefinitionRegistryImpl
来管理BeanDefinition
,我们自然需要扫描并读取需要加入容器的信息并注册到BeanDefinitionRegistry
中,因此我们需要实现一个BeanDefinitionReader
来实现这些功能。因为我们是基于注解扫描,因此我们取名AnnotatedBeanDefinitionReader
/**
* @author 白日
* @create 2023/12/12 16:39
* @description 读取扫描需要加入容器的类,将类信息注册到 BeanDefinitionRegistry
*/
public class AnnotatedBeanDefinitionReader {
private final BeanDefinitionRegistry registry;
private final Logger logger = LoggerFactory.getLogger(getClass());
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public void registry(Class<?>... clazz){
for (Class<?> aClass : clazz) {
registryBeanDefinitions(scanForClassNames(aClass));
}
}
/**
* 通过反射将带有@ComponetBean注解等需要加入管理的类创建Definitions记录信息
* @param beanClassNames 扫描的全限名
*/
private void registryBeanDefinitions(Set<String> beanClassNames) {
Map<String, BeanDefinition> defs = new HashMap<>();
beanClassNames.forEach(className -> {
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeansException(e);
}
//是否带有@Component注解
Component component = ClassUtils.findAnnotation(clazz, Component.class);
if(component != null) {
BeanDefinition def = BeanDefinitionFactory.createBeanDefinition(clazz);
this.registry.registerBeanDefinition(def.getName(), def);
}
//是否带有@Configuration注解,将@Bean标注的方法当作工厂方法
Configuration configuration = ClassUtils.findAnnotation(clazz, Configuration.class);
if(configuration != null){
BeanDefinition def = ClassUtils.findFactoryMethods(clazz);
this.registry.registerBeanDefinition(def.getName(), def);
}
});
}
/**
*拿到类上@ComponentScan配置的包名扫描出需要加入容器的全限名
* @param configClass 配置入口类
* @return 扫描到的全限名
*/
private Set<String> scanForClassNames(Class<?> configClass) {
ComponentScan ComponentScanAnnotation = configClass.getAnnotation(ComponentScan.class);
String[] scanPackages;
if(ComponentScanAnnotation == null || ComponentScanAnnotation.value().length == 0){
scanPackages = new String[]{configClass.getPackageName()};
}else{
scanPackages = ComponentScanAnnotation.value();
}
Set<String> beanClassNames = new HashSet<>();
for (String scanPackage : scanPackages) {
List<String> scan = new ResourceResolver(scanPackage).scan(resource -> {
if (resource.name().endsWith(".class")) {
return resource.name().substring(0, resource.name().length() - 6).replace("\\",".");
}
return null;
});
beanClassNames.addAll(scan);
}
Import anImport = configClass.getAnnotation(Import.class);
for (Class<?> aClass : anImport.value()) {
beanClassNames.add(aClass.getName());
}
logger.debug("扫描出的类名:{}", beanClassNames);
return beanClassNames;
}
}
注意:
- 该类需要传入一个
BeanDefinitionRegistry
,在拿到类的BeanDifination
时调用BeanDefinitionRegistry
的registerBeanDefinition
方法将扫描出的BeanDefinition
注册到传入的BeanDefinitionRegistry
中。 - 查找
@Conponent
时需要递归查找,因为@Controller
等具体的组件注解上又有@Conponent
注解,若在@Controller
中定义了bean
的value
值,我们还需要通过反射调用注解的value
方法拿到bean
的name
。 - @Configuration标注的类和其下@Bean标注的工厂方法是都需要加入BeanDifination中的,因为工厂方法是需要获取工厂的实例才能调用的。比如下面的类我们就需要创建3个
BeanDifination
:- DateTimeConfig本身;
- LocalDateTime;
- ZonedDateTime。
@Configuration
public class DateTimeConfig {
@Bean
LocalDateTime local() { return LocalDateTime.now(); }
@Bean
ZonedDateTime zoned() { return ZonedDateTime.now(); }
}
后续展望
原本的Spring框架的ApplicationContext
是在BeanFactory
的基础上实现的,它使用了**适配器模式(Adapter)**使ApplicationContext
持有了在之前实现的BeanFactory
的,将一些接口的实现委托给BeanFactory
并去扩展功能。
为了简便,我们省略了BeanFactory
的实现(大多数情况都不会使用到BeanFactory
),因此我们在ApplicationContext
的接口中就定义了原本BeanFactory
的方法,并在ApplicationContextImpl
中进行了实现。
我们将AnnotationConfigApplicationContext
作为容器的入口类,实现了AnnotationConfigRegistry
,我们的目的是简化容器的使用,在该类我们只需要用户传入使用注解标注了资源文件,包扫描路径等配置信息的类我们即可将BeanDefination
的注册,资源的加载,Bean实例化等操作完成供用户使用。
ory`并去扩展功能。
为了简便,我们省略了BeanFactory
的实现(大多数情况都不会使用到BeanFactory
),因此我们在ApplicationContext
的接口中就定义了原本BeanFactory
的方法,并在ApplicationContextImpl
中进行了实现。
我们将AnnotationConfigApplicationContext
作为容器的入口类,实现了AnnotationConfigRegistry
,我们的目的是简化容器的使用,在该类我们只需要用户传入使用注解标注了资源文件,包扫描路径等配置信息的类我们即可将BeanDefination
的注册,资源的加载,Bean实例化等操作完成供用户使用。