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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 最新最全面的Spring详解(一)——Spring概述与IOC容器(中)

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

**注意:**这个类没有什么特别之处。 它是一个POJO,不依赖于容器特定的接口、基类或注解。


1、使用参数的顺序实现


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


package x.y;
public class ThingOne {
    public ThingOne(ThingTwo thingTwo,ThingThree thingThree) {
        // ...
    }
}

假设【ThingTwo】和【ThingThree】类没有继承关系,就不存在潜在的歧义。 因此,下面的配置工作正常,并且您不需要在<constructor-arg/> 元素中显式指定【构造函数参数索引或类型】。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <!-- 直接写就可以 -->
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>
    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

2、构造函数参数类型匹配

当引用另一个bean时,类型是已知的,可以进行匹配(如上例所示)。 当使用简单类型时,例如<value>true</value> , 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;
    }
}

在前面的场景中,如果你通过使用【type】属性显式指定构造函数参数的类型,容器可以使用与简单类型匹配的类型,如下面的示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

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

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

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

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


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


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

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

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

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


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


<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>
    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面的示例显示了相应的【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;
    }
}

其他情况:

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

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面的示例显示了相应的’ 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;
    }
}

下面的示例显示了相应的’ 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;
    }
}


【static】工厂方法的参数是由<constructor-arg/> 元素提供的,就像实际使用了构造函数一样。spring会根据元数据构造工厂对象,再由工厂对象创建实例,创建的实例交由spring容器管理。


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


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


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


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


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


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


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


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


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


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

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>


(2)idref元素

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

<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>


前面的beanDifination代码段(在运行时)与下面的代码段完全相同:

<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

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


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


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


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

<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required here -->
</bean>
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref bean="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>


(4)内部bean


在<property/> 或<constructor-arg/> 元素内部的<bean/> 元素定义了一个内部bean,如下面的例子所示:

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

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


(5)集合


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

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

映射键或值或集合值的值也可以是以下元素中的任何一个:


bean | ref | idref | list | set | map | props | value | null


(6)null值和空字符串

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

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

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

exampleBean.setEmail("");

<null/>元素处理 null值。 下面的例子显示了一个示例:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上述配置相当于以下Java代码:


exampleBean.setEmail(null);


(7)带有【p命名空间】的XML配置方式


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


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


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <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"/>
</beans>


下一个例子包括另外两个beanDifination,它们都引用了另一个bean:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <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>
</beans>

建议您仔细选择方法,并将其告知您的团队成员,用以形成规范的统一的XML文档。


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


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


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


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <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"/>
</beans>

【c: 命名空间】通过名称设置构造函数参数。 类似地,它需要在XML文件中声明对应的命名空间。

对于【构造函数参数名不可用的罕见情况】(通常是在没有调试信息的情况下编译字节码),可以使用回退参数索引,如下所示:

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>

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


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


(9)复合属性名


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

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

【 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中,这种行为是由<bean/> 元素上的【lazy-init】属性控制的,如下面的示例所示:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

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


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

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

🍀(5)自动装配


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


自动装配可以显著减少指定属性或构造函数参数的需要。

自动装配可以随着对象的发展更新配置。 例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。

当使用基于xml的配置元数据时,您可以使用<bean/>元素的【autowire】属性为beanDifination指定自动装配模式。 自动装配功能有四种模式。 您可以指定每个bean的自动装配,从而可以选择要自动装配哪些bean,自动装配的四种模式如下表所示:


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


通过’ byType ‘或’ constructor '自动装配模式,您可以连接【数组和类型化集合】。 在这种情况下,容器中所有【匹配预期类型的自动装配候选对象】都将被提供以满足依赖关系。其中,自动连接的“Map”实例的值包含所有与期望类型匹配的bean实例,而“Map”实例的键包含相应的bean名称。


从自动装配中排除Bean


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


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


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


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


🍀(6)循环依赖

9835894f6a3b4ace97efa223b1195539.png

容器会按照如下方式执行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的所有后续请求和引用都会返回缓存的对象。 下面的图片展示了单例作用域是如何工作的:

9363660039094c068fa260d46d020a9f.png

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

<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

🍀(2)原型作用域


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


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

5c0c08a62ad949e0af08e605e6e95f5f.png

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


<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>


与其他作用域相比,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 '作用域。

相关文章
|
8天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
25 2
|
8天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
19 1
|
1月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
21天前
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
23天前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
44 0
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
35 0
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
76 0
|
7月前
|
XML Java 数据格式
Spring IoC容器初始化过程(xml形式)
Spring IoC容器初始化过程(xml形式)
82 0
|
7月前
|
XML Java 数据格式
Spring5源码(15)-IoC容器启动过程简析及XmlBeanFactory初始化
Spring5源码(15)-IoC容器启动过程简析及XmlBeanFactory初始化
71 1