Spring:核心技术——IoC容器

Spring:核心技术——IoC容器

说明:本文翻译自Spring官方文档Core Technologies — 核心技术 (spring.io)

Version 6.0.7

This part of the reference documentation covers all the technologies that are absolutely integral to the Spring Framework.
参考文档的这一部分涵盖了Spring框架中不可或缺的所有技术。

Foremost amongst these is the Spring Framework’s Inversion of Control (IoC) container. A thorough treatment of the Spring Framework’s IoC container is closely followed by comprehensive coverage of Spring’s Aspect-Oriented Programming (AOP) technologies. The Spring Framework has its own AOP framework, which is conceptually easy to understand and which successfully addresses the 80% sweet spot of AOP requirements in Java enterprise programming.
其中最重要的是Spring框架的控制反转(Inversion of Control,IoC)容器。在全面介绍Spring框架的IoC容器之后,紧接着是对Spring的面向方面编程(AOP)技术的全面介绍。Spring框架有自己的AOP框架,它在概念上易于理解,并且成功地解决了Java企业编程中80%的AOP需求。

Coverage of Spring’s integration with AspectJ (currently the richest — in terms of features — and certainly most mature AOP implementation in the Java enterprise space) is also provided.
本文还介绍了Spring与AspectJ的集成(就特性而言,目前是最丰富的,当然也是Java企业领域中最成熟的AOP实现)。

AOT processing can be used to optimize your application ahead-of-time. It is typically used for native image deployment using GraalVM.
AOT处理可用于提前优化应用程序。它通常用于使用GraalVM的本机映像部署。

1. IoC容器

This chapter covers Spring’s Inversion of Control (IoC) container.
本章介绍Spring的控制反转(Inversion of Control,IoC)容器。

1.1. Spring IoC容器和Bean简介

This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
本章介绍了控制反转(IoC)原则的Spring框架实现。IoC也被称为依赖注入(DI)。在此过程中,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即,与它们一起工作的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖项的实例化或位置。

The org.springframework.beans and org.springframework.context packages are the basis for Spring Framework’s IoC container. The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object. ApplicationContext is a sub-interface of BeanFactory. It adds:
org.springframework.beansorg.springframework.context 包是Spring框架的IoC容器的基础。 BeanFactory 接口提供了能够管理任何类型对象的高级配置机制。 ApplicationContextBeanFactory 的子接口。它添加了(额外功能):

  • Easier integration with Spring’s AOP features
    更容易与Spring的AOP特性集成

  • Message resource handling (for use in internationalization)
    消息资源处理(用于国际化)

  • Event publication

    事件发布

  • Application-layer specific contexts such as the WebApplicationContext for use in web applications.
    应用程序层特定上下文,如用于Web应用程序的 WebApplicationContext

In short, the BeanFactory provides the configuration framework and basic functionality, and the ApplicationContext adds more enterprise-specific functionality. The ApplicationContext is a complete superset of the BeanFactory and is used exclusively in this chapter in descriptions of Spring’s IoC container. For more information on using the BeanFactory instead of the ApplicationContext, see the section covering the BeanFactory API.
简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContextBeanFactory 的完整超集,在本章描述Spring的IoC容器时专门使用。有关使用 BeanFactory 而不是 ApplicationContext 的详细信息,请参见 BeanFactory API部分。

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。这正是bean有别于应用程序中其他对象的地方。Bean以及它们之间的依赖关系反映在容器使用的配置元数据中。

1.2. 容器概述

The org.springframework.context.ApplicationContext interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans. The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It lets you express the objects that compose your application and the rich interdependencies between those objects.
org.springframework.context.ApplicationContext 接口表示Spring IoC容器,负责实例化、配置和组装bean。容器通过阅读配置元数据来获取关于实例化、配置和组装哪些对象的指令。配置元数据以XML、Java注释或Java代码表示。它允许您表示组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

Several implementations of the ApplicationContext interface are supplied with Spring. In stand-alone applications, it is common to create an instance of ClassPathXmlApplicationContext or FileSystemXmlApplicationContext. While XML has been the traditional format for defining configuration metadata, you can instruct the container to use Java annotations or code as the metadata format by providing a small amount of XML configuration to declaratively enable support for these additional metadata formats.
Spring提供了 ApplicationContext 接口的几个实现。在独立应用程序中,通常创建 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 的实例。虽然XML一直是定义配置元数据的传统格式,但您可以通过提供少量XML配置以声明方式启用对这些附加元数据格式的支持,来指示容器使用Java注释或代码作为元数据格式。

In most application scenarios, explicit user code is not required to instantiate one or more instances of a Spring IoC container. For example, in a web application scenario, a simple eight (or so) lines of boilerplate web descriptor XML in the web.xml file of the application typically suffices (see Convenient ApplicationContext Instantiation for Web Applications). If you use the Spring Tools for Eclipse (an Eclipse-powered development environment), you can easily create this boilerplate configuration with a few mouse clicks or keystrokes.
在大多数应用程序场景中,实例化Spring IoC容器的一个或多个实例不需要显式用户代码。例如,在Web应用程序场景中,应用程序的 web.xml 文件中简单的八行(左右)样板Web描述符XML通常就足够了(请参见Web应用程序的便捷ApplicationContext实例化)。如果您使用Spring Tools for Eclipse(一个由Eclipse支持的开发环境),您可以通过几次鼠标点击或击键轻松地创建这个样板配置。

