最新最全面的Spring详解(一)——Spring概述与IOC容器

前言

本文为 【Spring】Spring概述与IOC容器 相关知识,下边将对Spring概述,IOC容器(包括:IOC概述、配置元数据、容器实例化与使用、Bean的概述、依赖注入 Dependency Injection、Bean 作用范围(作用域)、更多Bean的特性、基于注解的容器配置、容器的启动过程、classpath扫描和组件管理)等进行详尽介绍~

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)

目录
【Spring】Spring概述与IOC容器
前言
目录
一、Spring概述
1️⃣为什么使用Spring
2️⃣Spring 的特性
3️⃣Spring的组成
二、IOC 容器
1️⃣IOC概述
2️⃣配置元数据
3️⃣容器实例化与使用
4️⃣Bean的概述
5️⃣依赖注入 Dependency Injection
6️⃣Bean 作用范围(作用域)
7️⃣更多Bean的特性
8️⃣基于注解的容器配置
9️⃣容器的启动过程
🔟classpath扫描和组件管理
后记

一、Spring概述

Spring创始人: Rod Johnson,Java和J2EE开发领域的专家,Spring框架的创始人,同时也是SpringSource的联合创始人。

Spring官网地址: https://spring.io/projects/spring-framework#overview
Spring下载地址: https://repo.spring.io/ui/native/release/org/springframework/spring
Spring官方文档: https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/index.html
Spring中文文档: https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/

1️⃣为什么使用Spring
Spring使Java编程对每个人来说更快、更容易、更安全。 Spring对速度、简单性和生产率的关注使它成为世界上最流行的Java框架。 Spring给整个行业带来等了春天,为我们软件的开发带来了极大的便利。

🍀(1)Spring is everywhere
​Spring框架的足够灵活受到世界各地开发人员的信任。 无论是流媒体电视、在线购物、还是无数其他创新的解决方案,Spring每天都为数百万终端用户提供愉快的体验。 Spring也有来自所有科技巨头的贡献,包括阿里巴巴、亚马逊、谷歌、微软等。

🍀(2)Spring is flexible
​Spring灵活而全面的扩展能力和第三方库让开发人员可以构建几乎任何可以想象到的应用程序。 Spring框架的【控制反转(IoC)】和【依赖注入(DI)】特性为一系列广泛的特性和功能提供了基础。 无论您是在为web构建安全的、响应式的、基于云的微服务,还是为企业构建复杂的流数据流,Spring都有工具可以提供帮助。

🍀(3)Spring is productive
​Spring Boot(这是我们以后要学习的框架)改变了您处理Java编程任务的方式,从根本上简化了您的体验。 Spring Boot结合了应用程序上下文和自动配置的嵌入式web服务器等必要条件,使microservice开发变得轻而易举。 为了更快,您可以将Spring Boot与Spring Cloud丰富的支持库、服务器、模式和模板组合在一起,以创纪录的时间将整个基于微服务的架构安全地部署到云中。

🍀(4)Spring is fast
​我们的工程师非常关心性能。 在Spring中,默认情况下,您会注意到快速启动、快速关闭和优化执行。 Spring项目也越来越多地支持reactive(nonblocking)编程模型,以获得更高的效率。 开发人员的生产力是Spring的超级力量。 Spring Boot帮助开发人员轻松地构建应用程序,而且比其他竞争范式要轻松得多。

🍀(5)Spring is secure
​Spring在处理安全问题方面十分可靠。 Spring代码的贡献者与安全专业人员一起修补和测试任何报告的漏洞。 第三方依赖关系也被密切监控,并定期发布更新,以帮助您的数据和应用程序尽可能安全。 此外,Spring Security使您更容易集成行业标准的安全方案,并交付可靠的默认安全解决方案。

🍀(6)Spring is supportive
​Spring社区是一个庞大的、全球性的、多样化的社区,涵盖了所有年龄和能力的人,从完全的初学者到经验丰富的专业人士。 无论你处在人生的哪个阶段,你都能找到帮助你进入下一个阶段的支持和资源。

2️⃣Spring 的特性
Core technologies: dependency injection, events, resources, i18n, validation, data binding, type conversion, SpEL,AOP.(核心技术:包括依赖注入、事件模型、资源处理、国际化、数据绑定和验证、类型转化、spring表达式、面向切面编程。核心技术是一切的关键,后边衍生的多个特性都是依托于核心技术。)
Testing: mock objects, TestContext framework, Spring MVC Test, WebTestClient.
Data Access: transactions, DAO support, JDBC, ORM, Marshalling XML.
Spring MVC and Spring WebFlux web frameworks.
Integration: remoting, JMS, JCA, JMX, email, tasks, scheduling, cache.
Languages: Kotlin, Groovy, dynamic languages.
3️⃣Spring的组成
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

Spring Core: 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring Context: Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP: 通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
Spring DAO: JDBC DAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。SpringDAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM: Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web: Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring Web MVC: MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
二、IOC 容器
在学习IOC 容器之前首先需要了解一些常见的名词:

容器: 可以管理对象的生命周期、对象与对象之间的依赖关系。
POJO: ​ POJO(Plain Old Java Object)这种叫法是Martin Fowler、Rebecca Parsons和Josh MacKenzie在2000年的一次演讲的时候提出来的。按照Martin Fowler的解释是“Plain
Old Java
Object”,从字面上翻译为“纯洁老式的Java对象”,但大家都使用“简单java对象”来称呼它。POJO的内在含义是指:那些没有继承任何类、也没有实现任何接口,更没有被其它框架侵入的java对象。不允许有业务方法,也不能携带connection之类的方法,实际就是普通JavaBeans。
JavaBean: ​ JavaBean是一种JAVA语言写成的可重用组件。JavaBean符合一定规范编写的Java类,不是一种技术,而是一种规范。大家针对这种规范,总结了很多开发技巧、工具函数。符合这种规范的类,可以被其它的程序员或者框架使用。它的方法命名,构造及行为必须符合特定的约定:(1)所有属性为private;(2)这个类必须有一个公共的缺省构造函数。即是提供无参数的构造器;(3)这个类的属性使用getter和setter来访问,其他方法遵从标准命名规范;(4)这个类应是可序列化的。实现serializable接口;(5)因为这些要求主要是靠约定而不是靠实现接口,所以许多开发者把JavaBean看作遵从特定命名约定的POJO。
POJO与Java Bean的区别:
POJO JAVABean
除了Java语言强加的限制外,它没有其他特殊限制。 这是一个特殊的POJO,它有一些限制。
它没有对成员提供太多控制。 它提供对成员的完全控制。
它可以实现Serializable接口。 它应该实现可序列化的接口。
可以通过字段名称访问字段。 字段只能由getter和setter访问。
字段可以具有任何可见性。 字段只有私人可见性。
可能/可能没有no-arg构造函数。 它必须具有无参数构造函数。
当您不想限制成员并让用户完全访问您的实体时使用它。 当您要向用户提供您的实体,但仅向实体的一部分提供服务时,将使用它。
POJO类和Bean均用于定义Java对象,以提高其可读性和可重用性。POJO没有其他限制,而bean是具有某些限制的特殊POJO。

