20191224 Spring官方文档(Core 1.1-1.4)

1. IoC容器

1.1。Spring IoC容器和Bean简介

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

  • 与Spring的AOP功能轻松集成
  • 消息资源处理(用于国际化)
  • 活动发布
  • 应用层特定的上下文,例如用于Web应用程序中的WebApplicationContext 。

简而言之,BeanFactory提供了配置框架和基本功能,并且ApplicationContext增加了更多针对企业的功能。ApplicationContext是BeanFactory的一个完整的超集。

在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为Bean。Bean是由Spring IoC容器实例化,组装和管理的对象。否则,bean仅仅是应用程序中许多对象之一。Bean及其依赖项反映在容器使用的配置元数据中。

1.2。容器概述

org.springframework.context.ApplicationContext接口代表Spring IoC容器,并负责实例化,配置和组装Bean。容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。配置元数据以XML,Java批注或Java代码表示。它使您能够表达组成应用程序的对象以及这些对象之间的丰富相互依赖关系。

Spring提供了ApplicationContext接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例 。尽管XML是定义配置元数据的传统格式,但是您可以通过提供少量XML配置来声明性地启用对其他元数据格式的支持,从而指示容器将Java注释或代码用作元数据格式。

1.2.1。配置元数据

配置元数据表示您作为应用程序开发人员如何告诉Spring容器实例化,配置和组装应用程序中的对象。可选XML格式或Java格式。

1.2.2。实例化容器

Spring的Resource抽象为从URI语法中定义的位置读取InputStream提供了一种方便的机制。具体来说,Resource 路径用于构造应用程序上下文。

org.springframework.core.io.Resource

组成基于XML的配置元数据

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

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

所有位置路径都相对于进行导入的定义文件,使用的是相对路径,最好不要使用任何斜线。

可以但不建议使用相对的../路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖。特别是,对于运行时解析过程选择“最近的”类路径根目录然后查找其父目录的URL,classpath:,不建议使用此引用(例如classpath:../services.xml)。类路径配置的更改可能导致选择其他错误的目录。

可以使用完全限定的资源位置来代替相对路径:例如file:C:/config/services.xml或classpath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常最好为这样的绝对位置保留一个间接寻址,例如,通过在运行时针对JVM系统属性解析的 ${..} 占位符。

1.2.3。使用容器

最灵活的变体是GenericApplicationContextReader代理结合使用:

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

您可以从不同的配置源中读取Bean定义,在相同的ApplicationContext混合和匹配此类Reader代理。

1.3。Bean总览

Spring IoC容器管理一个或多个bean。在容器本身内,这些bean定义表示为BeanDefinition 对象,其中包含(除其他信息外)以下元数据:

  • 包限定的类名:通常,定义了Bean的实际实现类。
  • Bean行为配置元素,用于声明Bean在容器中的行为(作用域,生命周期回调等)。
  • 对其他bean进行工作所需的引用。这些引用也称为协作者或依赖项。
  • 要在新创建的对象中设置的其他配置设置,例如,池的大小限制或要在管理连接池的bean中使用的连接数。

除了包含有关如何创建特定bean的信息的bean定义之外,ApplicationContext实现还允许注册在容器外部(由用户)创建的现有对象。这是通过getBeanFactory()方法访问ApplicationContext的BeanFactory来完成的,该方法返回DefaultListableBeanFactory实现。DefaultListableBeanFactory 通过registerSingleton(..)registerBeanDefinition(..)方法支持此注册。但是,典型的应用程序只能与通过常规bean定义元数据定义的bean一起使用。

Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他自省步骤中正确地推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时(与对工厂的实时访问同时)对新bean的注册,并且可能导致并发访问异常和bean容器中的状态不一致。

1.3.1。命名Beans

每个bean具有一个或多个标识符。这些标识符在承载Bean的容器内必须是唯一的。一个bean通常只有一个标识符。但是,如果需要多个,则可以将多余的别名视为别名。