The following diagram shows a high-level view of how Spring works. Your application classes are combined with configuration metadata so that, after the ApplicationContext is created and initialized, you have a fully configured and executable system or application.
下图显示了Spring工作方式的高级视图。您的应用程序类与配置元数据相结合,这样,在创建并初始化 ApplicationContext 之后,您就拥有了一个完全配置好的可执行系统或应用程序。

container magic

Figure 1. The Spring IoC container
图1. Spring IoC容器

1.2.1. 配置元数据

As the preceding diagram shows, the Spring IoC container consumes a form of configuration metadata. This configuration metadata represents how you, as an application developer, tell the Spring container to instantiate, configure, and assemble the objects in your application.
如上图所示,Spring IoC容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员,告诉Spring容器如何实例化、配置和组装应用程序中的对象。

Configuration metadata is traditionally supplied in a simple and intuitive XML format, which is what most of this chapter uses to convey key concepts and features of the Spring IoC container.
配置元数据通常以简单直观的XML格式提供,本章大部分内容都使用这种格式来传达Spring IoC容器的关键概念和特性。

XML-based metadata is not the only allowed form of configuration metadata. The Spring IoC container itself is totally decoupled from the format in which this configuration metadata is actually written. These days, many developers choose Java-based configuration for their Spring applications.
基于XML的元数据不是配置元数据的唯一允许形式。SpringIoC容器本身与实际编写配置元数据的格式完全分离。目前,许多开发人员选择基于Java的配置来配置Spring应用程序。

For information about using other forms of metadata with the Spring container, see:
有关对Spring容器使用其它形式的元数据的信息,请参见:

  • Annotation-based configuration: define beans using annotation-based configuration metadata.
    基于注解的配置:使用基于注释的配置元数据定义bean。
  • Java-based configuration: define beans external to your application classes by using Java rather than XML files. To use these features, see the @Configuration, @Bean, @Import, and @DependsOn annotations.
    基于Java的配置:使用Java而不是XML文件定义应用程序类外部的bean。要使用这些功能,请参见 @Configuration@Bean@Import@DependsOn 注释。

Spring configuration consists of at least one and typically more than one bean definition that the container must manage. XML-based configuration metadata configures these beans as <bean/> elements inside a top-level <beans/> element. Java configuration typically uses @Bean-annotated methods within a @Configuration class.
Spring配置由容器必须管理的至少一个、通常多个bean定义组成。基于XML的配置元数据将这些bean配置为顶层 <beans/> 元素中的 <bean/> 元素。Java配置通常在 @Configuration 类中使用 @Bean 注释的方法。

These bean definitions correspond to the actual objects that make up your application. Typically, you define service layer objects, persistence layer objects such as repositories or data access objects (DAOs), presentation objects such as Web controllers, infrastructure objects such as a JPA EntityManagerFactory, JMS queues, and so forth. Typically, one does not configure fine-grained domain objects in the container, because it is usually the responsibility of repositories and business logic to create and load domain objects.
这些bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象、持久层对象(如存储库或数据访问对象(DAO))、表示对象(如Web控制器)、基础结构对象(如JPA EntityManagerFactory )、JMS队列等。通常,用户不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是存储库和业务逻辑的职责。

The following example shows the basic structure of XML-based configuration metadata:
以下示例显示了基于XML的配置元数据的基本结构:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="..."> (1) (2)
        <!-- 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 -->

</beans>
  1. The id attribute is a string that identifies the individual bean definition.
    id 属性是标识单个bean定义的字符串。
  2. The class attribute defines the type of the bean and uses the fully qualified class name.
    class 属性定义bean的类型,并使用完全限定的类名。

The value of the id attribute can be used to refer to collaborating objects. The XML for referring to collaborating objects is not shown in this example. See Dependencies for more information.
id 属性的值可用于引用协作对象。本例中没有显示用于引用协作对象的XML。有关详细信息,请参见依赖项。

1.2.2. 实例化容器

The location path or paths supplied to an ApplicationContext constructor are resource strings that let the container load configuration metadata from a variety of external resources, such as the local file system, the Java CLASSPATH, and so on.
提供给 ApplicationContext 构造函数的一个或多个位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

After you learn about Spring’s IoC container, you may want to know more about Spring’s Resource abstraction (as described in Resources), which provides a convenient mechanism for reading an InputStream from locations defined in a URI syntax. In particular, Resource paths are used to construct applications contexts, as described in Application Contexts and Resource Paths.
在了解Spring的IoC容器之后,您可能希望了解有关Spring的 Resource 抽象(如资源中所述)的更多信息,它提供了一种方便的机制,用于从URI语法中定义的位置读入InputStream。特别地, Resource 路径用于构造应用上下文,如应用上下文和资源路径中所述。

The following example shows the service layer objects (services.xml) configuration file:
以下示例显示了服务层对象 (services.xml) 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 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 -->

</beans>

