Spring bean详细介绍


简而言之,Spring bean是Spring框架在运行时管理对象。Spring bean是任何Spring应用程序的基本构建块。您编写的大多数应用程序逻辑代码都将放在Spring bean中。
Spring bean的管理包括:

  • 创建一个对象
  • 提供依赖项(例如其他bean,配置属性)
  • 拦截对象方法调用以提供额外的框架功能
  • 销毁一个对象

Spring bean是框架的基本概念。作为Spring的用户,您应该对这个核心抽象有深刻的理解。

如何定义Spring bean?
如您所知,Spring负责创建bean对象。但首先,您需要告诉框架它应该创建哪些对象。
你是怎么做到的?
通过提供bean定义
Bean定义告诉Spring框架应该将哪些类用作bean。但那还不是全部。Bean定义就像食谱一样。它们还描述了bean的属性。我们将在本文后面讨论属性。但在我们进入之前,让我们关注bean的定义。
您可以通过三种不同的方式定义Spring bean:

  • 使用构造@Component注释(或其衍生物)注释您的类
  • 编写在自定义Java配置类中使用@Bean注释的bean工厂方法
  • 在XML配置文件中声明bean定义

在现代项目中,您将仅使用组件注释和bean工厂方法。如果涉及到XML配置,那么Spring现在主要允许它向后兼容。
bean定义方法之间选择主要取决于对要用作Spring bean的类的源代码的访问。

Spring bean为@Component
如果您拥有源代码,则通常直接在类上使用@Component注释。

@Component
class MySpringBeanClass {
    //...
}

在运行时,Spring会找到所有使用@Component或其派生类进行注释的,并将它们用作bean定义。查找带注释的类的过程称为组件扫描
您可能想知道@Component的衍生物是什么。
@Conpontent衍生物是Spring构造型注释,它们本身用@Component注释。这个事实允许我们用它们代替@Component
@Component衍生列表包括:
  • @Service
  • @Repository
  • @Controller

注释之间的区别纯粹是信息性的。它们允许您根据通用职责轻松对bean进行分类。您可以使用这些注释将bean类标记为特定应用程序层的成员,Spring框架会将它们全部视为@Components

使用@Bean作为工厂方法
如果某个类属于某个外部库而您无法使用@Component进行注释,您必须在自定义bean的配置类中使用@ Bean注释创建工厂方法。如果您不想使类依赖于Spring,您也可以将此选项用于您拥有的类。
这是一个带有单个bean工厂方法的示例配置类:

@Configuration
class MyConfigurationClass {
   
   @Bean
   public NotMyClass notMyClass() {
       return new NotMyClass();
   }
   
}

Spring使用工厂方法在运行时创建实际对象。如您所见,该方法只返回一个类的新实例。如果某个类属于某个外部库而您无法使用@Component进行注释,则使用@Bean创建工厂方法是唯一的选择。
@Configuration注释也来自Spring。实际上,它是@Component的衍生物,但具有特殊用途。注释将类标记为@Bean定义的容器。您可以在单个配置类中编写多个工厂方法。

Spring bean属性
此时,您已经知道如何将类标记为Spring bean。下一步是学习如何自定义bean的功能。
无论您选择哪种bean定义方法,它们都允许描述同一组bean属性。属性提供有关Spring应如何创建对象的详细信息。
Spring bean属性列表包括:

  • 名称
  • 依赖
  • 范围
  • 初始化模式
  • 初始化回调
  • 破坏回调

为了简化bean定义,Spring为几乎所有属性提供了默认值。但是,了解如何自定义默认值非常重要。让我们逐个研究Spring bean属性。

1.Bean类
创建bean定义时,将其与应用程序中的单个具体类连接。这个类本身是bean的主要属性。
当Spring查找依赖项时,class属性是bean的默认标识符。这是否意味着您不能为单个类提供多个bean定义?不,这是可能的。但在这种情况下,为避免歧义,您应该使用另一个bean标识符:一个name名称。

2. Bean名称name
Spring bean名称是Spring用于标识bean的自定义字符串。与bean类不同,名称在整个应用程序中必须是唯一的。您不能定义两个具有相同名称的bean,即使它们的类型不同。
幸运的是,您不必为您创建的每个bean设置名称。Spring在运行时为其内部使用生成名称。除非您想按名称识别bean,否则可以安全地使用默认设置。
您需要使用bean名称的主要情况是为使用@Bean批注定义的同一个类提供了几个bean。在这种情况下,名称允许您标识要用作另一个bean的依赖项的特定实例

3. 如何命名Spring bean?
使用@Bean批注的name属性。这是两个具有相同类型的bean的示例。

@Configuration
class MyConfigurationClass {
 
   @Bean(name = "myBeanClass")
   MyBeanClass myBeanClass() {
       return new MyBeanClass();
   }
 
   @Bean(name =
"anotherMyBeanClass")
   MyBeanClass anotherMyBeanClass() {
       return new MyBeanClass();
   }
 
}

实际上,您不经常定义bean名称。对于单个类具有多个bean是相当罕见的情况。然而,如果能了解命名bean的各种可能性也是不错的。

