likes
comments
collection
share

Spring 注解配置Bean

作者站长头像
站长
· 阅读数 18

无论是注解方式还是配置文件方式,它们的底层工作流程都是将配置解析成BeanDefinition转换为Bean实例对象,并将其注册到Spring容器中。区别在于BeanDefinition的来源不同,注解方式是通过注解信息生成BeanDefinition,配置文件方式是通过配置文件解析器。

样例

@Configuration
public class PersonConfiguration {

    @Bean(name = "person")
    public Person getPerson() {
        return new Person(20, "法外狂徒");
    }
}
record Person(int id, String name) {

}
class Client {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(PersonConfiguration.class);
        applicationContext.refresh();

        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }
}

依旧是曾经的那个工厂(BeanFactory)

AnnotationConfigApplicationContext

Spring 注解配置Bean

  • AnnotationConfigApplicationContext有两个成员变量 Spring 注解配置Bean
    • 注解型就不需要像配置型一样声明Reader了,毕竟配置文件的类型千奇百怪,当然要单独告诉Spring是用的xml,还Groovy了。换句话说,注解就是注解,没有其他内容了,所以该类就直接将Reader封装了
    • AnnotatedBeanDefinitionReader用于解析指定类中的注解,而ClassPathBeanDefinitionScanner用于扫描类路径下的所有类并解析指定的注解

      一个是扫描特定类,一个扫描特定路径,如果有重叠的地方,放心,Spring必然有做过滤,毕竟这reader和scanner共用同一个registry。

    • 这意味着AnnotatedBeanDefinitionReader更适合在单个类上使用,而ClassPathBeanDefinitionScanner更适合在整个项目中使用。
    • 此外,ClassPathBeanDefinitionScanner可以扫描并注册包含在jar文件中的类,而AnnotatedBeanDefinitionReader只能解析已加载到JVM的类。这也是它们之间的区别之一。
    • 需要注意的是,这两个类生成的BeanDefinition本质上是相同的,因为它们都是通过解析注解来创建的。因此,在使用上,它们没有本质上的区别
  • 实例化AnnotationConfigApplicationContext时会导致必要的Spring内置组件被解析成BeanDefinition。简单说就是一些准备工作。

register(Class<?>... componentClasses)

applicationContext.register(PersonConfiguration.class);

注册Bean

Spring 注解配置Bean

  • doRegisterBean,注册BeanDefinition,之所以说是registry是工厂是因为其子类是工厂。 Spring 注解配置Bean
  • BeanDefinitionHolder 是一个封装了 BeanDefinition 和其对应的名称的类。它的作用是在创建 BeanDefinition 的同时,也为其分配一个名称,并在 BeanFactory 中保存。这个名称可以是在配置文件中指定的,也可以是自动生成的。
  • 在 Spring 的 IoC 容器中,每一个 Bean 都有一个唯一的名称,这个名称通常是由该 Bean 在配置文件中的 id 或 name 属性指定的。而一个 BeanDefinition 本身并没有包含这个名称信息,因此需要将 BeanDefinition 和其对应的名称一起封装起来,才能在 IoC 容器中正确地处理该 Bean。因此,为了将 BeanDefinition 与其名称一起保存在 BeanFactory 中,就需要使用 BeanDefinitionHolder 这个封装类。
  • 还是和配置文件型一样,注册Bean不是实例化Bean。

核心方法refresh()

refresh() 方法是 Spring 框架中 ApplicationContext 接口的核心方法,用于刷新 ApplicationContext,也就是将 ApplicationContext 容器初始化和刷新,使其能够响应外部请求。

研究一下Spring是什么时候解析注解@Bean

Spring 注解配置Bean

简述一下refresh()的大致流程

Spring 注解配置Bean

  • BeanFactoryPostProcessor 是一个在容器实例化所有 BeanDefinition 对象之后、在它们进行实例化之前运行的后置处理器。因此,它被称为“后置处理器”。在这个阶段,BeanFactory 对象已经被创建,但是还没有被用来创建单例 Bean 实例。BeanFactoryPostProcessor 提供了一种方式,可以对已经加载到 BeanFactory 中的 BeanDefinition 进行修改或添加属性。这种方式允许应用程序在运行时更改其行为。例如,一个 BeanFactoryPostProcessor 可以动态地向 BeanDefinition 添加属性,也可以用默认值覆盖属性。
  • 要知道,BeanDefinition只是Bean的描述,简单说只是配置信息,并不是class对象,生成BeanDefinition时,某些类可能都还没被加载。

详细讲解@Bean解析时机,以及实例化Bean

  • ConfigurationClassPostProcessor类,会将配置类(@Configuration)解析成Spring中的ConfigurationClass。 Spring 注解配置Bean

Spring 注解配置Bean

Spring 注解配置Bean

  • 解析@Configuration的关键片段 Spring 注解配置Bean

  • 之后的流程就是将@Bean方法,也解析成BeanDefinition Spring 注解配置Bean

  • 最后就是在refresh()方法中的finishBeanFactoryInitialization(beanFactory)方法中调用喜闻乐见的doGetBean()方法,其中@Configuration被Spring视为代理类,@Bean视为工厂方法,最后通过反射调用工厂方法生成Bean实例。如果是单例就会被放入Spring的缓存中。 Spring 注解配置Bean Spring 注解配置Bean

  • @Configuration会被先解析,最后再解析@Bean,Spring会成为每个Bean生成BeanName,按一定顺序放入beanDefinitionNames,然后实例化顺序就确定了。 Spring 注解配置Bean

小结

基于注解与基于XML配置的Spring Bean在创建时机上存在的不同之处

  • 基于XML配置的方式,Bean对象的创建是在程序首次从工厂中获取该对象时才创建的。
  • 基于注解配置的方式,Bean对象的创建是在注解处理器解析相应的@Bean注解时调用了该注解所修饰的方法,当该方法执行后,相应的对象自然就已经被创建出来了,这时,Spring就会将该对象纳入到工厂的管理范围之内,当我们首次尝试从工厂中获取到该Bean对象时,这时,该Bean对象实际上已经完成了创建并已被纳入到了工厂的管理范围之内。
  • 简单说,配置文件型,在需要Bean的时候Spring才会去实例化Bean。(延迟加载)
  • 而注解型,Spring会将Bean先实例化。

注解型为什么不延迟加载

@Component
public class MyService {
    @Autowired
    private MyRepository myRepository;
    // ...
}

@Component
public class MyRepository {
    // ...
}
  • 如果我们采用延迟加载的方式,当容器加载 MyService 类时,由于不知道 MyRepository 的具体信息,Spring 框架也就无法为 MyService 生成对应的 Bean 实例。这就会导致 MyServicemyRepository 属性的自动注入失败,无法使用 MyRepository 对象。
  • Java语言算是动态的,类之间的关系算是强绑定的,在启动时可能没错,但在运行期间可能会出错,为了保证应用程序的正确性,注解配置默认是不延迟加载的。
    • JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)。
  • 而配置文件中的Bean定义是通过XML或者其他格式的文件进行描述的,这些文件是静态的,关系在启动时方便检查,所以延迟加载安全性高一些。