在基于XML配置文件,您可以使用id属性,name属性,或两者来指定bean标识符。id属性使您可以精确指定一个ID。按照惯例,这些名称是字母数字(“myBean”,“someService”等),但它们也可以包含特殊字符。如果要为bean引入其他别名,也可以在name 属性中指定它们,并用逗号(,),分号(;)或空格分隔。作为历史记录,在Spring 3.1之前的版本中,id属性被定义为一种xsd:ID类型,该类型限制了可能的字符。从3.1开始,它被定义为xsd:string类型。请注意,Bean id唯一性仍由容器强制执行,尽管不再由XML解析器执行。

您不需要为bean 提供name或id。如果不提供显式提供 name或id,容器将为该bean生成一个唯一的名称。但是,如果要通过名称引用该bean,则通过使用ref元素或服务定位器样式查找,必须提供一个名称。不提供名称的动机与使用内部bean和自动装配合作者(Autowiring Collaborators)有关。

Bean命名约定是在命名bean时将标准Java约定用于实例字段名称。也就是说,bean名称以小写字母开头,并从那里用驼峰式大小写。

一致地命名Bean使您的配置更易于阅读和理解。另外,如果您使用Spring AOP,则在将建议应用于名称相关的一组bean时,它会很有帮助。

通过在类路径中进行组件扫描,Spring会按照前面描述的规则为未命名的组件生成Bean名称:从本质上讲,采用简单的类名称并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。这些规则与java.beans.Introspector.decapitalize(由Spring在此处使用)定义的规则相同。

在Bean定义之外别名Bean

在实际定义bean的地方指定所有别名并不总是足够的。有时需要为在别处定义的bean引入别名。

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

1.3.2。实例化Bean

可以通过以下Class两种方式之一使用该属性:

  • 通常,在容器本身通过反射性地调用其构造函数直接创建Bean的情况下,指定要构造的Bean类,这在某种程度上等同于使用new运算符的Java代码。
  • 要指定包含要被创建对象的static工厂方法的实际类,在不太常见的情况下,容器将在类上调用static工厂方法来创建Bean。从static工厂方法的调用返回的对象类型可以是同一类,也可以是完全不同的另一类。

内部类名称

如果要为static嵌套类配置Bean定义,则必须使用嵌套类的二进制名称。

例如,如果您在com.example包中有一个名为SomeThing的类,并且 SomeThing类有一个名为OtherThing的static嵌套类,则bean定义上的class属性值将为com.example.SomeThing$OtherThing

请注意,名称中使用了$字符以将嵌套的类名与外部类名分开。

用构造函数实例化

当通过构造方法创建一个bean时,所有普通类都可以被Spring使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码只需指定bean类就足够了。但是,根据您用于该特定bean的IoC的类型,您可能需要一个默认(空)构造函数。

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

用静态工厂方法实例化

在定义使用静态工厂方法创建的bean时,请使用class 属性指定包含static工厂方法的类,并使用命名factory-method为属性的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有可选参数,如后面所述),并返回一个活动对象,该对象随后将被视为已通过构造函数创建。这种bean定义的一种用法是用旧版代码调用static工厂。

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

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

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

使用实例工厂方法实例化

使用实例工厂方法进行实例化会从容器中调用现有bean的非静态方法来创建新bean。要使用此机制,请将class属性保留为空,并在factory-bean属性中指定当前(或父容器或祖先容器)中包含要创建对象的实例方法的Bean的名称。使用factory-method属性设置工厂方法本身的名称。

<!-- 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"/>
    
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

在Spring文档中,“factory bean”是指在Spring容器中配置并通过实例或静态工厂方法创建对象的bean 。相反, FactoryBean(请注意,大写字母)是指特定于Spring的 FactoryBean。

1.4。依赖关系

1.4.1。依赖注入

DI存在两个主要变体:基于构造函数的依赖注入和基于Setter的依赖注入。

基于构造函数的依赖注入

只能通过构造函数注入进行依赖项注入的类:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private 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...
}