The following example shows the data access objects daos.xml file:
以下示例显示数据访问对象 daos.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <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 -->

</beans>

In the preceding example, the service layer consists of the PetStoreServiceImpl class and two data access objects of the types JpaAccountDao and JpaItemDao (based on the JPA Object-Relational Mapping standard). The property name element refers to the name of the JavaBean property, and the ref element refers to the name of another bean definition. This linkage between id and ref elements expresses the dependency between collaborating objects. For details of configuring an object’s dependencies, see Dependencies.
在前面的示例中,服务层由 PetStoreServiceImpl 类和 JpaAccountDaoJpaItemDao 类型的两个数据访问对象(基于JPA对象关系映射标准)组成。 property name 元素引用JavaBean属性的名称, ref 元素引用另一个bean定义的名称。 idref 元素之间的这种链接表达了协作对象之间的依赖性。有关配置对象依赖关系的详细信息,请参阅依赖关系

合成基于XML的配置元数据

It can be useful to have bean definitions span multiple XML files. Often, each individual XML configuration file represents a logical layer or module in your architecture.
让bean定义跨越多个XML文件是很有用的。通常,每个单独的XML配置文件都表示体系结构中的一个逻辑层或模块。

You can use the application context constructor to load bean definitions from all these XML fragments. This constructor takes multiple Resource locations, as was shown in the previous section. Alternatively, use one or more occurrences of the <import/> element to load bean definitions from another file or files. The following example shows how to do so:
您可以使用应用程序上下文构造器从所有这些XML片段加载bean定义。此构造函数接受多个 Resource 位置,如上一节所示。或者,通过使用一个或多个 <import/> 标签定义从另一个或多个文件加载bean定义。下面的示例说明了如何执行此操作:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

In the preceding example, external bean definitions are loaded from three files: services.xml, messageSource.xml, and themeSource.xml. All location paths are relative to the definition file doing the importing, so services.xml must be in the same directory or classpath location as the file doing the importing, while messageSource.xml and themeSource.xml must be in a resources location below the location of the importing file. As you can see, a leading slash is ignored. However, given that these paths are relative, it is better form not to use the slash at all. The contents of the files being imported, including the top level <beans/> element, must be valid XML bean definitions, according to the Spring Schema.
在前面的示例中,从三个文件加载外部Bean定义:services.xmlmessageSource.xmlthemeSource.xml.。所有位置路径都相对于执行导入的定义文件,因此 services.xml 必须与执行导入的文件位于同一目录或类路径位置,而 messageSource.xmlthemeSource.xml 必须位于导入文件位置下方的 resources 位置。如您所见,前导斜杠被忽略。然而,考虑到这些路径是相对的,最好不要使用斜线。根据Spring Schema,导入的文件内容(包括顶层 <beans/> 元素)必须是有效的XMLbean定义。

It is possible, but not recommended, to reference files in parent directories using a relative “…/” path. Doing so creates a dependency on a file that is outside the current application. In particular, this reference is not recommended for classpath: URLs (for example, classpath:../services.xml), where the runtime resolution process chooses the “nearest” classpath root and then looks into its parent directory. Classpath configuration changes may lead to the choice of a different, incorrect directory.
可以(但不建议)使用相对“…/”引用父目录中的文件“path。这样做会创建对当前应用程序外部文件的依赖项。特别是,不建议将此引用用于 classpath: URL(例如, classpath:../services.xml ),在这些URL中,运行时解析进程选择“最近的”类路径根,然后查看其父目录。类路径配置更改可能导致选择不同的、不正确的目录。

You can always use fully qualified resource locations instead of relative paths: for example, file:C:/config/services.xml or classpath:/config/services.xml. However, be aware that you are coupling your application’s configuration to specific absolute locations. It is generally preferable to keep an indirection for such absolute locations — for example, through " … " p l a c e h o l d e r s t h a t a r e r e s o l v e d a g a i n s t J V M s y s t e m p r o p e r t i e s a t r u n t i m e . 您可以始终使用完全限定的资源位置,而不是相对路径:例如 ‘ f i l e : C : / c o n f i g / s e r v i c e s . x m l ‘ 或 ‘ c l a s s p a t h : / c o n f i g / s e r v i c e s . x m l ‘ 。但是,请注意,您将应用程序的配置耦合到特定的绝对位置。通常最好为这样的绝对位置保留一个间接寻址 − − 例如,通过“ {…}" placeholders that are resolved against JVM system properties at runtime. 您可以始终使用完全限定的资源位置,而不是相对路径:例如 `file:C:/config/services.xml` 或 `classpath:/config/services.xml` 。但是,请注意,您将应用程序的配置耦合到特定的绝对位置。通常最好为这样的绝对位置保留一个间接寻址--例如,通过“ "placeholdersthatareresolvedagainstJVMsystempropertiesatruntime.您可以始终使用完全限定的资源位置,而不是相对路径:例如file:C:/config/services.xmlclasspath:/config/services.xml。但是,请注意,您将应用程序的配置耦合到特定的绝对位置。通常最好为这样的绝对位置保留一个间接寻址例如,通过{…}”占位符,在运行时根据JVM系统属性解析这些占位符。

