03.创建BeanDefinition


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定义的initMethoddestroyMethod标识的初始化于销毁的方法名,以及其他@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,此时我们需要返回标注了@isPrimaryBeanDifinition,否则抛出异常。

实现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时调用BeanDefinitionRegistryregisterBeanDefinition方法将扫描出的BeanDefinition注册到传入的BeanDefinitionRegistry中。
  • 查找@Conponent时需要递归查找,因为@Controller等具体的组件注解上又有@Conponent注解,若在@Controller中定义了beanvalue值,我们还需要通过反射调用注解的value方法拿到beanname
  • @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实例化等操作完成供用户使用。

image-20231217211913515
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值