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注释。
在运行时,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对象。