构造函数参数解析匹配通过使用参数的类型进行。如果Bean定义的构造函数参数中不存在潜在的歧义,则在实例化Bean时,在Bean定义中定义构造函数参数的顺序就是将这些参数提供给适当的构造函数的顺序。

public class ExampleBean {

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

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

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

### 构造函数参数类型匹配 ###

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

### 构造函数参数索引 ###
#### 索引从0开始。

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

### 构造函数参数名称 ###

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

#### 配合@ConstructorProperties使用

public class ExampleBean {

    // Fields omitted

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

<constructor-arg name="years" value="7500000"/> 需要配合@ConstructorProperties注解使用,如果不使用注解,则Spring会去解析构造函数的参数名称,使用参数名后才能与name属性的值相匹配。

基于Setter的依赖注入

下面的示例显示只能通过使用纯setter注入来依赖注入的类。

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

您以BeanDefinition的形式配置依赖项,将其与PropertyEditor实例结合使用以将属性从一种格式转换为另一种格式。

可以在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。【已废弃,不起作用了,建议使用构造函数注入或实现org.springframework.beans.factory.InitializingBean

Spring团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在为null的必需的依赖项。

Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。

依赖关系解析过程

如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。使用setter注入可以避免构造函数注入。

1.4.2。依赖关系和详细配置

直接值(原语,字符串等)

## 方式1:<property/>标签

<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="masterkaoli"/>
</bean>

## 方式2:使用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 id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

## 配置java.util.Properties类型的属性

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="myBean1" class="examples.MyBean1">

        <property name="properties">
            <value>
                jdbc.driver.className=com.mysql.jdbc.Driver
                jdbc.url=jdbc:mysql://localhost:3306/mydb
            </value>
        </property>
    </bean>
</beans>

idref标签

idref标签是一个简单的防错方法,将容器另一个bean的id(一个字符串值,而不是引用)传给<constructor-arg/><property/> 标签。

<bean id="myBean1" class="examples.MyBean1">
    <property name="myBean2">
        <idref bean="myBean1"/>
    </property>
</bean>

<bean id="myBean2" class="examples.MyBean2"/>

@Data
public class MyBean1 {
    private String myBean2;

}

注意:这里MyBean1中的属性myBean2是String型,idref标签的功能与<value>类似,就是idref多了验证的功能,减少配置的书写错误机率。

对其他Bean的引用(协作者)

ref标签是<constructor-arg/><property/> 定义标签内部的最终标签。

ref标签的bean属性的值可以与目标Bean 的id属性相同,也可以与目标Bean 的name属性值之一相同。

通过parent属性指定目标Bean 将创建对当前容器的父容器中的Bean的引用。

内部Bean

<property/><constructor-arg/>标签内的<bean/>标签定义一个内部Bean。

内部bean定义不需要定义的ID或name。如果指定,则容器不使用该值作为标识符。容器还会忽略创建时的scope标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。不可能独立访问内部bean或将它们注入到协作bean中而不是封装在bean中。

作为一个特例,可以从自定义范围接收销毁回调,例如,对于单例bean中包含的一个请求范围的内部bean。内部bean实例的创建与其包含的bean绑定在一起,但是销毁回调使它可以参与请求范围的生命周期。这不是常见的情况。内部bean通常只共享其包含bean的作用域。

集合

<list/><set/><map/><props/>标签分别设置Java Collection类型ListSetMapProperties的属性和参数。

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

Map的key或value,set的值,也可以是以下任意元素:

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

集合合并

<bean id="parent" class="examples.MyBean1" abstract="true">
    <property name="properties">
        <props>
            <prop key="a">1</prop>
            <prop key="b">2</prop>
        </props>
    </property>
</bean>

<bean id="myBean1" parent="parent">
    <property name="properties">
        <props merge="true">
            <prop key="c">3</prop>
            <prop key="d">4</prop>
        </props>
    </property>
</bean>

这一合并行为同样适用于<list/><map/><set/> 集合类型。在<list/>元素的特定情况下,将维护与List集合类型关联的语义(即ordered值集合的概念)。父级的值位于所有子级列表的值之前。在MapSetProperties集合类型的情况下,没有顺序存在。因此,对于容器内部使用的关联Map,Set和Properties实现类型基础的集合类型,没有任何排序语义有效。

集合合并的局限性

不能合并不同的集合类型(例如Map和List)。

在父集合定义上指定merge属性是多余的,不会导致所需的合并。

空字符串值和空字符串值

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

exampleBean.setEmail("");
<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

exampleBean.setEmail(null);

带p命名空间的XML快捷方式

p命名空间允许您使用bean元素的属性(而不是嵌套 <property/>元素)来描述协作bean的属性值,或同时使用这两者。

p命名空间未在XSD文件中定义,仅存在于Spring的核心中。

<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"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"
        p:spouse-ref="jane"/>
</beans>

带c命名空间的XML快捷方式

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

复合属性名称

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

something具有fred属性,fred属性具有bob属性,bob具有sammy 特性,并且最终sammy属性被设置为值123。为了使其工作,something 的fred属性和fred的bob属性一定不能在bean构建之后为null。否则,将引发NullPointerException

1.4.3。使用depends-on

有时bean之间的依赖性不太直接。一个示例是何时需要触发类中的静态初始值设定项,例如用于数据库驱动程序注册。depends-on属性可以在初始化使用此元素的bean之前显式强制初始化一个或多个bean。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

depends-on属性既可以指定初始化时间依赖性,也可以仅在单例 bean 的情况下指定相应的销毁时间依赖性。与给定bean定义依赖关系的从属bean(带depends-on属性的Bean)首先被销毁,然后再销毁给定bean本身。这样,depends-on还可以控制销毁顺序。

1.4.4。延迟初始化Bean

默认情况下,ApplicationContext实现会在初始化过程中积极创建和配置所有 单例 bean。

延迟初始化的bean告诉IoC容器在首次请求时而不是在启动时创建一个bean实例。

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

当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,由于在启动时必须满足单例的依赖关系,所以ApplicationContext在启动时会创建延迟初始化的bean。延迟初始化的bean被注入到其他未延迟初始化的单例bean中。

还可以通过使用元素default-lazy-init上的属性在容器级别控制延迟初始化。

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

1.4.5。自动装配协作器

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

