Spring对IoC的实现
IoC 控制反转 与 DI 依赖注入
IoC:控制反转,是一种思想。为了降低程序耦合度,提⾼程序扩展⼒,达到OCP原则,达到DIP原则。
控制反转,反转的是什么?
- 对象的创建权:对象的创建交由第三方容器负责
- 对象和对象之间关系的维护权:对象间关系的维护交由第三方容器负责。
DI:Dependence Injection,依赖注入。是 IoC 思想的一种实现方法。
依赖:指的是对象和对象之间的关联关系。
注⼊:指的是⼀种数据传递行为,通过注⼊⾏为来让对象和对象产生关系。
依赖注入常见的实现方式有两种:
- set 注入
- 构造注入
Spring通过依赖注入的方式来完成Bean管理。
Bean管理(Bean对象之间关系的维护):Bean对象的创建,以及Bean对象中属性的赋值。
set 注入
set注⼊,基于set⽅法实现的,底层会通过反射机制调⽤属性对应的set⽅法然后给属性赋值。
这种⽅式要求属性必须对外提供set⽅法。
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<!--使用 property 标签指定使用set注入。使用 ref 引用容器内部的 bean 作为值注入-->
<property name="userDao" ref="userDaoBean"/>
</bean>- 依据命名规范,调用相应的 setXxx 方法实现属性的注入。
解析:
- 通过property标签获取到属性名:userDao
- 通过属性名推断出set⽅法名:setUserDao
- 通过反射机制调⽤setUserDao()⽅法给属性赋值
- property标签的name是属性名。
- set 注入基于 setter,与属性名无关,跟方法名有关。
对于 <property> 标签来说,ref也可以采用标签的方式,但使⽤ref属性是多数的:
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<ref bean="userDaoBean"/>
</property>
</bean>构造注入
核⼼原理:通过调⽤构造⽅法来给属性赋值。
必须提供相应的构造方法。
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--index="0"表示构造⽅法的第⼀个参数,将orderDaoBean对象传递给构造⽅法的第⼀个参
数。-->
<constructor-arg index="0" ref="orderDaoBean"/>
</bean>- index 指明构造参数的位置,使用 ref 引用 bean 作为参数值。
也可以使用构造方法形式参数的名字来指定:
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--index="0"表示构造⽅法的第⼀个参数,将orderDaoBean对象传递给构造⽅法的第⼀个参
数。-->
<constructor-arg name="orderDao" ref="orderDaoBean"/>
</bean>也可以不指定下标和参数名,可以类型自动推断,查找容器内的 bean 匹配类型自动赋值,<constructor-arg> 顺序无所谓。
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--顺序已经和构造⽅法的参数顺序不同了-->
<constructor-arg ref="userDaoBean"/>
<constructor-arg ref="orderDaoBean"/>
</bean>注入参数详解
set 注入 基于 <property>,构造注入基于 <constructor-arg>,二者出标签名外基本相同,属性、子标签有着高度的相似性。因此,以 set 注入为例,详解注入的参数、子标签、注入类型,使用时在构造注入中灵活变通。
注入外部 bean
外部Bean的特点:bean定义到外⾯,在property标签中使⽤ref属性进⾏注⼊。通常这种⽅式是常⽤。
<property> 标签的 ref 是要注⼊的bean对象的id。ref 引用外部 bean 作为注入值。
使用 ref 进行注入,记为 引用注入。
装配:创建系统组件之间关联的动作。
<!--1. 使用 ref 属性(常用)-->
<property name="userDao" ref="userDaoBean"/>
<!--2. 使用 <ref> 标签-->
<property name="userDao">
<ref bean="userDaoBean"/>
</property>注入内部 bean
内部Bean:在bean标签中嵌套bean标签。
在 <property> 标签内部使用 <bean> 标签声明一个bean作为注入的值。
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<bean class="com.powernode.spring6.dao.UserDao"/>
</property>
</bean>注入简单类型
与 MyBatis 类似,Spring 实现了对一些类的解析,允许在 xml 文件中以直接给出这些类型的赋值表达,由Spring自行解析。这些类统称为简单类型。
将对象视为简单类型使用 value 属性注入,记为 简单注入。
分析源码,Spring 简单类型包括:
- 基本数据类型
- 基本数据类型对应的包装类
- String或其他的CharSequence⼦类
- Number⼦类
- Date⼦类
- Enum⼦类
- URI
- URL
- Temporal⼦类
- Locale
- Class
- 以上简单值类型对应的数组类型。
简单类型的注入,经典是给数据源的属性赋值,把数据源交给Spring管理,由Spring实现注入:
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
// ...
}String是简单类型,使用 value 属性注入值即可:
<bean id="dataSource" class="com.powernode.spring6.beans.MyDataSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>简单类型中的注意点:
大部分简单类型的 value 都不需要特别注意,能看懂基本不会错,但有些简单类型有着格式或有效性的判定:
简单方式注入 Date 类型,需要为Date对象toString()方法执行的结果,长这样:Fri Sep 30 15:26:38 CST 2022。
简单方式注入 Date 格式复杂,鸡肋,通常以 引用方式 注入 Date。
简单方式注入 URL 类型时,会检查url是否有效,无效会报错。
级联属性的注入
与 MyBatis 类似,级联属性以 . 表示,底层向下查找 setter进行赋值。
<bean id="clazzBean" class="com.powernode.spring6.beans.Clazz"/>
<bean id="student" class="com.powernode.spring6.beans.Student">
<property name="name" value="张三"/>
<!--要点1:以下两⾏配置的顺序不能颠倒-->
<property name="clazz" ref="clazzBean"/>
<!--要点2:clazz属性必须有getter⽅法-->
<property name="clazz.name" value="⾼三⼀班"/>
</bean>数组的注入
使用 <array> 标签指明注入的是一个数组,使用 <value> <ref> 子标签完成值表示。
- 如果数组中是简单类型,使⽤value标签。
- 如果数组中是⾮简单类型,使⽤ref标签。
简单类型数组的注入
public class Person {
private String[] favariteFoods;
// ...
}使用 <array>.<value> 标签完成数组的注入,依据标签顺序进行注入:
<bean id="person" class="com.powernode.spring6.beans.Person">
<property name="favariteFoods">
<array>
<value>鸡排</value>
<value>汉堡</value>
<value>鹅肝</value>
</array>
</property>
</bean>
<!-- favariteFoods = {"鸡排","汉堡","鹅肝"} -->非简单类型数组的注入
public class Goods {
private String name;
// ...
}
public class Order {
// ⼀个订单中有多个商品
private Goods[] goods;
// ...
}使用 <array>.<ref> 标签完成数组的注入,依据标签顺序进行注入:
<bean id="goods1" class="com.powernode.spring6.beans.Goods">
<property name="name" value="⻄⽠"/>
</bean>
<bean id="goods2" class="com.powernode.spring6.beans.Goods">
<property name="name" value="苹果"/>
</bean>
<bean id="order" class="com.powernode.spring6.beans.Order">
<!--注入数组-->
<property name="goods">
<array>
<!--这⾥使⽤ref标签即可-->
<ref bean="goods1"/>
<ref bean="goods2"/>
</array>
</property>
</bean>注入 List、Set
注入数组使用 <array> 标签,还有 <list> <set> 标签用来注入 List 或 Set。简单类型用 <value> ,非简单类型用 <ref>。和上述同。
<bean id="peopleBean" class="com.powernode.spring6.beans.People">
<property name="names">
<!--注入 List -->
<list>
<value>铁锤</value>
<value>张三</value>
<value>张三</value>
<value>张三</value>
<value>狼</value>
</list>
</property>
</bean>
<bean id="peopleBean" class="com.powernode.spring6.beans.People">
<property name="phones">
<set>
<!--注入 Set -->
<value>110</value>
<value>110</value>
<value>120</value>
<value>120</value>
<value>119</value>
<value>119</value>
</set>
</property>
</bean>注入 Map
注入 Map 使用 <map> 标签表示,但还需要指定 key:
<bean id="peopleBean" class="com.powernode.spring6.beans.People">
<property name="addrs">.
<!--注入 Map -->
<map>
<!--如果key不是简单类型,使⽤ key-ref 属性-->
<!--如果value不是简单类型,使⽤ value-ref 属性-->
<entry key="1" value="北京⼤兴区"/>
<entry key="2" value="上海浦东区"/>
<entry key="3" value="深圳宝安区"/>
</map>
</property>
</bean>- key、value 如有不是简单类型的,使用带
-ref的属性即可。
注入 Properties
java.util.Properties继承java.util.Hashtable,所以Properties也是⼀个Map集合。不同之处,Properties 的 key 和 value 都是 String 类型,因此也有特殊的处理。
public class People {
private Properties properties;
// ...
}使用 <props> 声明注入 Properties,嵌套 <prop> 声明一个键值对(value写在标签体中)。
<bean id="peopleBean" class="com.powernode.spring6.beans.People">
<property name="properties">
<props>
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>注入 null
public class Vip {
private String email;
private String tel;
// ...
}注⼊null使⽤:<null/> 或者 不为该属性赋值。
<bean id="vipBean" class="com.powernode.spring6.beans.Vip">
<!--注入 null 的第⼀种⽅式:不赋值-->
<!--<property name="tel" value="..."/>-->
<!--注入 null 的第二种⽅式-->
<property name="email">
<null/>
</property>
</bean>- 注入 null 时应考虑到空指针异常。
注入 ""
注⼊空字符串使⽤:<value/> 或者 value=""。
<bean id="vipBean" class="com.powernode.spring6.beans.Vip">
<!--空串的第⼀种⽅式-->
<property name="tel" value=""/>
<!--空串的第⼆种⽅式-->
<property name="email">
<value/>
</property>
</bean>注入值的特殊符号
XML中有5个特殊字符,分别是:<、>、'、"、&。
这5个特殊符号在XML中会被特殊对待,会被当做XML语法的⼀部分进⾏解析,如果这些特殊符号直接 出现在注⼊的字符串当中,会报错。
- 通常发生在简单注入中。
解决⽅案包括两种:
特殊符号使⽤转义字符代替。
特殊字符 转义字符 > >< <' '" "& &<!--before--> <property name="result" value="2 < 3"/> <!--after--> <property name="result" value="2 < 3"/>将含有特殊符号的字符串放到
<![CDATA[]]>当中。因为放在CDATA区中的数据不会被 XML⽂件解析器解析。<!--before--> <property name="result" value="2 < 3"/> <!--after--> <property name="result"> <!--只能使⽤value标签 --> <!-- <![CDATA[...]]>直接解析 ... 作为标签体 --> <value><![CDATA[2 < 3]]></value> </property>
- 这两种解决方案都是遵循 xml 的语法规范,并不是 Spring 的语法糖。
p命名空间注入
p命名空间是对 Spring 配置文件的拓展,可以简化set注入的配置。
使用:
在 xml 配置文件头部添加配置信息:
xmlns:p="http://www.springframework.org/schema/p"p命名空间注⼊是基于setter⽅法的,所以需要对应的属性提供setter⽅法。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="customerBean" class="com.powernode.spring6.beans.Customer"
p:name="zhangsan" p:age="20"/>
<!--使用 p:属性名 指代 property 子标签实现set注入-->
</beans>- 注入非简单类型,使用带
-ref的属性即可。
c命名空间注入
和 p命名空间一样,c命名空间是简化构造注入存在的。
使用方法:
xml 配置文件添加头部信息,以启用 c命名空间:
xmlns:c="http://www.springframework.org/schema/c"提供相应的构造方法

