文章目录
1.4 Bean
之间的依赖处理
典型的企业应用程序不包含单个对象(或Spring
术语中的bean
)。即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。下一部分将说明如何从定义多个独立的Bean
定义到实现对象协作以实现目标的完全实现的应用程序。
1.4.1 Bean
之间的依赖注入
依赖注入(DI
)是一个过程,通过该过程,对象仅通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(即,与它们一起工作的其他对象)。从工厂方法返回。然后,容器在创建bean
时注入那些依赖项。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来控制bean
自身依赖关系的实例化或位置的bean
本身的逆过程(因此称为Control Inversion
)。
DI
存在两个主要变体:基于构造函数的依赖注入和基于Setter的依赖注入
。
-
基于构造函数的依赖注入
基于构造函数的
DI
是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造Bean
几乎是等效的,并且本次讨论将构造函数和静态工厂方法的参数视为类似。以下示例显示了只能通过构造函数注入进行依赖项注入的类:public class TestBean { private TestBeanA testBeanA; public TestBean(TestBeanA testBeanA) { this.testBeanA = testBeanA; } public TestBeanA getTestBeanA() { return testBeanA; } }
- 构造函数参数解析
构造函数参数解析匹配通过使用
参数的类型进行
。如果Bean
定义的构造函数参数中不存在潜在的歧义,则在实例化Bean
时,在Bean
定义中定义构造函数参数的顺序就是将这些参数提供给适当的构造函数的顺序。考虑以下类别:public class TestBean { private TestBeanA testBeanA; private TestBeanB testBeanB; public TestBean(TestBeanA testBeanA,TestBeanB testBeanB) { this.testBeanA = testBeanA; this.testBeanB = testBeanB; } // get/set ... }
假设
TestBeanA
和TestBeanB
类没有通过继承关联,则不存在潜在的歧义。因此,以下配置可以正常运行,并且您无需在<constructor-arg />
元素中显式指定构造函数参数索引或类型(也就是说依赖的是引用类型,不是基本数据类型,这里String
也划到基本数据类型处理,其实不属于基本数据类型)。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="testBean" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"> <constructor-arg ref="testBeanA"/> <constructor-arg ref="testBeanB"/> </bean> <bean id="testBeanA" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanA"> </bean> <bean id="testBeanB" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanB"> </bean> </beans>
当引用另一个
bean
时,类型是已知的,并且可以发生匹配(与前面的示例一样)。当使用简单类型(例如<value> true </ value>
)时,Spring
无法确定值的类型(也就是说域是基本数据类型加String
类型),因此在没有帮助的情况下无法按类型进行匹配。考虑以下类别:public class TestBean { private int age; private String name; public TestBean(int age, String name) { this.age = age; this.name = name; } // get/set ... }
-
构造函数参数类型匹配
在上述情况下,如果通过使用
type
属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配。如下例所示:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="testBean" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"> <constructor-arg type="int" value="18"/> <constructor-arg type="java.lang.String" value="张三"/> </bean> </beans>
-
构造函数参数索引匹配
您可以使用
index
属性来显式指定构造函数参数的索引(索引是从0开始的),如以下示例所示:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="testBean" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"> <constructor-arg index="0" value="18"/> <constructor-arg index="1" value="张三"/> </bean> </beans>
除了解决多个简单值的歧义性之外,指定索引还可以解决歧义,其中构造函数具有两个相同类型的参数。
-
构造函数参数名称匹配
您还可以使用构造函数参数名称来消除歧义,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="testBean" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"> <constructor-arg name="age" value="18"/> <constructor-arg name="name" value="张三"/> </bean> </beans>
4.使用
@ConstructorProperties JDK
注解显式命名构造函数参数与getter
方法相对应,配合xml
通过构造参数名称进行初始化bean
(这个配置如上)。然后,样本类必须如下所示:public class TestBean { private int age; private String name; @ConstructorProperties({"age","name"}) public TestBean(int age, String name) { this.age = age; this.name = name; } //get/set ... }
-
基于Setter的依赖注入
通过使用无参数构造函数或无参数静态工厂方法实例化您的
bean
之后,容器通过在bean
上调用setter
方法来完成基于setter的DI
。 下面的示例显示只能通过使用纯
setter
注入来依赖注入的类。public class TestBean { private int age; private String name; public TestBean() { } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
ApplicationContext
支持它管理的bean
的基于构造函数和基于setter的DI
。在已经通过构造函数方法注入了某些依赖项之后,它还支持基于setter的DI
。您可以以BeanDefinition
的形式配置依赖项,并与PropertyEditor
实例结合使用,以将属性从一种格式转换为另一种格式。但是,大多数Spring
用户并不直接(即以编程方式)使用这些类,而是使用XML bean
定义,带注释的组件(即以@ Component,@ Controller等进行注释的类)或@Bean
方法来处理这些类。基于Java的@Configuration
类。然后将这些源在内部转换为BeanDefinition
实例,并用于加载整个Spring IoC
容器实例。使用构造函数注入依赖或基于setter注入依赖?由于可以混合使用基于构造函数的DI和基于setter的DI,因此将构造函数用于强制性依赖项并将setter方法或配置方法用于可选依赖性是一个很好的经验法则。注意,可以在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。Spring团队通常提倡构造函数注入,因为它使您可以将应用程序组件实现为不可变对象,并确保所需的依赖项不为null。此外,注入构造函数的组件始终以完全初始化的状态返回到客户端(调用)代码。附带说明一下,大量的构造函数自变量是一种不好的代码味,这表明该类可能承担了太多的职责,应进行重构以更好地解决关注点分离问题。Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。使用最适合特定班级的DI风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
-
依赖注入解析过程
容器执行
bean
依赖项解析,如下所示:-
使用描述所有
bean
的配置元数据创建和初始化ApplicationContext
。可以通过XML,Java
代码或注释指定配置元数据。 -
对于每个
bean
,其依赖关系都以属性,构造函数参数或static-factory
方法的参数的形式表示(如果使用它而不是普通的构造函数)。实际创建Bean时,会将这些依赖项提供给Bean
。 -
每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个
bean
的引用。 -
作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,
Spring
可以将以字符串格式提供的值转换为所有内置类型,例如int,long,String,boolean
等。 在创建容器时,
Spring
容器会验证每个bean
的配置。但是,在实际创建Bean
之前,不会设置Bean
属性本身。创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的Bean
。范围在Bean
范围中定义。否则,仅在请求时才创建Bean
。创建和分配bean
的依赖关系及其依赖关系(依此类推)时,创建bean
可能会导致创建一个bean
图。请注意,这些依赖项之间的分辨率不匹配可能会在后期出现,即在第一次创建受影响的bean
时。 循环依赖
如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。例如:A类通过构造函数注入需要B类的实例,而B类通过构造函数注入需要A类的实例。如果您为将类A和B相互注入而配置了
bean
,则Spring IoC
容器会在运行时检测到此循环引用,并抛出BeanCurrentlyInCreationException
。一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。或者,避免构造函数注入,而仅使用setter注入。换句话说,尽管不建议这样做,但是您可以使用setter注入配置循环依赖关系,与典型情况(没有循环依赖项)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在完全初始化之前被注入另一个bean(经典的“养鸡和鸡蛋”场景)。 通常,您可以信任
Spring
做正确的事。它在容器加载时检测配置问题,例如对不存在的Bean
的引用和循环依赖项。在实际创建Bean
时,Spring
设置属性并尽可能晚地解决依赖关系。这意味着,如果创建对象或其依赖项之一有问题,则正确加载了Spring的容器以后可以在您请求对象时生成异常-例如,由于缺少或无效,Bean
引发异常属性。这可能会延迟某些配置问题的可见性,这就是为什么默认情况下ApplicationContext
实现会预先实例化单例bean
的原因。在实际需要这些bean
之前需要花一些前期时间和内存来创建它们,您会在创建ApplicationContext
时发现配置问题,而不是稍后。您仍然可以覆盖此默认行为,以便单例bean
延迟初始化,而不是预先实例化。 如果不存在循环依赖关系,则在将一个或多个协作
Bean
注入到从属Bean
中时,每个协作Bean
都将被完全配置,然后再注入到从属Bean
中。这意味着,如果bean A依赖于bean B
,则Spring IoC
容器会在对bean A
调用setter
方法之前完全配置beanB
。换句话说,bean
被实例化(如果不是预先实例化的singleton
)。 ),设置其依赖项,并调用相关的生命周期方法(例如已配置的init
方法或InitializingBean
回调方法)。
-
1.4.2 依赖注入的详细配置
可以将bean
属性和构造函数参数定义为对其他托管bean
(协作者)的引用或内联定义的值。 Spring
的基于 XML
的配置元数据为此目的在其<property />
和<constructor-arg />
元素中支持子元素类型。
-
直接填写具体值(字符串,数字等)
<property />
元素的value
属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring
的转换服务用于将这些值从字符串转换为属性或参数的实际类型。以下示例显示了设置的各种值:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="testBean" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"> <property name="name" value="李四"/> <property name="age" value="18"/> </bean> </beans>
以下示例使用p-namespace进行更简洁的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" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="testBean" class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean" p:name="李四" p:age="18"> <!--<property name="name" value="李四"/>--> <!--<property name="age" value="18"/>--> </bean> </beans>
前面的
XML
更简洁。但是,除非在创建bean
定义时使用支持自动属性完成的IDE
(例如IntelliJ IDEA
或Eclipse
的Spring Tools
),否则错字是在运行时而不是设计时发现的。强烈建议您使用此类IDE
帮助。您还可以配置
java.util.Properties
实例,如下所示:<bean id="mappings" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <!-- typed as a java.util.Properties --> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property> </bean>
Spring
容器通过使用JavaBeans PropertyEditor
机制将<value />
元素内的文本转换为java.util.Properties
实例。这是一个很好的捷径,并且是Spring
团队偏爱使用嵌套的<value />
元素而不是value
属性样式的几个地方之一。-
idref
元素 使用idref
元素只是一种防错方法,可以将容器中另一个bean
的id
(字符串值-不是引用)传递给<constructor-arg />
或<property />
元素。以下示例显示了如何使用它:<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean"/> </property> </bean>
前面的bean定义片段(在运行时)与下面的片段完全等效:
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean"/> </bean>
第一种形式优于第二种形式,因为使用idref标记可使容器在部署时验证所引用的名为bean的实际存在。在第二个变体中,不对传递给客户端bean的targetName属性的值执行验证。拼写错误仅在实际实例化客户端bean时发现(最有可能导致致命的结果)。如果客户端Bean是原型Bean,则可能在部署容器很久之后才发现此错字和所产生的异常。
在4.0 Bean XSD中不再支持idref元素上的local属性,因为它不再提供常规Bean引用上的值。升级到4.0模式时,将现有的idref local引用更改为idref bean。
<idref />
元素带来价值的一个常见地方(至少在Spring 2.0之前的版本中)是在ProxyFactoryBean
bean
定义中配置AOP
拦截器。在指定拦截器名称时使用<idref />
元素可防止您拼写错误的拦截器ID。
-
-
对其他
Bean
的引用ref
ref元素是或定义元素内的最后一个元素。在这里,您将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。引用的bean是要设置其属性的bean的依赖关系,并且在设置属性之前根据需要对其进行初始化。 (如果协作者是单例bean,则它可能已经由容器初始化了。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是通过bean还是父属性指定另一个对象的ID或名称。
通过标记的bean属性指定目标bean是最通用的形式,并且允许创建对同一容器或父容器中任何bean的引用,而不管它是否在同一XML文件中。 bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值之一相同。下面的示例演示如何使用ref元素:
<ref bean="someBean"/>
通过parent属性指定目标Bean将创建对当前容器的父容器中的Bean的引用。父属性的值可以与目标Bean的id属性或目标Bean的名称属性中的值之一相同。目标Bean必须位于当前容器的父容器中。主要在具有容器层次结构并且要使用与父bean名称相同的代理将现有bean包装在父容器中时,才应使用此bean参考变量。以下清单对显示了如何使用parent属性:
<!-- in the parent context --> <bean id="accountService" class="com.something.SimpleAccountService"> <!-- insert dependencies as required as here --> </bean>
<!-- in the child (descendant) context --> <bean id="accountService" <!-- bean name is the same as the parent bean --> class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref parent="accountService"/> <!-- notice how we refer to the parent bean --> </property> <!-- insert other configuration and dependencies as required here --> </bean>
ref元素的local属性在4.0 Bean XSD中不再受支持,因为它不再提供常规Bean引用上的值。升级到4.0模式时,将现有的reflocal引用更改为ref bean。
-
内部
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定义不需要定义的ID或名称。如果指定,则容器不使用该值作为标识符。容器在创建时也将忽略作用域标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。不可能独立地访问内部bean或将其注入到协作bean中而不是封装到封闭bean中。
作为一个极端的例子,可以从自定义范围接收破坏回调,例如对于单例bean中包含的请求范围内的bean。内部bean实例的创建与其包含的bean绑定在一起,但是销毁回调使它可以参与请求范围的生命周期。这不是常见的情况。内部bean通常只共享其包含bean的作用域。
-
注入集合类数据
<list />,<set />,<map />和<props />
元素分别设置Java集合类型的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
-
集合合并
Spring容器还支持合并集合。应用程序开发人员可以定义父
<list />,<map />,<set />或<props />元素,并具有子<list />,<map />,<set />或<props />
元素。从父集合继承并覆盖值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素会覆盖父集合中指定的值。下面的示例演示了集合合并:
<beans> <bean id="parent" abstract="true" class="example.ComplexObject"> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.com</prop> <prop key="support">support@example.com</prop> </props> </property> </bean> <bean id="child" parent="parent"> <property name="adminEmails"> <!-- the merge is specified on the child collection definition --> <props merge="true"> <prop key="sales">sales@example.com</prop> <prop key="support">support@example.co.uk</prop> </props> </property> </bean> <beans>
注意子
bean
定义的adminEmails
属性的<props />
元素上使用merge = true
属性。当子bean由容器解析并实例化后,生成的实例具有adminEmails Properties
集合,其中包含将孩子的adminEmails
集合与父对象的adminEmails
集合合并的结果。以下清单显示了结果:administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk
子属性集合的值集继承了父
<props />
的所有属性元素,而支持值的子值覆盖了父集合中的值。此合并行为类似地适用于
<list />,<map />和<set />集合类型。在<list />
元素的特定情况下,将维护与List集合类型关联的语义(即,值的有序集合的概念)。父级的值先于子级列表的所有值。对于“map”,“Set”和“Properties”
集合类型,不存在任何排序。因此,对于容器内部使用的关联Map,Set和Properties
实现类型基础的集合类型,没有任何排序语义有效。 -
集合合并的局限性
您不能合并不同的集合类型(例如地图和列表)。如果尝试这样做,则会引发适当的Exception。必须在下面的继承的子定义中指定merge属性。在父集合定义上指定merge属性是多余的,不会导致所需的合并。
-
强类型集合
随着Java 5中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。如果使用Spring将强类型的Collection依赖注入到Bean中,则可以利用Spring的类型转换支持,以便在将强类型的Collection实例的元素添加到Bean中之前,先将其转换为适当的类型。采集。以下Java类和bean定义显示了如何执行此操作:
public class SomeClass { private Map<String, Float> accounts; public void setAccounts(Map<String, Float> accounts) { this.accounts = accounts; } }
<beans> <bean id="something" class="x.y.SomeClass"> <property name="accounts"> <map> <entry key="one" value="9.99"/> <entry key="two" value="2.75"/> <entry key="six" value="3.99"/> </map> </property> </bean> </beans>
当准备注入某物bean的accounts属性时,可以通过反射获得有关强类型Map <String,Float>的元素类型的泛型信息。因此,Spring的类型转换基础结构将各种值元素识别为Float类型,并将字符串值(9.99、2.75和3.99)转换为实际的Float类型。
-
-
注入
NULL
和空的字符串Spring将属性等的空参数视为空String。以下基于XML的配置元数据代码段将email属性设置为空的String值(“”)。
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
前面的示例等效于以下Java代码:
exampleBean.setEmail("");
<null />
元素处理空值。以下清单显示了一个示例:<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
The preceding configuration is equivalent to the following Java code:
exampleBean.setEmail(null);
-
使用
P:
的命名空间代替properties
标签通过p-namespace,您可以使用bean元素的属性(而不是嵌套的元素)来描述协作bean的属性值,或同时使用这两者。
Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML Schema定义。但是,p命名空间未在XSD文件中定义,仅存在于Spring的核心中。
以下示例显示了两个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>
该示例在p命名空间中的bean定义中显示了一个名为email的属性。这告诉Spring包含一个属性声明。如前所述,p名称空间没有架构定义,因此您可以将属性的名称设置为属性名称。
下一个示例包括另外两个bean定义,它们都引用了另一个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:spouse-ref="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean> </beans>
此示例不仅包括使用p-namespace的属性值,而且还使用特殊格式来声明属性引用。第一个bean定义使用<property name =“ spouse” ref =“ jane” />创建从bean john到bean jane的引用,而第二个bean定义使用p:spouse-ref =“ jane”作为属性完全一样的东西。在这种情况下,配偶是属性名称,而-ref部分指示这不是一个直接值,而是对另一个bean的引用。
p命名空间不如标准XML格式灵活。例如,用于声明属性引用的格式与以Ref结尾的属性发生冲突,而标准XML格式则没有。建议您仔细选择方法,并与团队成员进行交流,以避免同时使用这三种方法生成XML文档。
-
使用
c:
的命名空间代替construct-arg
标签与带有p命名空间的XML快捷方式类似,Spring 3.1中引入的c命名空间允许使用内联属性来配置构造函数参数,而不是嵌套的builder-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:命名空间使用与p:相同的约定(bean引用为尾随-ref)以按名称设置构造函数参数。同样,即使未在XSD模式中定义它(也存在于Spring内核中),也需要在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允许)。相应的索引符号也可用于元素,但不常用,因为在那里的普通声明顺序就足够了。
实际上,构造函数解析机制在匹配参数方面非常有效,因此,除非您确实需要,否则建议在整个配置过程中使用名称表示法。
-
属性是引用
Bean
给引用Bean
中的属性赋值设置bean属性时,可以使用复合属性名称或嵌套属性名称,只要路径中除最终属性名称以外的所有组件都不为空即可。考虑以下bean定义:
<bean id="something" class="things.ThingOne"> <property name="fred.bob.sammy" value="123" /> </bean>
Something bean具有fred属性,该属性具有bob属性,该属性具有sammy属性,并且最终的sammy属性被设置为123的值。为了使其正常工作,something的fred属性和bob属性构造bean之后,fred不能为null。否则,将引发NullPointerException。
1.4.3 使用depends-on
如果一个bean是另一个bean的依赖项,则通常意味着将一个bean设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的元素来完成此操作。但是,有时bean之间的依赖性不太直接。一个示例是何时需要触发类中的静态初始值设定项,例如用于数据库驱动程序注册。依赖属性可以显式地强制初始化一个或多个使用该元素的bean之前的bean。下面的示例使用depends-on属性来表示对单个bean的依赖关系:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="testBean"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"
depends-on="testBeanA">
<property name="testBeanA" ref="testBeanA"/>
</bean>
<bean id="testBeanA"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanA"/>
</beans>
要表达对多个bean的依赖关系,请提供一个bean名称列表作为Depends-on属性的值(逗号,空格和分号是有效的分隔符):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="testBean"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"
depends-on="testBeanA,testBeanB">
<property name="testBeanA" ref="testBeanA"/>
</bean>
<bean id="testBeanA"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanA"/>
<bean id="testBeanB"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanB"/>
</beans>
Depends-on属性既可以指定初始化时间依赖性,也可以仅在单例bean的情况下指定相应的销毁时间依赖性。与给定bean定义依赖关系的从属bean首先被销毁,然后再销毁给定bean本身。因此,依赖也可以控制关闭顺序。
1.4.4 设置Bean
的懒加载
默认情况下,作为初始化过程的一部分,ApplicationContext实现会急于创建和配置所有单例bean。通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。如果您不希望这种行为,则可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在首次请求时(而不是在启动时)创建一个bean实例。
在XML中,此行为由元素上的lazy-init属性控制,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="testBean"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"
depends-on="testBeanA">
<property name="testBeanA" ref="testBeanA"/>
</bean>
<bean id="testBeanA"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanA"
lazy-init="true"/>
</beans>
当前面的配置被ApplicationContext占用时,在ApplicationContext启动时不会急于预先实例化懒惰的testBean,而是会急切地预先实例化testBeanA。
但是,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,ApplicationContext会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖关系。延迟初始化的bean被注入到其他未延迟初始化的单例bean中。
您还可以通过使用元素上的default-lazy-init属性在容器级别控制延迟初始化,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- .... -->
</beans>
1.4.5 Autowiring
自动注入的配置
Spring容器可以自动装配协作bean之间的关系。您可以通过检查ApplicationContext的内容,让Spring为您的bean自动解决协作者(其他bean)。自动装配具有以下优点:
1. 自动装配可以大大减少指定属性或构造函数参数的需要。
2. 随着对象的发展,自动装配可以更新配置。例如,如果需要将依赖项添加到类中,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中尤其有用,而不必担心在代码库变得更稳定时切换到显式接线的选择。
使用基于XML的配置元数据时,可以使用元素的autowire属性为bean定义指定自动装配模式。自动装配功能具有四种模式。您可以为每个bean指定自动装配,因此可以选择要自动装配的装配。下表描述了四种自动装配模式:
自动装配模式 | 解释 |
---|---|
no | (默认)无自动装配。 Bean引用必须由ref元素定义。对于较大的部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 按属性名称自动布线。 Spring查找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配,并且包含一个master属性(即,它具有setMaster(…)方法),那么Spring将查找一个名为master的bean定义并使用它来设置该属性。 |
byType | 如果容器中恰好存在一个属性类型的bean,则使该属性自动连接。如果存在多个,则将引发致命异常,这表明您可能无法对该bean使用byType自动装配。如果没有匹配的bean,则什么也不会发生(未设置该属性)。 |
constructor | 与byType类似,但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个bean,则将引发致命错误。 |
使用byType或构造函数自动装配模式,您可以连接数组和类型化的集合。在这种情况下,将提供容器中与预期类型匹配的所有自动装配候选,以满足相关性。如果期望的键类型为String,则可以自动装配强类型Map实例。自动关联的Map实例的值包括与期望类型匹配的所有bean实例,并且Map实例的键包含相应的bean名称。
-
自动装配的局限性和缺点
当在项目中一致使用自动装配时,自动装配效果最佳。如果通常不使用自动装配,则可能使开发人员仅使用自动装配来连接一个或两个bean定义感到困惑。
考虑自动装配的局限性和缺点:
- 属性和构造器参数设置中的显式依赖关系始终会覆盖自动装配。您不能自动连接简单属性,例如基元,字符串和类(以及此类简单属性的数组)。此限制是设计使然。
- 自动装配不如显式接线精确。尽管如前所述,Spring还是谨慎避免在可能产生意外结果的歧义情况下进行猜测。 Spring管理的对象之间的关系不再明确记录。
- 接线信息可能不适用于可能从Spring容器生成文档的工具。
- 容器内的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组,集合或Map实例,这不一定是问题。但是,对于需要单个值的依赖项,不会任意解决此歧义。如果没有唯一的bean定义可用,则会引发异常。
在后一种情况下,您有几种选择:
- 放弃自动布线,转而使用明确的布线
- 通过将其bean的autowire-candidate属性设置为false,避免自动装配bean定义
- 通过将其元素的primary属性设置为true,将单个bean定义指定为主要候选对象。
- 如基于注释的容器配置中所述,通过基于注释的配置实现更细粒度的控件。
-
从自动装配中排除Bean
在每个bean的基础上,您可以从自动装配中排除一个bean。使用Spring的XML格式,将元素的autowire-candidate属性设置为false。容器使该特定的bean定义对自动装配基础结构不可用(包括注释样式配置,如@Autowired)。
autowire-candidate属性设计为仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使未将指定的Bean标记为自动装配候选,该名称也可得到解析。因此,如果名称匹配,按名称自动装配仍会注入Bean。
您还可以基于与Bean名称的模式匹配来限制自动装配候选。顶级元素在其default-autowire-candidates属性中接受一个或多个模式。例如,要将自动装配候选状态限制为名称以Repository结尾的任何bean,请提供* Repository值。要提供多种模式,请在以逗号分隔的列表中定义它们。 Bean定义的autowire-candidate属性的显式值true或false始终优先。对于此类bean,模式匹配规则不适用。
这些技术对于您不希望通过自动装配将其注入其他bean的bean非常有用。这并不意味着排除的bean本身不能使用自动装配进行配置。相反,bean本身不是自动装配其他bean的候选对象。
1.4.6 方法注入
在大多数应用场景中,容器中的大多数bean是单例的。当一个单例Bean需要与另一个单例Bean协作或一个非单例Bean需要与另一个非单例Bean协作时,通常可以通过将一个Bean定义为另一个Bean的属性来处理依赖关系。当bean的生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,也许在A的每个方法调用上都使用。容器仅创建一次单例bean A,因此只有一次机会来设置属性。每次需要一个容器时,容器都无法为bean A提供一个新的bean B实例。
解决方案是放弃某些控制反转。您可以通过实现ApplicationContextAware接口,并通过对容器进行getBean(“ B”)调用来使bean A知道该容器,以便每次bean A需要它时都请求一个(通常是新的)bean B实例。以下示例显示了此方法:
public class TestBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void testMethodIn(){
TestBeanA testBeanA = createdTestBeanA();
System.out.println(testBeanA.getName());
}
public TestBeanA createdTestBeanA(){
return this.applicationContext.getBean("testBeanA",TestBeanA.class);
}
}
前面的内容是不理想的,因为业务代码知道并耦合到Spring框架。方法注入是Spring IoC容器的一项高级功能,使您可以干净地处理此用例。
-
Lookup Method 注入
查找方法注入是容器重写容器管理的Bean上的方法并返回容器中另一个命名Bean的查找结果的能力。查找通常涉及原型bean,Spring框架通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类,从而实现此方法注入。
- 为了使此动态子类起作用,Spring Bean容器子类的类也不能是final,而要覆盖的方法也不能是final。
- 对具有抽象方法的类进行单元测试需要您自己对该类进行子类化,并提供该抽象方法的存根实现。
- 组件扫描也需要具体的方法,这需要具体的类。
- 另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的动态子类。
对于前面的代码片段中的TestBean类,Spring容器动态地覆盖createdTestBeanA()方法的实现。如重做的示例所示,TestBean类没有任何Spring依赖项:
public abstract class TestBean { public void testMethodIn(){ TestBeanA testBeanA = createdTestBeanA(); System.out.println(testBeanA.getName()); } protected abstract TestBeanA createdTestBeanA(); }
在包含要注入的方法的客户端类(在本例中为TestBean)中,要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果该方法是抽象的,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="testBean"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"
depends-on="testBeanA" >
<lookup-method name="createdTestBeanA" bean="testBeanA"/>
</bean>
<bean id="testBeanA"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanA"
lazy-init="true" scope="prototype"/>
<bean id="testBeanB"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanB"
lazy-init="true"/>
</beans>
每当需要新的testBeanA bean实例时,标识为testBean的bean就会调用其自己的createTestBeanA()方法。如果确实需要将testBeanA bean部署为原型,则必须小心。如果是单例,则每次都返回testBeanA bean的相同实例。
另外,在基于注释的组件模型中,您可以通过@Lookup注释声明一个查找方法,如以下示例所示:
public abstract class TestBean {
public void testMethodIn(){
TestBeanA testBeanA = createdTestBeanA();
System.out.println(testBeanA.getName());
}
@Lookup("testBeanA")
protected abstract TestBeanA createdTestBeanA();
}
或者,更惯用的是,您可以依赖于目标bean根据lookup方法的声明的返回类型来解析:
public abstract class TestBean {
public void testMethodIn(){
TestBeanA testBeanA = createdTestBeanA();
System.out.println(testBeanA.getName());
}
@Lookup
protected abstract TestBeanA createdTestBeanA();
}
请注意,通常应使用具体的存根实现声明此类带注释的查找方法,以使其与Spring的组件扫描规则(默认情况下抽象类会被忽略)兼容。此限制不适用于显式注册或显式导入的Bean类。
-
任意方法替换
与查找方法注入相比,方法注入的一种不太有用的形式是能够用另一种方法实现替换托管bean中的任意方法。
借助基于XML的配置元数据,您可以使用replaced-method元素将现有的方法实现替换为已部署的Bean。考虑以下类,该类具有一个我们要覆盖的名为testMethodIn的方法:
public class TestBean { public TestBeanA testMethodIn(String string){ return null; } public void test(){ System.out.println(testMethodIn("abc").getName()); } }
实现org.springframework.beans.factory.support.MethodReplacer接口的类提供了新的方法定义,如以下示例所示:
public class TestBeanA implements MethodReplacer {
private String name = "testBeanA";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
String arg = (String) args[0];
return this;
}
}
用于部署原始类并指定方法覆盖的Bean定义类似于以下示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="testBean"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean"
depends-on="testBeanA" >
<replaced-method name="testMethodIn" replacer="testBeanA"/>
</bean>
<bean id="testBeanA"
class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBeanA"
lazy-init="true" scope="prototype"/>
</beans>
您可以在元素内使用一个或多个元素来指示要覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要对参数签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有都匹配java.lang.String:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,所以通过让您仅键入与参数类型匹配的最短字符串,此快捷方式可以节省很多输入。
参考文献
【https://docs.spring.io/spring-framework/docs/current/reference/html/core.html】【1.4. Dependencies】