The namespace itself provides the import directive feature. Further configuration features beyond plain bean definitions are available in a selection of XML namespaces provided by Spring — for example, the context and util namespaces.
命名空间本身提供了import指令功能。除了普通bean定义之外,Spring提供的XML命名空间中还提供了更多配置特性——例如, contextutil 命名空间。

Groovy Bean定义DSL

As a further example for externalized configuration metadata, bean definitions can also be expressed in Spring’s Groovy Bean Definition DSL, as known from the Grails framework. Typically, such configuration live in a “.groovy” file with the structure shown in the following example:
作为外部化配置元数据的另一个示例,bean定义也可以用Spring的Groovy Bean Definition DSL来表示,这在Grails框架中是已知的。通常,这样的配置存在于一个.groovy文件中,其结构如下例所示:

beans {
   
    dataSource(BasicDataSource) {
   
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
   
        dataSource = dataSource
    }
    myService(MyService) {
   
        nestedBean = {
    AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

This configuration style is largely equivalent to XML bean definitions and even supports Spring’s XML configuration namespaces. It also allows for importing XML bean definition files through an importBeans directive.
这种配置风格在很大程度上等同于XMLbean定义,甚至支持Spring的XML配置名称空间。它还允许通过 importBeans 指令导入XML bean定义文件。

1.2.3. 使用容器

The ApplicationContext is the interface for an advanced factory capable of maintaining a registry of different beans and their dependencies. By using the method T getBean(String name, Class<T> requiredType), you can retrieve instances of your beans.
ApplicationContext 是高级工厂的接口,该工厂能够维护不同bean及其依赖项的注册表。通过使用方法 T getBean(String name, Class<T> requiredType) ,您可以检索bean的实例。

The ApplicationContext lets you read bean definitions and access them, as the following example shows:
ApplicationContext 允许您读取和访问Bean定义,如以下示例所示:

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

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

With Groovy configuration, bootstrapping looks very similar. It has a different context implementation class which is Groovy-aware (but also understands XML bean definitions). The following example shows Groovy configuration:
使用Groovy配置时,引导看起来非常相似。它有一个不同的上下文实现类,它是Groovy感知的(但也理解XMLbean定义)。下面的示例显示了Groovy配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

The most flexible variant is GenericApplicationContext in combination with reader delegates — for example, with XmlBeanDefinitionReader for XML files, as the following example shows:
最灵活的变体是 GenericApplicationContext 与reader委托的组合——例如,对于XML文件,使用 XmlBeanDefinitionReader ,如下例所示:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

You can also use the GroovyBeanDefinitionReader for Groovy files, as the following example shows:
您还可以将 GroovyBeanDefinitionReader 用于Groovy文件,如下例所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

You can mix and match such reader delegates on the same ApplicationContext, reading bean definitions from diverse configuration sources.
您可以在同一个 ApplicationContext 上混合和匹配这样的读取器委托,从不同的配置源阅读bean定义。

You can then use getBean to retrieve instances of your beans. The ApplicationContext interface has a few other methods for retrieving beans, but, ideally, your application code should never use them. Indeed, your application code should have no calls to the getBean() method at all and thus have no dependency on Spring APIs at all. For example, Spring’s integration with web frameworks provides dependency injection for various web framework components such as controllers and JSF-managed beans, letting you declare a dependency on a specific bean through metadata (such as an autowiring annotation).
然后可以使用 getBean 检索bean的实例。 ApplicationContext 接口有一些其他的方法来检索bean,但是,理想情况下,应用程序代码不应该使用它们。实际上,您的应用程序代码根本不应该调用 getBean() 方法,因此根本不依赖于SpringAPI。例如,Spring与Web框架的集成为各种Web框架组件(如控制器和JSF管理的bean)提供了依赖注入,允许您通过元数据(如自动装配注释)声明对特定bean的依赖。

1.3. Bean概述

A Spring IoC container manages one or more beans. These beans are created with the configuration metadata that you supply to the container (for example, in the form of XML <bean/> definitions).
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据(例如,以XML <bean/> 定义的形式)创建的。

Within the container itself, these bean definitions are represented as BeanDefinition objects, which contain (among other information) the following metadata:
在容器本身中,这些bean定义表示为 BeanDefinition 对象,其中包含(以及其他信息)以下元数据:

  • A package-qualified class name: typically, the actual implementation class of the bean being defined.
    包限定的类名:通常是正在定义的bean的实际实现类。
  • Bean behavioral configuration elements, which state how the bean should behave in the container (scope, lifecycle callbacks, and so forth).
    Bean行为配置元素,这些元素规定了Bean在容器中的行为方式(范围、生命周期回调等)。
  • References to other beans that are needed for the bean to do its work. These references are also called collaborators or dependencies.
    对Bean执行其工作所需的其他Bean的引用。这些引用也称为协作者或依赖项。
  • Other configuration settings to set in the newly created object — for example, the size limit of the pool or the number of connections to use in a bean that manages a connection pool.
    要在新创建的对象中设置的其他配置设置——例如,池的大小限制或要在管理连接池的Bean中使用的连接数。

This metadata translates to a set of properties that make up each bean definition. The following table describes these properties:
此元数据转换为一组属性,这些属性构成了每个bean定义。下表介绍了这些属性:

表1. Bean定义

Property Explained in… 解释于…
Class Instantiating Beans 实例化Bean
Name Naming Beans
Scope Bean Scopes
Constructor arguments 构造函数参数 Dependency Injection 依赖注入
Properties Dependency Injection 依赖注入
Autowiring mode Autowiring Collaborators 自动装配协作者
Lazy initialization mode 惰性初始化模式 Lazy-initialized Beans 延迟初始化Bean
Initialization method 初始化方法 Initialization Callbacks 初始化回调
Destruction method 销毁方法 Destruction Callbacks 销毁回调

In addition to bean definitions that contain information on how to create a specific bean, the ApplicationContext implementations also permit the registration of existing objects that are created outside the container (by users). This is done by accessing the ApplicationContext’s BeanFactory through the getBeanFactory() method, which returns the DefaultListableBeanFactory implementation. DefaultListableBeanFactory supports this registration through the registerSingleton(..) and registerBeanDefinition(..) methods. However, typical applications work solely with beans defined through regular bean definition metadata.
除了包含如何创建特定bean的信息的bean定义之外, ApplicationContext 实现还允许注册在容器之外(由用户)创建的现有对象。这是通过 getBeanFactory() 方法访问ApplicationContext的 BeanFactory 来完成的,该方法返回 DefaultListableBeanFactory 实现。 DefaultListableBeanFactory 通过 registerSingleton(..)registerBeanDefinition(..) 方法支持此注册。但是,典型的应用程序只使用通过常规bean定义元数据定义的bean。

Bean metadata and manually supplied singleton instances need to be registered as early as possible, in order for the container to properly reason about them during autowiring and other introspection steps. While overriding existing metadata and existing singleton instances is supported to some degree, the registration of new beans at runtime (concurrently with live access to the factory) is not officially supported and may lead to concurrent access exceptions, inconsistent state in the bean container, or both.
Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确地推理它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但在运行时注册新bean(与对工厂的实时访问并发)并不受正式支持,这可能会导致并发访问异常、bean容器中的不一致状态,或者两者兼而有之。

1.3.1. 命名Bean

Every bean has one or more identifiers. These identifiers must be unique within the container that hosts the bean. A bean usually has only one identifier. However, if it requires more than one, the extra ones can be considered aliases.
每个bean都有一个或多个标识符。这些标识符在承载bean的容器中必须是唯一的。bean通常只有一个标识符。但是,如果它需要多个别名,则可以将多余的别名视为别名。

In XML-based configuration metadata, you use the id attribute, the name attribute, or both to specify bean identifiers. The id attribute lets you specify exactly one id. Conventionally, these names are alphanumeric (‘myBean’, ‘someService’, etc.), but they can contain special characters as well. If you want to introduce other aliases for the bean, you can also specify them in the name attribute, separated by a comma (,), semicolon (;), or white space. Although the id attribute is defined as an xsd:string type, bean id uniqueness is enforced by the container, though not by XML parsers.
在基于XML的配置元数据中,可以使用 id 属性、 name 属性或同时使用这两个属性来指定Bean标识符。 id 属性允许您仅指定一个 id 。传统上,这些名称是字母数字(“myBean”、“someService”等),但是它们也可以包含特殊字符。如果要为bean引入其他别名,也可以在 name 属性中指定它们,用逗号( , )、分号( ; )或空格分隔。尽管 id 属性被定义为 xsd:string 类型,但是容器强制bean id 的唯一性,而不是XML解析器。

You are not required to supply a name or an id for a bean. If you do not supply a name or id explicitly, the container generates a unique name for that bean. However, if you want to refer to that bean by name, through the use of the ref element or a Service Locator style lookup, you must provide a name. Motivations for not supplying a name are related to using inner beans and autowiring collaborators.
您不需要为Bean提供 nameid 。如果没有显式地提供 nameid ,容器将为该bean生成一个唯一的名称。但是,如果您希望通过使用 ref 元素或ServiceLocator样式查找按名称引用该bean,则必须提供名称。不提供名称的动机与使用内部bean自动装配协作者有关。

Bean Naming Conventions
Bean命名约定

The convention is to use the standard Java convention for instance field names when naming beans. That is, bean names start with a lowercase letter and are camel-cased from there. Examples of such names include accountManager, accountService, userDao, loginController, and so forth.
约定是在命名bean时对实例字段名使用标准Java约定。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰式大小写。这样的名称的示例包括 accountManageraccountServiceuserDaologinController 等。

Naming beans consistently makes your configuration easier to read and understand. Also, if you use Spring AOP, it helps a lot when applying advice to a set of beans related by name.
一致地命名bean使您的配置更易于阅读和理解。此外,如果您使用SpringAOP,那么在将建议应用于一组名称相关的bean时,它会有很大帮助。

With component scanning in the classpath, Spring generates bean names for unnamed components, following the rules described earlier: essentially, taking the simple class name and turning its initial character to lower-case. However, in the (unusual) special case when there is more than one character and both the first and second characters are upper case, the original casing gets preserved. These are the same rules as defined by java.beans.Introspector.decapitalize (which Spring uses here).
通过在类路径中进行组件扫描,Spring会按照前面描述的规则为未命名的组件生成bean名称:本质上就是取简单的类名并将其首字符转换为小写。但是,在(不常见的)特殊情况下,当有多个字符并且第一个和第二个字符都是大写时,原始大小写将被保留。这些规则与 java.beans.Introspector.decapitalize (Spring在这里使用的)定义的规则相同。

在Bean定义之外为Bean添加别名

In a bean definition itself, you can supply more than one name for the bean, by using a combination of up to one name specified by the id attribute and any number of other names in the name attribute. These names can be equivalent aliases to the same bean and are useful for some situations, such as letting each component in an application refer to a common dependency by using a bean name that is specific to that component itself.
在Bean定义本身中,您可以为Bean提供多个名称,方法是使用 id 属性指定的最多一个名称和 name 属性中任意数量的其他名称的组合。这些名称可以是同一个bean的等效别名,并且在某些情况下非常有用,例如,通过使用特定于应用程序中的每个组件本身的bean名称,使该组件引用一个公共依赖项。

Specifying all aliases where the bean is actually defined is not always adequate, however. It is sometimes desirable to introduce an alias for a bean that is defined elsewhere. This is commonly the case in large systems where configuration is split amongst each subsystem, with each subsystem having its own set of object definitions. In XML-based configuration metadata, you can use the <alias/> element to accomplish this. The following example shows how to do so:
但是,指定实际定义bean的所有别名并不总是足够的。有时需要为在其他地方定义的bean引入别名。这在大型系统中是常见的情况,其中配置在每个子系统之间被分割,每个子系统具有其自己的对象定义集。在基于XML的配置元数据中,可以使用 <alias/> 元素来完成此操作。下面的示例说明了如何执行此操作:

<alias name="fromName" alias="toName"/>

In this case, a bean (in the same container) named fromName may also, after the use of this alias definition, be referred to as toName.
在这种情况下,名为 fromName 的bean(在同一个容器中)在使用这个别名定义之后也可以称为 toName

For example, the configuration metadata for subsystem A may refer to a DataSource by the name of subsystemA-dataSource. The configuration metadata for subsystem B may refer to a DataSource by the name of subsystemB-dataSource. When composing the main application that uses both these subsystems, the main application refers to the DataSource by the name of myApp-dataSource. To have all three names refer to the same object, you can add the following alias definitions to the configuration metadata:
例如,子系统A的配置元数据可以通过名称 subsystemA-dataSource 来引用数据源。子系统B的配置元数据可以引用名称为 subsystemB-dataSource 的数据源。当组合使用这两个子系统的主应用程序时,主应用程序通过名称 myApp-dataSource 引用DataSource。要使所有三个名称都引用同一对象,可以将以下别名定义添加到配置元数据中:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

Now each component and the main application can refer to the dataSource through a name that is unique and guaranteed not to clash with any other definition (effectively creating a namespace), yet they refer to the same bean.
现在,每个组件和主应用程序都可以通过一个唯一的名称引用dataSource,并保证不会与任何其他定义冲突(有效地创建了一个名称空间),但它们引用的是同一个bean。

Java-configuration
Java配置

If you use Java Configuration, the @Bean annotation can be used to provide aliases. See Using the @Bean Annotation for details.
如果使用Java配置,则 @Bean 注释可用于提供别名。有关详细信息,请参见使用 @Bean 注解

1.3.2. 实例化Bean

A bean definition is essentially a recipe for creating one or more objects. The container looks at the recipe for a named bean when asked and uses the configuration metadata encapsulated by that bean definition to create (or acquire) an actual object.
bean定义本质上是创建一个或多个对象的方法。当被询问时,容器查看命名bean的配置,并使用该bean定义封装的配置元数据来创建(或获取)实际对象。

If you use XML-based configuration metadata, you specify the type (or class) of object that is to be instantiated in the class attribute of the <bean/> element. This class attribute (which, internally, is a Class property on a BeanDefinition instance) is usually mandatory. (For exceptions, see Instantiation by Using an Instance Factory Method and Bean Definition Inheritance.) You can use the Class property in one of two ways:
如果使用基于XML的配置元数据,则在 <bean/> 元素的 class 属性中指定要实例化的对象的类型(或类)。这个 class 属性(在内部,它是 BeanDefinition 实例上的 Class 属性)通常是强制的。(例外情况,请参见使用实例工厂方法Bean定义继承进行实例化。)可以通过以下两种方式之一使用 Class 属性:

  • Typically, to specify the bean class to be constructed in the case where the container itself directly creates the bean by calling its constructor reflectively, somewhat equivalent to Java code with the new operator.
    通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这在某种程度上相当于Java代码中的 new 操作符。
  • To specify the actual class containing the static factory method that is invoked to create the object, in the less common case where the container invokes a static factory method on a class to create the bean. The object type returned from the invocation of the static factory method may be the same class or another class entirely.
    指定包含 static 工厂方法的实际类,该方法被调用来创建对象,这种情况不太常见,即容器调用类上的 static 工厂方法来创建Bean。从 static 工厂方法的调用返回的对象类型可以是同一个类或完全是另一个类。

Nested class names 嵌套类名

If you want to configure a bean definition for a nested class, you may use either the binary name or the source name of the nested class.
如果要为嵌套类配置Bean定义,可以使用嵌套类的二进制名称或源名称。

For example, if you have a class called SomeThing in the com.example package, and this SomeThing class has a static nested class called OtherThing, they can be separated by a dollar sign ($) or a dot (.). So the value of the class attribute in a bean definition would be com.example.SomeThing$OtherThing or com.example.SomeThing.OtherThing.
例如,如果在 com.example 包中有一个名为 SomeThing 的类,而这个 SomeThing 类有一个名为 OtherThingstatic 嵌套类,则可以用美元符号( $ )或点( . )分隔它们。因此,bean定义中 class 属性的值应该是 com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing

使用构造函数实例化

When you create a bean by the constructor approach, all normal classes are usable by and compatible with Spring. That is, the class being developed does not need to implement any specific interfaces or to be coded in a specific fashion. Simply specifying the bean class should suffice. However, depending on what type of IoC you use for that specific bean, you may need a default (empty) constructor.
当您通过构造函数方法创建bean时,所有普通类都可以被Spring使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式进行编码。只需指定bean类就足够了。但是,根据您为特定bean使用的IoC类型,您可能需要一个默认(空)构造函数。

The Spring IoC container can manage virtually any class you want it to manage. It is not limited to managing true JavaBeans. Most Spring users prefer actual JavaBeans with only a default (no-argument) constructor and appropriate setters and getters modeled after the properties in the container. You can also have more exotic non-bean-style classes in your container. If, for example, you need to use a legacy connection pool that absolutely does not adhere to the JavaBean specification, Spring can manage it as well.
SpringIoC容器几乎可以管理您希望它管理的任何类。它并不局限于管理真正的JavaBean。大多数Spring用户更喜欢实际的JavaBeans,它只有一个默认的(无参数的)构造函数,以及按照容器中的属性建模的适当的setter和getter。您还可以在容器中包含更多的非bean样式的类。例如,如果您需要使用一个完全不符合JavaBean规范的遗留连接池,Spring也可以管理它。

With XML-based configuration metadata you can specify your bean class as follows:
使用基于XML的配置元数据,可以按如下方式指定Bean类:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

For details about the mechanism for supplying arguments to the constructor (if required) and setting object instance properties after the object is constructed, see Injecting Dependencies.
有关向构造函数提供参数(如果需要)以及在构造对象后设置对象实例属性的机制的详细信息,请参见注入依赖项

使用静态工厂方法实例化

When defining a bean that you create with a static factory method, use the class attribute to specify the class that contains the static factory method and an attribute named factory-method to specify the name of the factory method itself. You should be able to call this method (with optional arguments, as described later) and return a live object, which subsequently is treated as if it had been created through a constructor. One use for such a bean definition is to call static factories in legacy code.
在定义使用静态工厂方法创建的Bean时,请使用 class 属性指定包含 static 工厂方法的类,并使用名为 factory-method 的属性指定工厂方法本身的名称。您应该能够调用此方法(使用可选参数,如后面所述)并返回一个活动对象,该对象随后被视为通过构造函数创建的对象。这种bean定义的一个用途是调用遗留代码中的 static 工厂。

The following bean definition specifies that the bean will be created by calling a factory method. The definition does not specify the type (class) of the returned object, but rather the class containing the factory method. In this example, the createInstance() method must be a static method. The following example shows how to specify a factory method:
下面的bean定义指定将通过调用工厂方法来创建bean。该定义不指定返回对象的类型(类),而是指定包含工厂方法的类。在本例中, createInstance() 方法必须是 static 方法。下面的示例说明如何指定工厂方法:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

The following example shows a class that would work with the preceding bean definition:
下面的示例显示了一个将与前面的Bean定义一起使用的类:

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

    public static ClientService createInstance() {
   
        return clientService;
    }
}

For details about the mechanism for supplying (optional) arguments to the factory method and setting object instance properties after the object is returned from the factory, see Dependencies and Configuration in Detail.
有关在对象从工厂返回后向工厂方法提供(可选)参数和设置对象实例属性的机制的详细信息,请参见依赖项和配置的详细信息

使用实例工厂方法进行实例化

Similar to instantiation through a static factory method, instantiation with an instance factory method invokes a non-static method of an existing bean from the container to create a new bean. To use this mechanism, leave the class attribute empty and, in the factory-bean attribute, specify the name of a bean in the current (or parent or ancestor) container that contains the instance method that is to be invoked to create the object. Set the name of the factory method itself with the factory-method attribute. The following example shows how to configure such a bean:
与通过静态工厂方法实例化类似,使用实例工厂方法实例化从容器调用现有bean的非静态方法来创建新bean。要使用此机制,请将 class 属性保留为空,并在 factory-bean 属性中指定当前(或父代或祖先)容器中的bean的名称,该容器包含要调用以创建对象的实例方法。使用 factory-method 属性设置工厂方法本身的名称。以下示例说明如何配置此类Bean:

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

The following example shows the corresponding class:
下面的示例显示了相应的类:

public class DefaultServiceLocator {
   

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
   
        return clientService;
    }
}

One factory class can also hold more than one factory method, as the following example shows:
一个工厂类还可以包含多个工厂方法,如下例所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

The following example shows the corresponding class:
下面的示例显示了相应的类:

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;
    }
}