util命名空间
使用util命名空间可以让配置复用。
使用方式:在配置文件头部添加配置信息
<beans ...
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="...
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
...
</beans>使用util命名空间,简化对 **Properties(类型)**的注入:
public class MyDataSource {
private Properties properties;
// ...
}<util:properties id="prop">
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</util:properties>
<bean id="dataSource1" class="com.powernode.spring6.beans.MyDataSource">
<!--使用 ref 引用util中定义的 prop-->
<property name="properties" ref="prop"/>
</bean>同理,可以注入其他集合类型的内容,在需要的地方引用即可:

基于xml的自动装配
Spring还可以完成⾃动化的注⼊,⾃动化注⼊⼜被称为**⾃动装配**。它可以根据**名字(name)进⾏⾃动装配,也可以根据类型(class)**进⾏⾃动装配。
自动装配需要手动在bean标签内启用,表示这个bean的属性值启用自动装配。
<bean id="..." class="..." autowire="byName || byType"/>- 无论是基于名字还是根据类型进行自动装配,底层都是基于setter。
- 基于名字,解析setter方法名;根据类型,解析setter参数类型。
基于名字进行自动装配
指自动注入时,会在容器内查找与该属性同名的bean(id与属性名相同),将这个bean注入到这个属性上。
public class UserService {
private UserDao dao;
// ...
}autowire="byName" 启用基于名字自动装配。
<bean id="dao" class="com.powernode.spring6.dao.UserDao"/>
<!-- autowire="byName" 启用根据名字自动装配 -->
<bean id="userService" class="com.powernode.spring6.service.UserService" autowire="byName"/>名字:与setter对应的部分,跟set方法名有关。