SpringBean: ​ SpringBean是受Spring管理的对象,所有能受Spring容器管理的对象都可以成为SpringBean。Spring中的bean,是通过配置文件、javaconfig等的设置,由Spring自动实例化,用完后自动销毁的对象。
SpringBean和JavaBean的区别: (1)用处不同:传统javabean更多地作为值传递参数,而spring中的bean用处几乎无处不在,任何组件都可以被称为bean;(2)写法不同:传统javabean作为值对象,要求每个属性都提供getter和setter方法;但spring中的bean只需为接受设值注入的属性提供setter方法;(3)生命周期不同:传统javabean作为值对象传递,不接受任何容器管理其生命周期;spring中的bean有spring管理其生命周期行为。
Entity Bean: Entity Bean是域模型对象,用于实现O/R映射,负责将数据库中的表记录映射为内存中的Entity对象,事实上,创建一个Entity
Bean对象相当于新建一条记录,删除一个 Entity Bean会同时从数据库中删除对应记录,修改一个Entity
Bean时,容器会自动将Entity Bean的状态和数据库同步。
1️⃣IOC概述
🍀编写spring代码,我们需要创建一个maven工程,并加入以下依赖:

org.springframework spring-core 5.2.18.RELEASE org.springframework spring-beans 5.2.18.RELEASE org.springframework spring-context 5.2.18.RELEASE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🍀本章将介绍Spring框架实现控制反转(IoC)的原理, IoC也称为依赖注入(DI)

org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。
BeanFactory接口提供了一种高级的配置机制,能够管理任何类型的对象。
ApplicationContext是BeanFactory的子接口。 它对BeanFactory进行了补充:

(1)更容易与Spring的AOP特性集成 。
(2)消息资源处理(用于国际化) ,解析消息的能力,支持国际化。继承自MessageSource接口。
(3)事件发布,向注册侦听器发布事件的能力。继承自ApplicationEventPublisher接口。
(4)应用程序层特定的上下文,如WebApplicationContext用于web应用程序。
(5)以通用方式加载文件资源的能力,继承自org.springframe .core.io.ResourceLoader接口。
🍀beanFactory和ApplicationContext接口展示如下:

public interface BeanFactory {}
1
public interface ApplicationContext
extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {}
1
2
3
​简而言之,BeanFactory提供了容器的基本功能,而ApplicationContext添加了更多特定于企业的功能。 ApplicationContext是BeanFactory的一个完整超集,仅在本章描述Spring的IoC容器时使用。

​在Spring中,由Spring IoC容器【管理】的构成【应用程序主干的对象】称为【bean】。 bean是由Spring IoC容器实例化、组装和管理的对象。 否则,bean只是应用程序中的众多对象之一。 bean及其之间的依赖关系反映在容器使用的【配置元数据】中。

​【applicationcontext】接口表示Spring IoC容器,并负责实例化、配置和组装bean。 容器通过读取配置元数据获得关于要实例化、配置和组装哪些对象的指令。 配置元数据以XML、Java注解或Java代码表示。 它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

​ Spring提供了ApplicationContext接口的几个实现。

​在独立应用程序中,创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例是很常见的。 虽然XML一直是定义配置元数据的传统格式,但您可以通过提供少量的XML配置以声明方式支持这些额外的元数据格式,指示容器使用Java注解或代码作为元数据格式。

2️⃣配置元数据
构建【Spring IoC容器】可以通过构建配置元数据的方式。 这个【配置元数据】说的是:作为应用程序开发人员,您要告诉Spring容器如何去【实例化、配置和组装】应用程序中的对象。 【元数据】传统上以简单而直观的XML格式提供,本章的大部分内容都使用这种格式来传达Spring IoC容器的关键概念和特性。

下面的示例展示了基于xml的配置元数据的基本结构:

<?xml version="1.0" encoding="UTF-8"?>

<bean id="..." class="...">  
    <!-- collaborators and configuration for this bean go here -->
</bean>

<bean id="..." class="...">
    <!-- collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions go here -->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
’ id ’ 属性是标识单个beanDifination的字符串。
’ class ’ 属性定义bean的类型,并使用完全限定的类名。
3️⃣容器实例化与使用
🍀实例化一个容器

