Spring控制反转(IoC)容器
Spring IoC Container 和 Bean 介绍
官方文档对控制反转的解释:
- IoC 也称为依赖注入
(DI)。这是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(即,它们使用的其他对象)
. 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean
本身的逆过程(因此得名,控制反转),通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖项的实例化或位置。
配置元数据
- 在没学习spring之前,创建一个对象会用new,而现在spring会创建对象,在spring中的beans来创建对象, bean
定义对应于构成应用程序的实际对象,Spring 配置包含至少一个并且通常不止一个容器必须管理的 bean 定义。
以下示例是官方文档显示的基于 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="...">
<!-- 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>
注意:
- 该id属性是一个字符串,用于标识单个 bean 定义。
- 该class属性定义 bean 的类型并使用完全限定的类名。
- 该id属性的值是指协作对象。
测试用例:
- 提供给
ApplicationContext
构造函数的一个或多个位置路径是资源字符串,允许容器从各种外部资源(例如本地文件系统、Java 等)加载配置元数据CLASSPATH
。
官方文档:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
Hello类
package pri.rong.pojo;
public class Hello {
private String str;
public Hello() {
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
beans.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
使用spring来创建对象,在spring这些都称为Bean
类型 变量值 = new 类型();
Hello hello = new Hello();
id = 变量名
class =new的对象;
property 相当于给对象中的属性设置一个值
以上过程就是控制反转
现在,我们彻底不用再去程序中修改了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IoC,一句话搞定
对象由spring创建、管理、装配。
-->
<!-- <bean id="hello" class="pri.rong.pojo.Hello">-->
<!--<!– property:属性–>-->
<!-- <property name="str" value="Spring"/>-->
<!-- </bean>-->
<bean id="hello" class="pri.rong.pojo.Hello" p:str="spring"/>
</beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pri.rong.pojo.Hello;
public class MyTest {
public static void main(String[] args) {
//获取spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象都在spring中管理了,我们使用直接去里面取出来即可
//将object对象强转为我们创建的对象Hello
Hello hello = (Hello) context.getBean("hello");
// context.getBean("123");
System.out.println(hello.toString());
}
}
命名bean
- 每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean
通常只有一个标识符。但是,如果它需要多个,则可以将多余的视为别名。在基于 XML 的配置元数据中,您可以使用id属性、name属性或两者来指定 bean 标识符。该id属性允许您指定一个 ID。通常,这些名称是字母数字(‘myBean’、‘someService’ 等),但它们也可以包含特殊字符。
Bean 命名约定
- 约定是在命名 bean 时对实例字段名称使用标准 Java 约定。也就是说,bean 名称以小写字母开头,并从那里开始使用驼峰式大小写。此类名称的示例包括accountManager、 accountService、userDao、loginController等等。
- 始终如一地命名 bean 使您的配置更易于阅读和理解。此外,如果您使用 Spring AOP,则在将建议应用于一组按名称相关的 bean 时会很有帮助。
在 Bean 定义之外给 Bean 取别名
- 在 bean 定义本身中,您可以通过使用id属性指定的最多一个名称和属性中任意数量的其他名称的组合,为 bean
提供多个名称name。这些名称可以是同一个 bean
的等效别名,并且在某些情况下很有用,例如让应用程序中的每个组件通过使用特定于该组件本身的 bean 名称来引用公共依赖项。 - 然而,在实际定义 bean 的地方指定所有别名并不总是足够的。有时需要为在别处定义的 bean
引入别名。这在大型系统中很常见,其中配置在每个子系统之间拆分,每个子系统都有自己的一组对象定义。在基于 XML
的配置元数据中,您可以使用元素来完成此操作。以下示例显示了如何执行此操作:
<alias name="fromName" alias="toName"/>
实例化 Bean
- bean 定义本质上是创建一个或多个对象的方法。当被询问时,容器会查看命名 bean 的配方,并使用该 bean
定义封装的配置元数据来创建(或获取)实际对象。 - 如果您使用基于 XML 的配置元数据,请class在元素的属性中指定要实例化的对象的类型(或类)。这个
class属性(在内部,它是Class一个BeanDefinition 实例的属性)通常是强制性的。
可以通过以下Class两种方式之一使用该属性:
- 通常,在容器本身通过反射调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,有点等同于带有new运算符的Java
代码。 - 指定包含static被调用以创建对象的工厂方法的实际类,在不太常见的情况下,容器调用 static类上的工厂方法来创建
bean。调用static工厂方法返回的对象类型可能是同一个类,也可能完全是另一个类。
使用构造函数实例化
- 当通过构造函数方法创建 bean 时,所有普通类都可以被 Spring 使用并与 Spring
兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 bean 类就足够了。但是,根据您对该特定 bean
使用的 IoC 类型,您可能需要一个默认(空)构造函数。 - Spring IoC 容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBean。大多数 Spring 用户更喜欢实际的
JavaBeans,它只有一个默认(无参数)构造函数和适当的 setter 和 getter,它们以容器中的属性为模型。
使用基于 XML 的配置元数据,可以按如下方式指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
依赖关系
依赖注入
- 依赖注入 (DI)
是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或对象实例在构造或构造后设置的属性来定义它们的依赖项(即,与它们一起工作的其他对象)。从工厂方法返回。然后容器在创建
bean 时注入这些依赖项。这个过程基本上是 bean
本身的逆过程(因此得名,控制反转),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。 - DI
原则使代码更清晰,当对象提供依赖关系时,解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。 - DI 存在两种主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
- 基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数代表一个依赖项。调用static带有特定参数的工厂方法来构造
bean 几乎是等效的,本讨论将static类似地处理构造函数和工厂方法的参数。以下示例显示了一个只能使用构造函数注入进行依赖注入的类:
测试用例
user类
package pri.rong.pojo;
public class User {
private String name;
// public User() {
// System.out.println("User的无参构造。");
//
// }
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name=" + name);
}
}
<?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="user" class="pri.rong.pojo.User" name="user2">
<!-- <property name="name" value="张杰"/>-->
<constructor-arg name="name" value="张杰"/>
</bean>
</beans>
构造函数参数类型匹配
- 在上述场景中,如果您通过type属性显式指定构造函数参数的类型,容器可以使用简单类型的类型匹配,如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
构造函数参数索引
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
注意:
该索引是从 0 开始的。
构造函数参数名称
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
基于 Setter 的依赖注入
- 基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数static工厂方法来实例化bean 之后调用 bean 上的
setter 方法来完成的。 - 将ApplicationContext支持基于构造函数和的Setter
DI为它所管理的豆类。在已经通过构造函数方法注入了一些依赖项之后,它还支持基于 setter 的 DI。
基于构造函数还是基于 setter 的 DI?
-
由于可以混合使用基于构造函数和基于 setter 的 DI,因此最好将构造函数用于强制依赖项,将 setter
方法或配置方法用于可选依赖项。 -
Spring 团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保所需的依赖项不是null.
此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为旁注,大量的构造函数参数是一种糟糕的代码味道,这意味着该类可能有太多的责任,应该重构以更好地解决适当的关注点分离问题。 -
Setter 注入应该主要仅用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter
注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。 -
使用对特定类最有意义的 DI 样式。有时,在处理您没有源的第三方类时,选择是为您做出的。例如,如果第三方类不公开任何 setter
方法,则构造函数注入可能是 DI 的唯一可用形式。
依赖注入的例子
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。Spring XML 配置文件的一小部分指定了一些 bean 定义,如下所示:
<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"/>
详细依赖和配置
直接值(原语、字符串等)
在value所述的属性元素指定属性或构造器参数的人类可读的字符串表示。Spring 的 转换服务用于将这些值从 a 转换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>
对于其他的,,
官方文档分别用以下示例显示了如何使用它们:
<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>
带有 p 命名空间的 XML 快捷方式
官方文档:
<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 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与以 结尾的属性冲突Ref,而标准 XML
格式则不然。我们建议您谨慎选择您的方法并将其传达给您的团队成员,以避免同时使用所有三种方法生成 XML 文档。
带有 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:命名空间使用相同的约定作为p:一个(尾部-ref的bean引用),供他们的名字设置构造函数的参数。同样,它需要在 XML
文件中声明,即使它没有在 XSD 模式中定义(它存在于 Spring 核心中)。