基于名字注入,就是根据名字,查询容器内bean的id,将id与名字相同bean注入到这个属性上。
根据类型自动装配
autowire="byType" 启用基于名字自动装配。
<!--byType表示根据类型⾃动装配-->
<bean id="accountService" class="com.powernode.spring6.service.UserService" autowire="byType"/>
<bean class="com.powernode.spring6.dao.UserDao"/>查找容器内部为该类型或其子类的bean(即使无id,也是在容器内,只是无法引用、获取),如果有多余1个bean符合该类型,则报错。
Spring 引入外部属性配置文件
引入外部配置文件(.properties),可以使用 ${key}来实现值的引用,方便修改。
启用方式:
在Spring配置文件中引入context命名空间:
<beans ... xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="... http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> ... </beans>准备 .properties 属性配置文件
#jdbc.properties driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/spring username=root password=root123在Spring配置文件中使用
<!--指定外部属性配置文件--> <context:property-placeholder location="jdbc.properties"/> <!--使用${key}引用值--> <bean id="dataSource" class="com.powernode.spring6.beans.MyDataSource"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean>
- 必须引入外部属性配置文件(?使用context标签),
${}才有效。 ${key}优先从所运行的系统上查找环境变量,根据key引用环境变量的值,如果环境变量中没有,再从属性配置文件中引入。
Spring 引入外部 spring配置文件
有时 spring 配置是相同的,能复用最好;有时 spring 配置过多,全写在一个文件里太庞杂,不好梳理……等等情况,都可以使用 <import> 来实现外部 spring 配置文件的导入,实现复用、文件划分等。
如:
开启组件扫描的配置 common.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"
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">
<!--组件扫描-->
<context:component-scan base-package="com.powernode.bank"/>
</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">
<!--引⼊其他的spring配置⽂件:开启组件扫描-->
<import resource="common.xml"/>
<!--其他配置...-->
</beans>- 在实际开发中,service单独配置到⼀个⽂件中,dao单独配置到⼀个⽂件中,然后在核⼼配置⽂件中引⼊。