  • 自动装配可以大大减少指定属性或构造函数参数的需要。
  • 随着对象的发展,自动装配可以更新配置。

使用基于XML的配置元数据时,可以使用<bean/>元素的autowire属性为 bean定义指定自动装配模式。自动装配功能具有四种模式。您可以为每个bean指定自动装配,因此可以选择要自动装配的装配。

模式 说明
no (默认)无自动装配。Bean引用必须由ref元素定义。对于较大的部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName 按属性名称自动注入。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配并且包含一个master属性(即它具有一个 setMaster(..)方法),那么Spring将查找一个名为master的bean定义,并使用它来设置该属性。
byType 如果容器中恰好存在一个该属性类型的bean,则使该属性自动装配。如果存在多个错误,则会引发致命异常,这表明您不能对该bean 使用byType自动装配。如果没有匹配的bean,则什么都不会发生(未设置该属性)。
constructor 类似于byType但适用于构造函数参数。如果容器中不存在一个构造函数参数类型的bean,则将引发致命错误。

自动注入的局限性和缺点

  • propertyconstructor-arg设置的显式依赖项始终会覆盖自动装配。您不能自动注入简单的属性,例如基本类型,Strings,和Classes(以及此类简单属性的数组)。此限制是设计使然。
  • 自动装配不如显式装配精确。
  • 装配信息可能不适用于从Spring容器生成文档的工具。
  • 容器内的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组,集合或 Map实例,这不一定是问题。但是,对于需要单个值的依赖项,这种歧义必须解决。如果没有唯一的bean定义可用,则会引发异常。

解决方法:

  • 放弃自动装配,转而使用明确的装配。
  • 通过将autowire-candidate属性设置为false来避免自动装配bean定义。
  • 通过将<bean/>元素的primary属性设置为true,将单个Bean定义指定为主要候选者。
  • 通过基于注释的配置实现更细粒度的控制。

从自动装配中排除Bean

在每个bean的基础上,您可以从自动装配中排除一个bean。使用Spring的XML格式,将<bean/>元素的autowire-candidate属性设置为false。容器使特定的bean定义对自动装配基础结构不可用(包括诸如@Autowired的注释样式配置)。

autowire-candidate属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的Bean未标记为自动装配候选,该名称也可得到解析。因此,如果名称匹配,按名称自动装配仍然会注入Bean。

您还可以基于Bean名称的模式匹配来限制自动装配候选。顶级<beans/>元素在其default-autowire-candidates属性内接受一个或多个模式 。例如,要将自动装配候选状态限制为名称以Repository结尾的任何bean ,请提供值*Repository。要提供多种模式,请在以逗号分隔的列表中定义它们。Bean定义的autowire-candidate属性的显式值true或false始终优先。对于此类bean,模式匹配规则不适用。

这些技术对于您不希望通过自动装配将其注入其他bean的bean非常有用。这并不意味着排除的bean本身不能使用自动装配进行配置。相反,bean本身不是自动装配其他bean的候选对象。

1.4.6。方法注入

假设单例bean A需要使用非单例(原型)bean B,也许在对A的每个方法调用上都使用它。容器仅创建一次单例bean A,因此只有一次机会来设置属性。每次需要一个容器时,容器都无法为bean A提供一个新的bean B实例。方法注入是Spring IoC容器的一项高级功能,使您可以干净地处理此用例。

Lookup 方法注入

查找方法注入是容器覆盖容器管理的Bean上的方法并返回容器中另一个命名Bean的查找结果的能力。查找方法注入通常涉及原型bean。Spring框架通过使用从CGLIB库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。

  • 为了使此动态子类起作用,Spring Bean容器子类也不能为final,并且要覆盖的方法也不能为final
  • 对具有abstract方法的类进行单元测试需要您自己对该类进行子类化,并提供该abstract方法的存根实现。
  • 组件扫描也需要具体方法,这需要拾取具体的类。
  • 另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法在其上创建运行时生成的子类。

要注入的方法需要以下形式的签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法为abstract,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

只要需要新的myCommand bean 实例,被标识为commandManager的bean 就会调用自己的createCommand()方法。如果确实需要,您必须小心地将myCommand bean 部署为原型。如果是单例,则每次都返回相同的myCommand Bean 实例。

可以通过@Lookup注释声明一个查找方法:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

更惯用的是,您可以依赖于目标bean根据lookup方法的声明的返回类型来解析:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

请注意,通常应使用具体的存根实现声明此类带注释的查找方法,以使它们与Spring的组件扫描规则兼容(默认情况下抽象类会被忽略)。此限制不适用于显式注册或显式导入的Bean类。

访问范围不同的目标bean的另一种方法是ObjectFactory/Provider注入点。

您可能还会发现ServiceLocatorFactoryBean有用(在org.springframework.beans.factory.config包中)。

任意方法替换

与查找方法注入相比,方法注入的一种不太有用的形式是能够用另一种方法实现替换托管bean中的任意方法。

借助基于XML的配置元数据,您可以使用replaced-method元素将现有方法实现替换为已部署bean的另一个方法实现。

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

实现org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如以下示例所示:

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

您可以在<replaced-method/> 元素内使用一个或多个<arg-type/>元素来指示要覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要对参数签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配项 java.lang.String

java.lang.String
String
Str

因为参数的数量通常足以区分每个可能的选择,所以通过让您仅键入与参数类型匹配的最短字符串,此快捷方式可以节省很多输入。

原文地址:https://www.cnblogs.com/huangwenjie/p/12093265.html