This approach shows that the factory bean itself can be managed and configured through dependency injection (DI). See Dependencies and Configuration in Detail.
这种方法表明工厂bean本身可以通过依赖注入(DI)来管理和配置。请参阅相关性和配置的详细信息

In Spring documentation, “factory bean” refers to a bean that is configured in the Spring container and that creates objects through an instance or static factory method. By contrast, FactoryBean (notice the capitalization) refers to a Spring-specific FactoryBean implementation class.
在Spring文档中,“factory bean”指的是在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相比之下, FactoryBean (注意大写)引用特定于Spring的 FactoryBean 实现类。

确定Bean的运行时类型

The runtime type of a specific bean is non-trivial to determine. A specified class in the bean metadata definition is just an initial class reference, potentially combined with a declared factory method or being a FactoryBean class which may lead to a different runtime type of the bean, or not being set at all in case of an instance-level factory method (which is resolved via the specified factory-bean name instead). Additionally, AOP proxying may wrap a bean instance with an interface-based proxy with limited exposure of the target bean’s actual type (just its implemented interfaces).
确定特定bean的运行时类型并非易事。bean元数据定义中指定的类只是一个初始类引用,可能与声明的工厂方法结合,或者是 FactoryBean 类,这可能导致bean的不同运行时类型,或者在实例级工厂方法的情况下根本不设置(而是通过指定的 factory-bean 名称解析)。此外,AOP代理可以用基于接口的代理包装bean实例,并限制目标bean实际类型的公开(仅限于其实现的接口)。