4. Bean依赖项
用作bean的对象可以使用其他bean来执行其作业。当Spring创建一个定义某些依赖项的对象时,框架需要首先创建这些依赖项。这些依赖项也可以有自己的依赖项。
在面向对象的应用程序中,我们通常使用相关对象巨大图表。幸运的是,我们不必考虑如何构建此图。我们不必考虑应该创建对象的顺序。Spring为我们做了所有这些。
Spring对您的唯一期望是特定bean的依赖项列表。

5.如何定义bean依赖?
Bean依赖关系定义是一个复杂的主题,值得单独一篇文章。请考虑以下段落作为该主题的介绍。
当你有一个用@Component标记的并且只有一个构造函数时,Spring使用构造函数参数列表作为必需依赖项列表。默认情况下,框架使用构造函数参数类型来提供适当的对象。

@Component
class BeanWithDependency {
 
   private final MyBeanClass beanClass;
 
   BeanWithDependency(MyBeanClass beanClass) {
       this.beanClass = beanClass;
   }
 
}

过去,我们在构造函数上使用@Autowired注释。但是,从Spring 4.3开始,如果只有一个构造函数,则不是强制性的。但是,如果bean类定义了多个构造函数,则应使用@Autowired标记一个。这样Spring知道哪个构造函数包含bean依赖项列表。
出于同样的原因,bean工厂方法可以定义其依赖关系。Spring使用适当的对象调用方法。

@Bean
BeanWithDependency beanWithOptionalDependency(MyBeanClass beanClass) {
   return new BeanWithDependency(beanClass);
}

6. Bean作用域
Spring bean的范围定义了框架在运行时创建的特定类的实例数。作用域还描述创建新对象的条件
Spring为您的bean提供了几个作用域。框架的核心有两个:

  • 单例 - 单个实例
  • 原型 - 多个实例

此外,Spring还附带了专门用于Web应用程序的bean作用域:
  • 请求
  • 会话
  • 全局会话
  • 应用级别Application

所有bean的默认作用域是单例。当bean具有单例作用域时,Spring只创建一个实例并在整个应用程序中共享它。单例是无状态对象的完美选择。如今,我们应用程序中的绝大多数bean都是无状态单例。
另一方面,如果对象包含状态,则应考虑其他作用域。要选择正确的一个,您应该问自己框架应该将该状态保留在内存中多长时间。但这是另一篇文章。

7. 如何设置作用域?
无论是使用@Component直接注释还是使用@Bean创建工厂方法,该方法都是相同的。使用@Scope批注及其字符串属性选择范围。

@Component
@Scope("prototype")
class MyPrototypeClass {
   
//...
}

@Bean
@Scope(
"prototype")
MyPrototypeClass myPrototypeClass() {
   return new MyPrototypeClass();
}

更重要的是,对于Web作用域,Spring附带了额外的别名注释。您可以使用这些注释代替@Scope

  • @RequestScope
  • @SessionScope
  • @ApplicationScope

Bean初始化模式
当您的应用程序启动时,Spring会在启动时会立即创建所有单例bean。此默认行为允许我们快速检测bean定义中的错误。另一方面,立即性eager bean初始化会使应用程序的启动变慢
幸运的是,您可以将bean的创建延迟到实际需要的时刻。您可以使用@Lazy注释执行此操作。

@Component
@Lazy
class MyLazyClass {
    //...
}

Bean初始化回调
一旦Spring根据bean定义创建新实例,您可能希望运行一些对象初始化逻辑。
如果此逻辑不依赖于框架,则可以在对象的构造函数中运行它。但是,为了确保在Spring初始化对象之后运行逻辑(例如,在可选的依赖注入之后),您应该使用初始化回调。

如何设置bean初始化回调?
如果使用@Component定义bean ,则有两个选项:

  • 使bean类实现InitializingBean。接口将强制您实现初始化方法。
  • 编写自定义初始化方法并使用javax @PostContruct注释进行标记。

在这两种情况下,Spring都会为您运行初始化回调。
用工厂方法定义的bean怎么样?
您可以使用@Bean及其名为initMethod的属性设置初始化回调。该属性需要一个具有初始化方法名称的字符串。

@Bean(initMethod = "someInitMethodName")
MySpringBeanClass meBeanClass() {
   return new MySpringBeanClass();
}

有趣的是,用作初始化回调的方法可以具有私有访问控制。Spring使用反射机制来调用该方法。

Bean销毁回调
与初始化回调类似,您可以定义Spring销毁bean时应调用的方法。Predestroy回调的使用要少得多,但要注意它们的存在是很好的。

如何设置bean销毁回调?
同样,如果您可以访问bean类的源代码,则可以使用以下两个选项之一:

  • 实现DisposableBean接口。Spring使用其唯一的方法进行销毁回调。
  • 编写自定义方法并使用Javax API中的@PreDestroy注释它。

对于工厂方法,使用@Bean批注及其destroyMethod属性。

@Bean(name = "myBeanClass", destroyMethod = "cleanUpMethod")
MySpringBeanClass meBeanClass() {
   return new MySpringBeanClass();
}

Spring如何从bean定义创建对象?
当您启动Spring应用程序时,框架首先会创建一个名为ApplicationContext的特殊对象。ApplicationContext,也称为控制反转(IoC)容器,是框架的核心
ApplicationContext是存在bean对象的容器。
上下文负责检测和读取Spring bean定义。一旦ApplicationContext加载了bean定义,它就可以根据提供的bean属性及其依赖关系开始为应用程序创建bean对象。