ApplicationContext 的构造函数可以是【xml文件的位置路径】的字符串,他允许容器从各种外部资源(如本地文件系统、Java的 'CLASSPATH ’ 等)加载配置元数据。

ApplicationContext context = new ClassPathXmlApplicationContext(“services.xml”, “daos.xml”);
1
下面的示例显示了服务层对象(services.xml)的配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<!-- services -->

<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
    <property name="accountDao" ref="accountDao"/>
    <property name="itemDao" ref="itemDao"/>
    <!-- additional collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions for services go here -->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
下面的例子展示了数据访问对象(dao.xml )配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<bean id="accountDao"
    class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
    <!-- additional collaborators and configuration for this bean go here -->
</bean>

<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
    <!-- additional collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions for data access objects go here -->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🍀容器的使用

​ 【ApplicationContext】是一个高级工厂的接口,它维护了一个bean的注册列表,保存了容器产生的所有bean对象。 通过使用方法T getBean(String name, Class requiredType) ,您可以检索bean的实例。

​ 【ApplicationContext】允许你读取和访问bean,如下面的示例所示:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext(“services.xml”, “daos.xml”);

// retrieve configured instance,这里使用bean的标识符活class对象检索bean的事例。
PetStoreService service = context.getBean(“petStore”, PetStoreService.class);

// use configured instance
List userList = service.getUsernameList();
1
2
3
4
5
6
7
8
4️⃣Bean的概述
Spring IoC容器管理一个或多个bean。 这些bean是使用您提供给容器的配置元数据创建的(例如,以XML< bean/>定义的形式)。

在容器本身中,这些定义好的【bean的元数据(描述bean的数据)】被表示【BeanDefinition】对象,其中包含但不限于以下元数据:

全限定类名:通常是被定义的bean的实际【实现类】。
Bean的行为配置元素:它声明Bean在容器中应该存在哪些行为(作用范围、生命周期回调等等)。
bean所需的其他bean的引用(成员变量):这些引用也称为【协作者】或【依赖项】。
接下来我们对其一一进行讲解:

🍀bean的命名

​每个bean都有【一个或多个】标识符。 这些标识符在承载bean的容器(ioc容器)中必须是唯一的。 bean通常只有一个标识符。 但是,如果需要多个,则可以考虑使用别名。

​在【基于xml】的配置元数据中,可以使用’ id ‘属性、’ name ‘属性或两者同时使用,来指定bean的标识符。 ’ id ‘属性允许您指定一个id,通常,这些名称是字母数字(‘myBean’, ‘someService’等),但它们也可以包含特殊字符。 如果想为bean引入其他别名(一个或者多个都可以),还可以在’ name ‘属性中指定它们,由逗号(’,’)、分号(’;')或空格分隔。

​您甚至不需要为bean提供’ name ‘或’ id ‘。 如果您没有显式地提供’ name ‘或’ id ‘,容器将为该bean生成唯一的名称。 但是,如果您想通过名称引用该bean,则必须通过使用’ ref '元素来提供名称。 xml中默认的名字是【权限定名称#数字】。

【bean命名约定 】:​ 在命名bean时,bean名称以小写字母开头,并从那里开始采用驼峰式大小写。 这类名称的例子包括’ accountManager ‘、’ accountService ‘、’ userDao ‘、’ loginController '等等;一致地命名bean可以使您的配置更容易阅读和理解。

🍀bean的别名

​在bean的定义中,您可以为bean提供多个名称,方法是使用’ id ‘属性指定的最多一个名称和’ name '属性中任意数量的其他名称的组合。 这些名称可以是相同bean的等效别名,在某些情况下很有用,例如允许应用程序中的每个组件使用特定于该组件本身的bean名称来引用公共依赖项。 举一个简单的例子,一个人在家叫【狗蛋】,在公司叫【小刘】。

​然而,在实际定义bean的地方指定所有别名并不一定能满足所有需求,有时需要为别处定义的bean(比如引入的jar包)引入别名。 这种情况在大型系统中很常见,其中配置在每个子系统之间被分割,每个子系统都有自己的一组对象定义。 在基于xml的配置元数据中,可以使用< alias/>元素来实现这一点。 下面的例子展示了如何做到这一点:

1 在这种情况下,一个名为【fromName】的bean被定义了一个新的别名【toName】。

例如,子系统A的配置元数据可以以【subsystemA-dataSource】的名称引用数据源。 子系统B的配置元数据可以以【subsystemB-dataSource 】的名称引用数据源。 当编写使用这两个子系统的主应用程序时,主应用程序以【myApp-dataSource】的名称引用数据源。 要使这三个名称都指向同一个对象,您可以向配置元数据添加以下别名定义:

1 2 现在,每个组件和主应用程序都可以通过唯一的名称来引用dataSource,并且保证不会与任何其他定义(有效地创建了一个名称空间)发生冲突,但它们引用的是相同的bean。

🍀实例化bean

beanDifination本质上是描述了一个bean是如何被创建的。 当被请求时,容器会查看指定bean的定义,并使用由该beanDifination封装的配置元数据来创建(或获取)实际对象。

​如果使用基于xml配置的元数据,则要在< bean/>元素的【class】属性中指定实例化的对象的类型。 这个’ class ‘属性(在内部是’ BeanDefinition ‘实例上的’ class '属性,一个bean的配置加载到内存会形成一个BeanDefinition事例)通常是强制性的。 你可以通过以下两种方式使用Class属性:

(1)在容器中,如果是通过【反射调用其构造函数】直接创建bean,则要指定bean的类型,这有点类似于使用“new”操作符的Java代码。
(2)这个类同样可以是用于创建对象的“静态”工厂方法的实际类,在这种情况下,容器调用该类上的【静态工厂方法来创建bean】。调用静态工厂方法返回的对象类型可能是同一个类,也可能完全是另一个类,这要看你的工厂方法的具体实现。
(1)使用构造函数实例化

当您通过构造函数方法创建bean时,所有普通类都可以被Spring使用并与Spring兼容。 也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式编码。 只需指定bean类就足够了。 但是,这种情况下您可能需要一个默认(无参)构造函数。

使用基于xml的配置元数据,您可以使用如下方法,指定您的bean类:

1 2 (2)使用静态工厂方法实例化

下面的beanDifination指定通过调用工厂方法创建bean:

在这个例子中,createInstance()方法必须是一个静态方法,下面的示例演示如何指定工厂方法:

1 下面的示例显示了一个具有静态工厂方法的类:

public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}

public static ClientService createInstance() {
    return clientService;
}

}
1
2
3
4
5
6
7
8
(3)使用实例工厂方法实例化

该方法类似于通过(静态工厂方法)实例化所需的bean,容器同样可以使用【实例工厂方法】调用【非静态方法】创建一个新的bean。 要使用这种机制,请将【class】属性保留为空,并在【factory-bean】属性中指定当前容器中包含要调用的实例方法的bean的名称。 使用“factory-method”属性设置工厂方法本身的名称。

下面的示例演示如何配置这样的bean:

1 2 3 4 5 6 7 下面的例子显示了相应的类:

public class DefaultServiceLocator {

private static ClientService clientService = new ClientServiceImpl();

public ClientService createClientServiceInstance() {
    return clientService;
}

}
1
2
3
4
5
6
7
8
一个工厂类也可以包含多个工厂方法,如下例所示:

1 2 3 4 5 6 7 下面的例子显示了相应的类:

public class DefaultServiceLocator {

private static ClientService clientService = new ClientServiceImpl();

private static AccountService accountService = new AccountServiceImpl();

public ClientService createClientServiceInstance() {
    return clientService;
}

public AccountService createAccountServiceInstance() {
    return accountService;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
注意: 其实我们这样明白一点,静态工厂方法可以直接调用,事例工厂方法需要容器先构建好事例再进行调用。

5️⃣依赖注入 Dependency Injection
​依赖注入(DI)是一个【过程】(目前可以理解为给成员变量赋值的过程),在此过程中,对象仅通过【构造函数参数】、【工厂方法参数】等来确定它们的依赖项。 然后容器在创建bean时注入这些依赖项。 从根本上说,这个过程与bean本身相反(因此得名“控制反转”)。

​使用依赖注入的代码更清晰,并且在向对象提供依赖时【解耦更有效】。

DI主要有以下两种方式:

Constructor-based依赖注入,基于构造器的依赖注入,本质上是使用构造器给成员变量赋值。
Setter-based依赖注入,基于setter方法的依赖注入,本质上是使用set方法给成员变量赋值。
🍀(1)基于构造函数的依赖注入

基于构造器的依赖注入是通过容器调用带有许多参数的构造器来实现的,每个参数表示一个依赖项:

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;

// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// business logic that actually uses the injected MovieFinder is omitted...

}
1
2
3
4
5
6
7
8
9
10
11
12
**注意:**这个类没有什么特别之处。 它是一个POJO,不依赖于容器特定的接口、基类或注解。

1、使用参数的顺序实现

如果beanDifination的构造函数参数中不存在【潜在的歧义】,那么在beanDifination中定义【构造函数参数的顺序】就是在实例化bean时将这些参数提供给适当构造函数的顺序,我们可以看一下下边这个类:

package x.y;

public class ThingOne {

public ThingOne(ThingTwo thingTwo,ThingThree thingThree) {
    // ...
}

}
1
2
3
4
5
6
7
8
假设【ThingTwo】和【ThingThree】类没有继承关系,就不存在潜在的歧义。 因此,下面的配置工作正常,并且您不需要在 元素中显式指定【构造函数参数索引或类型】。

<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
1 2 3 4 5 6 7 8 9 10 2、构造函数参数类型匹配

当引用另一个bean时,类型是已知的,可以进行匹配(如上例所示)。 当使用简单类型时,例如true , Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。 考虑以下官网提供的类:

package examples;

public class ExampleBean {

// Number of years to calculate the Ultimate Answer
private final int years;

// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;

public ExampleBean(int years,String ultimateAnswer) {
    this.years = years;
    this.ultimateAnswer = ultimateAnswer;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在前面的场景中,如果你通过使用【type】属性显式指定构造函数参数的类型,容器可以使用与简单类型匹配的类型,如下面的示例所示:

1 2 3 4 3、按照构造函数参数的下标匹配

你可以使用【index】属性显式指定构造函数参数的索引,如下例所示:

1 2 3 4 除了解决多个简单值的歧义之外,指定索引还解决构造函数具有相同类型的两个参数的歧义。

4、按照构造函数参数的名字匹配

还可以使用构造函数参数名来消除值的歧义,如下面的示例所示:

1 2 3 4 🍀(2)基于setter的注入

基于setter的DI是通过容器在【调用无参数构造函数】或【无参数“静态”工厂方法】实例化bean后调用bean上的setter方法来实现的。

​下面的示例显示了一个只能通过使用纯setter注入进行依赖注入的类。 这个类是传统的Java。 它是一个POJO,不依赖于容器特定的接口、基类或注解。

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;

// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// business logic that actually uses the injected MovieFinder is omitted...

}
1
2
3
4
5
6
7
8
9
10
11
12
【ApplicationContext】支持它管理的bean的【基于构造函数】和【基于setter】的依赖注入。 在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。也就意味着先通过有参构造构建对象,再通过setter方法进行特殊值的赋值。

下面的元数据配置示例为【基于setter】的DI方式:

<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
1 2 3 4 5 6 7 8 9 10 11 12 13 下面的示例显示了相应的【ExampleBean】类:

public class ExampleBean {

private AnotherBean beanOne;

private YetAnotherBean beanTwo;

private int i;

public void setBeanOne(AnotherBean beanOne) {
    this.beanOne = beanOne;
}

public void setBeanTwo(YetAnotherBean beanTwo) {
    this.beanTwo = beanTwo;
}

public void setIntegerProperty(int i) {
    this.i = i;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
其他情况:

现在考虑这个例子的一个变体,在这里,Spring不是使用构造函数,而是被告知调用一个【static】工厂方法来返回对象的一个实例:

1 2 3 4 5 6 7 8 下面的示例显示了相应的’ ExampleBean '类:

public class ExampleBean {

// a private constructor
private ExampleBean(...) {
    ...
}

// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
    AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

    ExampleBean eb = new ExampleBean (...);
    // some other operations...
    return eb;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
【static】工厂方法的参数是由 元素提供的,就像实际使用了构造函数一样。spring会根据元数据构造工厂对象,再由工厂对象创建实例,创建的实例交由spring容器管理。

🍀(3)基于构造函数还是基于setter的依赖注入?

由于您可以混合使用基于构造函数和基于setter的DI,一般情况下,我们对于【强制性依赖项】使用构造函数,对于【可选依赖项】使用setter方法注入,这是一个很好的经验法则。 注意,在setter方法上使用【@Required】注解可以使属性成为必需依赖项。

Spring团队通常提倡构造函数注入,因为它允许你将应用程序组件实现为不可变的对象,并确保所需的依赖项不是”空“的。 而且,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。

Setter注入主要应该只用于可选依赖项,这些依赖项可以在类中分配合理的默认值。 setter注入的一个好处是,setter方法使该类的对象能够在稍后进行重新配置或重新注入。

有时,在处理您没有源代码的第三方类时,您可以自行选择。 例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

🍀(4)依赖关系和配置细节

从上边的课程我们知道,可以将【bean属性】和【构造函数参数】定义为对【其他合作者bean(合作者)的引用】。 Spring基于xml配置的元数据应该为其 和元素中支持多样的元素类型。

(1)直接值(原语、字符串等)

元素的【value】属性将【属性或构造函数参数】指定为人类可读的字符串表示形式。 Spring的【类型转化器】用于将这些值从’ String '转换为属性或参数的实际类型(比如数字类型,甚至是对象)。

下面的示例显示了正在设置的各种值:

1 2 3 4 5 6 7 (2)idref元素

【idref 】元素只是将容器中另一个bean的【id 字符串值-不是引用】传递给· 或 元素的一种防错误方法。 下面的例子展示了如何使用它:

1 2 3 4 5 6 7 前面的beanDifination代码段(在运行时)与下面的代码段完全相同: 1 2 3 4 5 ​ 第一种形式比第二种形式更可取,因为使用’ idref '标记可以让容器在部署时【验证所引用的已命名bean是否实际存在】。 在第二个变体中,没有对传递给"theClientBean"的【targetName】属性的值执行验证。 只有在实际实例化【theClientBean】时才会发现拼写错误(很可能导致致命的结果)。 如果“客户端”bean是一【prototype bean马上要讲到】,那么这个错误和由此产生的异常可能只有在容器部署很久之后才会被发现。

(3)对其他bean的引用(Collaborators合作者)

【ref】元素是 或 定义元素中的最后一个元素。 在这里,您将bean的指定属性的值设置为容器管理的另一个bean(合作者bean)的引用。 被引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要初始化它。

通过 标记的【bean属性】指定目标bean是最常用的一种形式,它允许创建同一容器中的任何bean的引用,而不管它是否在同一XML文件中。 【bean属性】的值可以与目标bean的【id】属性相同,也可以与目标bean的【name】属性中的一个值相同。 下面的例子展示了如何使用【ref】元素:

<bean id=“accountService”
class=“org.springframework.aop.framework.ProxyFactoryBean”>





1
2
3
4
5
6
7
8
9
10
11
(4)内部bean

在 或 元素内部的 元素定义了一个内部bean,如下面的例子所示:

1 2 3 4 5 6 7 8 9 内部bean总是匿名的,并且总是与外部bean一起创建的。 不可能独立地访问内部bean,也不可能将它们注入到外围bean之外的协作bean中。

(5)集合

​ , , , 和 元素分别设置 Java Collection 类型 List, Set, Map,和 Properties的属性和参数。 下面的例子展示了如何使用它们:

administrator@example.org support@example.org development@example.org a list element followed by a reference just some string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
映射键或值或集合值的值也可以是以下元素中的任何一个:

bean | ref | idref | list | set | map | props | value | null
1
(6)null值和空字符串

Spring将属性的【空参数】等作为空字符串处理,以下基于xml的配置元数据片段将’ email '属性设置为空字符(“”)。

1 2 3 上面的例子等价于下面的Java代码:

exampleBean.setEmail(“”);
1
元素处理 null值。 下面的例子显示了一个示例:

1 2 3 4 5 上述配置相当于以下Java代码:

exampleBean.setEmail(null);
1
(7)带有【p命名空间】的XML配置方式

【p-名称空间】允许您使用【bean元素的属性】(而不是嵌套的元素)来描述协作bean的属性值,或者两者都使用。说的简单一点就是另外一种写法。

下面的示例显示了两个XML片段(第一个使用标准XML格式,第二个使用p-名称空间),它们解析相同的结果:

<bean name="classic" class="com.example.ExampleBean">
    <property name="email" value="someone@somewhere.com"/>
</bean>

<bean name="p-namespace" class="com.example.ExampleBean"
    p:email="someone@somewhere.com"/>
1 2 3 4 5 6 7 8 9 10 11 12 13 下一个例子包括另外两个beanDifination,它们都引用了另一个bean:

<bean name="john-classic" class="com.example.Person">
    <property name="name" value="John Doe"/>
    <property name="spouse" ref="jane"/>
</bean>

<bean name="john-modern"
    class="com.example.Person"
    p:name="John Doe"
    <!--p命名空间支持这样定义的bean的引用-->
    p:spouse-ref="jane"/>

<bean name="jane" class="com.example.Person">
    <property name="name" value="Jane Doe"/>
</bean>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
建议您仔细选择方法,并将其告知您的团队成员,用以形成规范的统一的XML文档。

(8)带有c命名空间的XML快捷方式

与带有p-名称空间的XML配置方式类似,在Spring 3.1中引入的【c-名称空间】允许内联属性来配置构造函数参数,而不是嵌套的【constructor-arg】元素。

下面的例子使用了【c: 命名空间】来完成与【基于构造器的依赖注入】:

<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>

<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
    <constructor-arg name="thingTwo" ref="beanTwo"/>
    <constructor-arg name="thingThree" ref="beanThree"/>
    <constructor-arg name="email" value="something@somewhere.com"/>
</bean>

<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
    c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
【c: 命名空间】通过名称设置构造函数参数。 类似地,它需要在XML文件中声明对应的命名空间。
对于【构造函数参数名不可用的罕见情况】(通常是在没有调试信息的情况下编译字节码),可以使用回退参数索引,如下所示:


1
2
3
由于XML语法的原因,索引表示法要求出现前导’ _ ',因为XML属性名不能以数字开头(尽管一些ide允许它)。 对于元素也有相应的索引表示法,但不常用,因为一般的声明顺序就足够了。

实际上,【构造函数解析机制】在匹配参数方面非常有效,所以除非真的需要,否则我们建议在整个配置中使用名称表示法。

(9)复合属性名

当您设置bean属性时,您可以使用复合或嵌套属性名,只要路径的所有组件(除了最终属性名)不为’ null '。 考虑以下beanDifination:

1 2 3 【 something】bean有一个【 fred 】属性,fred 属性有一个【bob】属性,bob 属性有一个【sammy】‘属性,最后的【sammy】属性的值被设置为’123’。 为了使其工作,在构造bean之后,’ something ‘的’ fred ‘属性和’ fred ‘的’ bob ‘属性不能为’ null '。 否则,抛出一个NullPointerException。

(10)延迟初始化的 Bean

默认情况下,【ApplicationContext】实现会作为初始化过程的一部分,会在容器初始化的时候急切地创建和配置所有【singleton bean】。 通常,这种预实例化是可取的,因为配置或周围环境中的错误可以被立马发现,而不是几个小时甚至几天之后(调用一个方法,创建一个实例的时候等)。 当这种行为不可取时,您可以通过将beanDifination标记为【惰性初始化】来防止【单例bean的预实例化】。 延迟初始化的bean告诉IoC容器在【第一次请求】时创建bean实例,而不是在启动时。

在XML中,这种行为是由 元素上的【lazy-init】属性控制的,如下面的示例所示:

1 2 然而,当一个【延迟初始化的bean】是一个没有延迟初始化的单例bean的依赖时,ApplicationContext会在启动时创建这个延迟初始化的bean,因为它必须满足单例bean的依赖, 延迟初始化的bean会被注入到没有延迟初始化的其他单例bean中。

你也可以在容器级通过在元素上使用“default-lazy-init”属性来控制延迟初始化,如下面的例子所示:

1 2 3 🍀(5)自动装配

Spring容器可以自动装配【协作bean之间的关系】。 自动装配具有以下优点:

自动装配可以显著减少指定属性或构造函数参数的需要。
自动装配可以随着对象的发展更新配置。 例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。
当使用基于xml的配置元数据时,您可以使用元素的【autowire】属性为beanDifination指定自动装配模式。 自动装配功能有四种模式。 您可以指定每个bean的自动装配,从而可以选择要自动装配哪些bean,自动装配的四种模式如下表所示:

运行方式 解释
no (默认)没有自动装配。 Bean引用必须由【ref】元素定义。 对于较大的部署,不建议更改默认设置,因为【明确指定协作者】可以提供更大的控制和清晰度。 在某种程度上,它记录了系统的结构。
byName 通过属性名自动装配。 Spring寻找与需要自动连接的属性同名的bean。 例如,如果一个beanDifination被设置为按名称自动装配,并且它包含一个“master”属性(也就是说,它有一个“setMaster(…)”方法),Spring会寻找一个名为“master”的beanDifination并使用它来设置属性。
byType 如果容器中恰好有一个属性类型的bean,则允许自动连接属性。 如果存在多个,则抛出异常,这表明您不能对该bean使用’ byType '自动装配。 如果没有匹配的bean,则不会发生任何事情(没有设置属性)。
constructor 类似于’ byType ',但适用于构造函数参数。 如果容器中没有一个构造函数参数类型的bean,则会引发致命错误。
通过’ byType ‘或’ constructor '自动装配模式,您可以连接【数组和类型化集合】。 在这种情况下,容器中所有【匹配预期类型的自动装配候选对象】都将被提供以满足依赖关系。其中,自动连接的“Map”实例的值包含所有与期望类型匹配的bean实例,而“Map”实例的键包含相应的bean名称。

从自动装配中排除Bean

在每个bean的基础上,您可以将一个bean排除在自动装配之外。 在Spring的XML格式中,将元素的【autowire-candidate】属性设置为’ false '。

“autowire-candidate”属性被设计成只影响【基于类型】的自动装配。 它不会影响【按名称的显式引用】,即使指定的bean没有被标记为自动连接候选对象,也会解析该引用。 因此,如果名称匹配,按名称自动装配仍然会注入一个bean。

​您还可以根据bean名称的模式匹配来限制自动装配候选对象。 顶级元素 在其【default-autowire-candidates】属性中接受一个或多个匹配规则。 例如,要将自动装配候选状态限制为名称以’ Repository ‘结尾的任何bean,可以提供’ *Repository '值。 要提供多个规则,请在逗号分隔的列表中定义它们。 beanDifination的【autowire-candidate】属性的值“true”或“false”总是优先。 对于这样的bean,模式匹配规则不适用。

​这些技术对于那些【永远不想通过自动装配将其注入到其他bean中的bean】非常有用。 但这并不意味着被排除的bean本身不能通过使用自动装配来配置。

🍀(6)循环依赖

容器会按照如下方式执行bean依赖关系解析:

使用描述所有bean的配置元数据创建和初始化【ApplicationContext】。 配置元数据可以由XML、Java代码或注解指定。
对于每个bean,其依赖关系都以属性、构造函数参数或静态工厂方法参数的形式表示。 这些依赖项是在实际创建bean时提供给bean的。
每个属性或构造函数参数的值将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,比如’ int ‘、’ long ‘、’ string '、'boolean '等等。
spring会在需要的时候实例化一个bean,我们说的简单一点,spring创建A对象,创建后会注入一个依赖项B,注入时发现依赖的bean不存在,于是就开始创建依赖的B对象,这是一个典型的控制翻转,循环依赖的问题就是实例化B时发现,B竟然依赖A,这是两个对象的互相依赖,组成了一个圆环,循环依赖可能是三个或是更多对象组成。

使用setter注入的循环依赖是可以解决的,通常是采用三级缓存的方式。

但如果主要使用构造函数注入,可能会创建不可解析的循环依赖场景。

6️⃣Bean 作用范围(作用域)
当您创建一个beanDifination时,其实是在为这个bean的定义创建描述他的元数据。 beanDifination是元数据的想法很重要,因为这意味着,与类一样,您可以从一份元数据中创建许多对象实例。

​您不仅可以控制beanDifination的对象中的各种依赖项和配置值,还可以控制从特定的bean的定义中创建的对象的作用范围。 这种方法功能强大且灵活,因为您可以通过配置,选择创建的对象的作用范围,而不必在Java类级别上确定对象的作用范围。 Spring框架支持六个作用域,其中四个只有在你使用web感知的ApplicationContext时才可用:

下表描述了支持的范围:

scope 描述
singleto 每个bean在ioc容器中都是独一无二的单例形式。
prototype 将单个beanDifination定义为,spring容器可以【实例化任意数量】的对象实例。
request 将单个beanDifination限定为单个HTTP请求的生命周期。 也就是说,每个HTTP请求都有自己的bean实例,它是在单个beanDifination的后面创建的。 仅在web环境中的Spring【ApplicationContext】的上下文中有效。
session 将单个beanDifination定义为HTTP 【Session】的生命周期。 仅在web环境中的Spring 【ApplicationContext】的上下文中有效。
application 将单个beanDifination定义为【ServletContext】的生命周期。 仅在web环境中的Spring 【ApplicationContext】的上下文中有效。
websocket 将单个beanDifination作用域定义为【WebSocket】的生命周期。 仅在web环境中的Spring【ApplicationContext】的上下文中有效。
🍀(1)单例的作用域

容器只管理【一个bean的共享实例】,所有对具有一个或多个标识符的bean的请求都将导致Spring容器返回一个特定唯一的bean实例。

​换句话说,当您定义一个beanDifination并且它的作用域为单例时,Spring IoC容器会创建由该beanDifination定义的对象的一个实例。 这个实例对象会存储单例bean的缓存中,对该命名bean的所有后续请求和引用都会返回缓存的对象。 下面的图片展示了单例作用域是如何工作的:

【Spring的单例bean概念不同于设计模式书中定义的单例模式】。 单例设计模式对对象的作用域进行硬编码,使得每个ClassLoader只创建一个特定类的实例。 Spring单例的作用域最好描述为每个容器和每个bean,这并不影响我们手动创建更多个实例。 单例作用域是Spring中的默认作用域。 要在XML中将beanDifination为单例,可以定义如下示例所示的bean:

1 2 3 4 🍀(2)原型作用域

非单例原型作用域导致【每次对特定bean发出请求时都要创建一个新的bean实例】。 也就是说,该bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用请求它,都会创建一个新的bean。 作为一条规则,您应该对所有有状态bean使用原型作用域,对无状态bean使用单例作用域。

下图说明了Spring原型的作用域:

下面的示例用XML将beanDifination为原型:

1 与其他作用域相比,Spring并【不管理原型bean的完整生命周期】。 容器实例化、配置和组装一个原型对象,并将其传递给客户端,而不需要进一步记录该原型实例,不会缓存,不会管理他的后续生命周期。 因此,尽管初始化生命周期回调方法在所有对象上都被调用但在原型的情况下,配置的销毁生命周期回调不会被调用(这个小知识下个小节讲)。

​在某些方面,Spring容器在原型作用域bean中的角色是Java【new】操作符的替代。 超过这一点的所有生命周期管理都必须由客户端处理。

🍀(3)会话、应用和WebSocket作用域

【request 】,【session 】, 【application】和【websocket 】作用域只有在你使用web项目中的Spring【ApplicationContext】实现(如XmlWebApplicationContext)时才可用。 如果您将这些作用域与常规Spring IoC容器一起使用,例如“ClassPathXmlApplicationContext”,则会抛出一个“IllegalStateException”,该异常会告知一个未知的bean作用域。

🍀(4)自定义范围

bean作用域机制是可扩展的,您可以定义自己的作用域,甚至可以重新定义现有的作用域,尽管后者被认为是不好的做法,而且您不能覆盖内置的’ singleton ‘和’ prototype '作用域。

7️⃣更多Bean的特性
Spring框架提供了许多接口,您可以使用这些接口自定义bean的性质。 本节将它们归类如下:

生命周期回调
ApplicationContextAware和BeanNameAware
其他rAware 接口
🍀(1)生命周期回调

初始化回调

org.springframework.beans.factory.InitializingBean.InitializingBean的接口允许bean在容器设置了bean上的所有必要属性之后执行【初始化工作】。【InitializingBean】接口指定了一个方法:

void afterPropertiesSet() throws Exception;
1
建议您不要使用【InitializingBean】接口,因为这将你的代码与Spring的代码耦合在一起。 我们更推荐使用【@PostConstruct】注解或指定POJO初始化方法。

​在基于xml的配置元数据的情况下,您可以使用【init-method】属性指定具有void无参数签名的方法的名称。 在Java配置中,您可以使用【@Bean】的【initMethod】属性。可以看看下面的例子:

1 public class ExampleBean {
public void init() {
    // do some initialization work
}

}
1
2
3
4
5
6
前面的示例几乎与下面的示例(包含两个例子)具有完全相同的效果:

1 public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
    // do some initialization work
}

}
1
2
3
4
5
6
7
然而,前面两个示例中的第一个并没有将代码与Spring耦合起来。

🍀(2)销毁回调

实现org.springframework.beans.factory.DisposableBean接口可以让bean在管理它的容器被销毁时获得回调。 ’ DisposableBean '接口指定了一个方法:

void destroy() throws Exception;
1
同样,我们并不建议您使用【DisposableBean】回调接口,因为我们没有必要将自己的代码与Spring耦合在一起。 另外,我们建议使用【@PreDestroy】注解或指定beanDifination支持的销毁方法。 对于基于xml的配置元数据,您可以在上使用’ destroy-method '属性。 在Java配置中,您可以使用“@Bean”的【destroyMethod】属性。如下所示:

1 public class ExampleBean {
public void cleanup() {
    // do some destruction work (like releasing pooled connections)
}

}
1
2
3
4
5
6
前面的定义与下面的定义几乎完全相同:

1 public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
    // do some destruction work (like releasing pooled connections)
}

}
1
2
3
4
5
6
7
🍀(3)默认初始化和销毁方法

当我们不使用spring特有的InitializingBean和disapablebean回调接口进行初始化和销毁时,我们通常会编写名为【init()】、 【initialize()】、 【dispose()】等的方法。 理想情况下,这种生命周期回调方法的名称在项目中应该是标准化的(项目经理规定了都必须这么写),以便所有开发人员使用相同的方法名称,并确保一致性。

​您可以配置统一的bean的初始化和销毁方法。 这意味着,作为应用程序开发人员,您可以仅仅声明一个名为【init()】的初始化方法即可,而不必为每个beanDifination配置一个【init method = “init"】属性。

​假设你的初始化回调方法名为“init()”,你的destroy回调方法名为“destroy()”。 你的类就像下面这个例子中的类:

public class DefaultBlogService implements BlogService {

private BlogDao blogDao;

public void setBlogDao(BlogDao blogDao) {
    this.blogDao = blogDao;
}

// this is (unsurprisingly) the initialization callback method
public void init() {
    if (this.blogDao == null) {
        throw new IllegalStateException("The [blogDao] property must be set.");
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后,您可以在bean中使用该类,类似如下:

<bean id="blogService" class="com.something.DefaultBlogService">
    <property name="blogDao" ref="blogDao" />
</bean>
1 2 3 4 5 6 7 顶层元素属性上的【default-init-method】属性导致Spring IoC容器将bean类上的一个名为【init】的方法识别为初始化方法回调。 在创建和组装bean时,如果bean类有这样的方法,就会在适当的时候调用它。

​如果现有的bean类已经有按约定命名的回调方法,那么您可以通过使用本身的【init-method】和【destroy-method】属性指定对应方法来覆盖默认值。

🍀(4)总结

从Spring 2.5开始,你有三个选项来控制bean的生命周期行为:

InitializingBean和 DisposableBean 和 DisposableBean回调接口
自定义 init()和 destroy() 方法
@PostConstruct 和 @PreDestroy 您可以组合这些机制来控制给定的bean
为同一个bean配置的多个生命周期机制(具有不同的初始化方法),调用顺序如下:

(1)用“@PostConstruct”注解的方法
(2)afterPropertiesSet() 由 InitializingBean 回调接口
(3)自定义配置的init()方法
Destroy方法的调用顺序相同:

(1)用@PreDestroy注解的方法
(2)destroy()由 DisposableBean 回调接口定义
(3)自定义配置的 destroy() 方法
🍀(5)ApplicationContextAware 和 BeanNameAware

下面显示了“ApplicationContextAware”接口的定义:

public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
1
2
3
因此,bean可以通过【ApplicationContextAware】接口,以编程方式【操作】创建它们的【ApplicationContext】。 其中一个用途是对其他bean进行编程检索, 有时这种能力是有用的。 但是,一般来说,您应该【避免使用它】,因为它将代码与Spring耦合在一起,而不遵循控制反转(Inversion of Control)风格,在这种风格中,协作者作为属性提供给bean。 ApplicationContext的其他方法提供了对文件资源的访问、发布应用程序事件和访问MessageSource。

​当ApplicationContext创建一个实现了BeanNameAware接口的类时。 他提供了对其关联对象定义中定义的名称的引用。 下面的例子显示了BeanNameAware接口的定义:

public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
1
2
3
回调在填充普通bean属性之后,但在初始化回调(如“InitializingBean.afterPropertiesSet()”或自定义初始化方法之前调用。

​总结: 实现了aware相关的接口,ioc容器不在遵循ioc风格,意思就是不在遵循按需初始化并注入依赖,而是在统一的地方统一注入,这个在源码中有所体现,后边的内容会涉及。

🍀(6)Other Aware Interfaces

除了“ApplicationContextAware”和“BeanNameAware”,spring提供了一个广泛的“aware”回调接口,让bean指示容器,他们需要一定【基础设施】的依赖。 作为一般规则,名称指示了所需依赖项的类型。 下表总结了一些最重要的“Aware”接口:

命名 依赖注入
ApplicationContextAware 将ApplicationContext注入bean当中
ApplicationEventPublisherAware 将ApplicationEventPublisherAware注入bean当中
BeanClassLoaderAware 将类加载器用于装入bean类
BeanFactoryAware 将BeanFactory注入bean当中
BeanNameAware 将bean的名称注入bean中
ResourceLoaderAware 配置了用于访问资源的加载器
ServletConfigAware 当前的’ ServletConfig '容器运行。 仅在web感知的Spring ’ ApplicationContext '中有效。
ServletContextAware 当前运行容器的“ServletContext”。 仅在web感知的Spring ’ ApplicationContext '中有效。
再次注意,使用这些接口将您的代码与Spring API绑定在一起,而不是遵循控制反转风格。 因此,我们将它们推荐给需要对容器进行编程访问的基础架构bean。

🍀(7)Bean的继承

bean的定义可以包含大量配置信息,包括构造函数参数、属性值和特定于容器的信息,比如初始化方法、静态工厂方法名,等等。 子beanDifination可以从父beanDifination继承配置数据。 子beanDifination可以根据需要覆盖一些值或添加其他值。 使用父beanDifination和子beanDifination可以节省大量输入。 实际上,这是模板的一种形式。

​当您使用基于xml的配置元数据时,您可以通过使用“parent”属性来指示子beanDifination,下面的例子展示了如何做到这一点:








1
2
3
4
5
6
7
8
9
10
11
12
如果没有指定,子beanDifination将使用来自父beanDifination的bean类,但也可以覆盖它。 在后一种情况下,子bean类必须与父bean兼容(也就是说,它必须接受父bean的属性值)。

​子beanDifination从父bean继承范围、构造函数参数值、属性值和方法覆盖,并可选择添加新值。 您指定的任何scope、初始化方法、销毁方法或“静态”工厂方法设置都会覆盖相应的父方法设置。

​其余的设置总是取自子定义:依赖、自动装配模式、依赖项检查、单例和延迟初始化。

​前面的示例通过使用【 abstract 】属性显式地将父beanDifination标记为抽象。 如果父beanDifination没有指定类,则需要显式地将父beanDifination标记为【抽象】,如下例所示:





1
2
3
4
5
6
7
8
9
10
父bean不能单独实例化,因为它是不完整的,而且它也显式地被标记为“抽象”。 当定义是【抽象的】时,它只能作为作为一个父beanDifination的【纯模板beanDifination使用】。 试图单独使用这样一个【抽象】的父bean,通过将其作为另一个bean的ref属性引用,或使用父bean ID执行显式的“getBean()”调用,将返回错误。 类似地,容器内部的’ preinstantiatesingleton() '方法会忽略定义为抽象的beanDifination。

8️⃣基于注解的容器配置
在配置Spring时,注解比XML更好吗?

引入基于注解的配置提出了这样一个问题:这种方法是否比XML“更好”, 简短的回答是“视情况而定”。 长期的答案是,每种方法都有其优点和缺点。通常,由开发人员决定哪种策略更适合他们。 由于注解在其声明中提供了【大量上下文】,从而导致配置更简短、更简洁。 然而,XML擅长【连接组件】,而无需修改它们的源代码或重新编译它们。 一些开发人员更喜欢接近源代码进行连接,而另一些开发人员则认为带注解的类不再是pojo,而且配置变得分散且更难控制。

使用注解配置,我们需要开启以下的配置:

<?xml version="1.0" encoding="UTF-8"?>

<context:annotation-config/>
1 2 3 4 5 6 7 8 9 10 11 12 🍀(1)使用 @Autowired

作用就是自动装配,有byType的语义。你可以将@Autowired注解应用到构造函数中,如下面的例子所示:

public class MovieRecommender {

private final CustomerPreferenceDao customerPreferenceDao;

@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
    this.customerPreferenceDao = customerPreferenceDao;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
注意: 从Spring Framework 4.3开始,如果目标bean只定义了一个构造函数,就不再需要在这样的构造函数上添加【 @Autowired 】注解。 然而,如果有几个构造函数可用,并且没有主/默认构造函数,那么至少其中一个构造函数必须用【@Autowired 】注解,以便告诉容器使用哪个构造函数。

你也可以将@Autowired注解应用到传统的 setter方法,如下面的例子所示:

public class SimpleMovieLister {

private MovieFinder movieFinder;

@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
你还可以将注解应用到具有任意名称和多个参数的方法,如下面的示例所示:

public class MovieRecommender {

private MovieCatalog movieCatalog;

private CustomerPreferenceDao customerPreferenceDao;

@Autowired
public void prepare(MovieCatalog movieCatalog,
        CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用的最多的但spring官方并不推荐的方法是,你也可以将 @Autowired 应用到字段上,甚至可以将它与构造函数混合使用,如下面的示例所示:

public class MovieRecommender {

private final CustomerPreferenceDao customerPreferenceDao;

@Autowired
private MovieCatalog movieCatalog;

@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
    this.customerPreferenceDao = customerPreferenceDao;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
你也可以通过在一个字段或方法中添加【@Autowired】注解来指示Spring从【ApplicationContext】中提供所有特定类型的bean,该字段或方法需要该类型的数组,如下面的例子所示:

public class MovieRecommender {

@Autowired
private MovieCatalog[] movieCatalogs;

// ...

}
1
2
3
4
5
6
7
这同样适用于类型化的集合,如下例所示:

public class MovieRecommender {

private Set<MovieCatalog> movieCatalogs;

@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
    this.movieCatalogs = movieCatalogs;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
即使是类型化的“Map”实例,只要期望的键类型是“String”,也可以自动连接。 映射值包含预期类型的所有bean,键包含相应的bean名,如下例所示:

public class MovieRecommender {

private Map<String, MovieCatalog> movieCatalogs;

@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
    this.movieCatalogs = movieCatalogs;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
注意: 默认情况下,当给定注入点没有可用的匹配候选bean时,自动装配将失败。 对于声明的数组、集合或映射,至少需要一个匹配元素。

默认行为是将带注解的方法和字段视为指示所需的依赖关系。 你可以像下面的例子一样改变这种行为,通过将一个不满足的注入点标记为非必需的(例如,通过将【 @Autowired】中的’ required ‘属性设置为’ false ')来让框架跳过它:

public class SimpleMovieLister {

private MovieFinder movieFinder;

@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
🍀(2)使用 @Primary微调基于注解的自动装配

由于按类型自动装配可能会导致多个【候选者】,因此通常需要对选择过程进行更多的控制。 实现这一点的一种方法是使用Spring的【@Primary】注解。 【@Primary】表示当多个bean可以作为一个依赖项的候选bean时,应该优先考虑某个特定bean。 如果在候选bean中恰好存在一个主要的bean,那么它将成为自动连接的值。

考虑以下配置,将’ firstMovieCatalog ‘定义为主要的’ MovieCatalog ':

以下内容【@Bean】是下个章节的:

@Configuration
public class MovieConfiguration {

@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }

@Bean
public MovieCatalog secondMovieCatalog() { ... }

// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
通过上述配置,下面的“MovieRecommender”将自动与“firstMovieCatalog”连接:

public class MovieRecommender {

@Autowired
private MovieCatalog movieCatalog;

// ...

}
1
2
3
4
5
6
7
当然在xml中我们可以如下配置、相应的beanDifination如下,效果是等价的:

<?xml version="1.0" encoding="UTF-8"?>

<context:annotation-config/>

<bean class="example.SimpleMovieCatalog" primary="true">
    <!-- inject any dependencies required by this bean -->
</bean>

<bean class="example.SimpleMovieCatalog">
    <!-- inject any dependencies required by this bean -->
</bean>

<bean id="movieRecommender" class="example.MovieRecommender"/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
🍀(3)使用@Qualifier微调基于注解的自动装配

当可以确定一个主要候选时,【@Primary】注解可以轻松完成这个工作。 当您需要对选择过程进行更多控制时,可以使用Spring的【@Qualifier】注解。 您可以将【限定符值】与特定的参数关联起来,从而缩小类型匹配的集合,以便为每个参数选择特定的bean。 在最简单的情况下,这可以是一个简单的描述性值,如下例所示:

public class MovieRecommender {

@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;

// ...

}
1
2
3
4
5
6
7
8
您还可以在单个构造函数参数或方法参数上指定’ @Qualifier '注解,如下面的示例所示:

public class MovieRecommender {

private MovieCatalog movieCatalog;

private CustomerPreferenceDao customerPreferenceDao;

@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
        CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
下面的示例显示了相应的beanDifination:

<?xml version="1.0" encoding="UTF-8"?>

<context:annotation-config/>

<bean class="example.SimpleMovieCatalog">
    <qualifier value="main"/> 

    <!-- inject any dependencies required by this bean -->
</bean>

<bean class="example.SimpleMovieCatalog">
    <qualifier value="action"/> 

    <!-- inject any dependencies required by this bean -->
</bean>

<bean id="movieRecommender" class="example.MovieRecommender"/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
注意: 除了使用qualifier标签决定,其实 @Qualifier可以使用id,name等属性定义的任何标识符。

​其实,如果您打算按【名称标识符】完成的注入,那么就可以不使用【@Autowired 】,即使它能够在类型匹配的候选对象中按bean名称进行选择(需要配合@Qualifier同时使用)。 有一个更好的选择是使用JSR-250的 【@Resource】注解,该注解在语义上定义为通过惟一的【名称标识】选择特定的目标组件,声明的类型与匹配过程无关。

🍀(4)使用 @Resource

Spring还通过在字段或bean属性设置方法上使用JSR-250的 【@Resource】注解(’ javax.annotation.Resource ')来支持注入。 这是Java EE中的常见模式, Spring也支持这种模式用于Spring管理的对象。

@Resource 带有一个name属性。 默认情况下,Spring将该值解释为要注入的bean名。 换句话说,它遵循by-name语义,如下面的示例所示:

public class SimpleMovieLister {

private MovieFinder movieFinder;

@Resource(name="myMovieFinder") 
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

}
1
2
3
4
5
6
7
8
9
如果没有显式指定名称,则默认名称为【字段名或setter方法的参数名】。 对于字段,它接受字段名。 对于setter方法,它采用bean属性名。 下面的例子将把名为【movieFinder】的bean注入到它的setter方法中:

public class SimpleMovieLister {

private MovieFinder movieFinder;

@Resource
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

}
1
2
3
4
5
6
7
8
9
因此,在下面的示例中,’ customerPreferenceDao ‘字段首先查找名为"customerPreferenceDao"的bean,然后按照类型’ customerPreferenceDao '的主类型匹配:

public class MovieRecommender {

@Resource
private CustomerPreferenceDao customerPreferenceDao;

@Resource
private ApplicationContext context; 

public MovieRecommender() {
}

// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
9️⃣容器的启动过程
核心方法: refresh()

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新,准备开店,检查环境,是不是适合开店,比如我选用哪个日志
prepareRefresh();

    // 把门面租下来,获得一个bean工厂,loadBeanDefinitions(beanFactory)获取蛋糕的制作流程
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

    // Prepare the bean factory for use in this context.
    prepareBeanFactory(beanFactory);
    // 忽略对应的自动装配
        //beanFactory.ignoreDependencyInterface(EnvironmentAware.class);

    try {
        // Allows post-processing of the bean factory in context subclasses.
        postProcessBeanFactory(beanFactory);

        // bean工厂已经基本好了,后置处理器
        invokeBeanFactoryPostProcessors(beanFactory);

        // Register bean processors that intercept bean creation.
        registerBeanPostProcessors(beanFactory);

        // Initialize message source for this context.
        initMessageSource();

        // Initialize event multicaster for this context.
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses.
        onRefresh();

        // Check for listener beans and register them.
        registerListeners();

        // 初始化bean
        finishBeanFactoryInitialization(beanFactory);

        // Last step: publish corresponding event.
        finishRefresh();
    }

    catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
        }

        // Destroy already created singletons to avoid dangling resources.
        destroyBeans();

        // Reset 'active' flag.
        cancelRefresh(ex);

        // Propagate exception to caller.
        throw ex;
    }

    finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        resetCommonCaches();
    }
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 已经完成了创建和属性填充给你的工作
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
// 1、调用实现的aware接口
invokeAwareMethods(beanName, bean);
}

	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
        // 调用beanpostproccessor的BeforeInitialization方法
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}

	try {
        // 调用初始化方法在这里
		invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				(mbd != null ? mbd.getResourceDescription() : null),
				beanName, "Invocation of init method failed", ex);
	}
	if (mbd == null || !mbd.isSynthetic()) {
        // 调用beanpostproccessor的AfterInitialization
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}

	return wrappedBean;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
🍀(1)初始化Spring容器

这个阶段相当于考察一下地理环境怎么样。

prepareRefresh(): 做一些准备阶段做的是:标记容器为active状态,以及检查当前的运行环境,比如使用log4j,还是jdklog等。

🍀(2)获得一个新的容器

这个阶段相当于租一个门面,同时准备好产品的制作流程。

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

如果有旧的容器,那么清空容器和容器中注册了的bean,创建新的容器DefaultListableBeanFactory。

protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
🍀(3)bean工厂的准备阶段

相当于做一些基础装修,比如设备的采购。

prepareBeanFactory(beanFactory);

设置一些处理器:

tandardBeanExpressionResolver
ResourceEditorRegistrar
1
2
🍀(4)调用所有的BeanFactory后置处理器

这是留给我们进行扩展的,同事spring在也有很多的扩展实现。

执行:

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
1
2
🍀(5)注册BeanPostProcessors

🍀(6)完成bean的创建

beanFactory.preInstantiateSingletons();
1
在创建bean的过程中,会执行如下流程:

(1)创建bean
(2)执行BeanPostProcessors
postProcessBeforeInitialization
1
(3)执行配置的初始化方法
(4)执行BeanPostProcessors
postProcessAfterInitialization
1
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}

try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, “Invocation of init method failed”, ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
一些重要的BeanFactory后置处理器:

BeanFactoryPostProcessor:BeanFactory后置处理器
ConfigurationClassPostProcessor:解析配置类的BeanFactory后置处理器
一些重要的BeanFactory:

InstantiationAwareBeanPostProcessor:Bean实例化前后运行的后置处理器,还负责设置属性值populateBean()
AutowiredAnnotationBeanPostProcessor:对注解@Autowired的实现
CommonAnnotationBeanPostProcessor:对注解 @Resource的实现
InitDestroyAnnotationBeanPostProcessor:主要是实现了Bean的@PostConstruct和@PreDestroy方法。
AnnotationAwareAspectJAutoProxyCreator:AOP代理的后置处理器,AOP生成代理的地方就是在后置处理器postProcessAfterInitialization方法中实现的。
InfrastructureAdvisorAutoProxyCreator:自动代理创建器,仅考虑基础结构Advisor
Bean,而忽略任何应用程序定义的Advisor。Spring 的事务使用的是这个后置处理器。
🔟classpath扫描和组件管理
见:最新最全面的Spring详解(二)——classpath扫描和组件管理

后记

↪️本文下接:最新最全面的Spring详解(一)——Spring概述与IOC容器
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
————————————————
版权声明:本文为CSDN博主「小新要变强」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值