The recommended way to find out about the actual runtime type of a particular bean is a BeanFactory.getType call for the specified bean name. This takes all of the above cases into account and returns the type of object that a BeanFactory.getBean call is going to return for the same bean name.
要了解特定bean的实际运行时类型,推荐的方法是对指定的bean名称进行 BeanFactory.getType 调用。这考虑了上述所有情况,并返回 BeanFactory.getBean 调用将为相同bean名称返回的对象类型。

1.4. 依赖

A typical enterprise application does not consist of a single object (or bean in the Spring parlance). Even the simplest application has a few objects that work together to present what the end-user sees as a coherent application. This next section explains how you go from defining a number of bean definitions that stand alone to a fully realized application where objects collaborate to achieve a goal.
一个典型的企业应用程序不是由单个对象(或者Spring术语中的bean)组成的。即使是最简单的应用程序也有几个对象,这些对象一起工作以呈现最终用户所看到的一致的应用程序。下一节将解释如何从定义多个独立的bean定义发展到一个完全实现的应用程序,在这个应用程序中,对象协作来实现一个目标。

1.4.1. 依赖注入

Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes or the Service Locator pattern.
依赖注入(DI)是一个过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系(即,与它们一起工作的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此得名“控制反转”),bean本身通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。

Code is cleaner with the DI principle, and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies and does not know the location or class of the dependencies. As a result, your classes become easier to test, particularly when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.
DI原则的代码更简洁,并且当对象提供了依赖项时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置或类。因此,类变得更易于测试,特别是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.
DI存在两种主要变体:基于构造函数的依赖注入基于Setter的依赖注入

基于构造函数的依赖注入

Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the bean is nearly equivalent, and this discussion treats arguments to a constructor and to a static factory method similarly. The following example shows a class that can only be dependency-injected with constructor injection:
基于构造函数的DI是通过容器调用具有多个参数的构造函数来实现的,每个参数表示一个依赖项。这与调用带有特定参数的 static 工厂方法来构造bean的做法几乎是等效的,并且本讨论以类似的方式处理构造函数的参数和 static 工厂方法的参数。下面的示例显示只能使用构造函数注入进行依赖项注入的类:

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...
}

Notice that there is nothing special about this class. It is a POJO that has no dependencies on container specific interfaces, base classes, or annotations.
请注意,这个类没有什么特别之处。它是一个POJO,不依赖于容器特定的接口、基类或注释。

构造函数参数解析

Constructor argument resolution matching occurs by using the argument’s type. If no potential ambiguity exists in the constructor arguments of a bean definition, the order in which the constructor arguments are defined in a bean definition is the order in which those arguments are supplied to the appropriate constructor when the bean is being instantiated. Consider the following class:
构造函数参数解析匹配通过使用参数的类型进行。如果Bean定义的构造函数参数中不存在潜在的歧义,则在Bean定义中定义构造函数参数的顺序就是实例化Bean时将这些参数提供给相应构造函数的顺序。考虑以下类:

package x.y;

public class ThingOne {
   

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

Assuming that the ThingTwo and ThingThree classes are not related by inheritance, no potential ambiguity exists. Thus, the following configuration works fine, and you do not need to specify the constructor argument indexes or types explicitly in the <constructor-arg/> element.
假设 ThingTwoThingThree 类不通过继承相关,则不存在潜在的模糊性。因此,下面的配置工作正常,您不需要在 <constructor-arg/> 元素中显式地指定构造函数参数索引或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值