4.1 IoC控制反转
![[001-引入#1.3 控制反转 IoC]]
4.1.1 set方法注入
set注入 ,是基于set方法实现的,底层通过反射机制调用属性对应的set方法然后给属性赋值!!
样例代码:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId>
<artifactId>spring6-002-dependency-injection</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
package com.powernode.spring6.dao;
public class UserDao {
public void insert(){
System.out.println("正在保存用户数据。");
}
}
package com.powernode.spring6.service;
import com.powernode.spring6.dao.UserDao;
public class UserService {
private UserDao userDao;
// 使用set方式注入,必须提供set方法。
// 反射机制要调用这个方法给属性赋值的。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
<?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="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
package com.powernode.spring6.test;
import com.powernode.spring6.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DITest {
@Test
public void testSetDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userServiceBean", UserService.class);
userService.save();
}
}
实现原理:
1. 通过property标签获取属性名: `userDao`
2. 通过属性名来推断 `set` 方法名: `setUserDao`
3. 通过反射机制调用 `setUserDao` 来给属性赋值
- property标签的name是属性名
- property标签的 ref 需要的是注入的bean对象的id (**是通过ref来完成对于bean的转配 ,这是bean最简单的一种装配方式.)
- 转配指的是 **创建系统组件之间关联的动作**
**set方法注入的核心** :
- 通过反射机制来调用set方法来给属性赋值,让两个对象之间产生关系
### 4.1.2 构造注入
核心原理: **通过构造方法来给属性赋值**
也就是在提供含有bean对象的属性的时候使用构造方法的形式
```java
public class OrderService {
private OrderDao orderDao;
private UserDao userDao;
// 通过反射机制调用构造方法给属性赋值
public OrderService(OrderDao orderDao, UserDao userDao) {
this.orderDao = orderDao;
this.userDao = userDao;
}
public void delete(){
orderDao.deleteById();
userDao.insert();
}
}
同时注意 , 这里有两个bean对象属性,那么再写配置文件 beans.xml 的时候第一个参数下标是0 , 第二个参数下标是 1.具体如下:
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--第一个参数下标是0-->
<constructor-arg index="0" ref="orderDaoBean"/>
<!--第二个参数下标是1-->
<constructor-arg index="1" ref="userDaoBean"/>
</bean>
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
当然,这里也可以直接使用 name属性来赋值
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<!--这里使用了构造方法上参数的名字-->
<constructor-arg name="orderDao" ref="orderDaoBean"/>
<constructor-arg name="userDao" ref="userDaoBean"/>
</bean>
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
什么都不使用也是可以的,自动注入spring中自动装配
4.2 set注入详解
4.2.1 注入外部Bean
特点: bean定义到外面, 在property标签中使用ref属性来 进行注入 .
也就是当有一个bean A是另一个bean B的属性时,我们在配置文件中首先先注入bean A, 在bean B中通过使用 property 的方式来注入bean A作为属性.
4.2.2 注入内部Bean
内部Bean的方式: bean标签中嵌套 bean 标签
与外部bean不同的是 注入内部Bean 是直接在Bean B中嵌套注入Bean A,而不是先注入Bean A之后,再将 Bean A作为属性注入 Bean B.
举例
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<bean class="com.powernode.spring6.dao.UserDao"/>
</property>
</bean>
4.2.3 注入简单类型
也就是Bean对象的属性是一些基本类型 (int, double, String ...)
基本类型 :
- 基本数据类型
- 基本数据类型对应的包装类
- String或其他的CharSequence子类
- Number子类
- Date子类
- Enum子类
- URI
- URL
- Temporal子类
- Locale
- Class
- 另外还包括以上简单值类型对应的数组类型。
也是按照注入类型一样,写set方法
在配置文件中会有所不同
<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>
当然,这里可以会思考如何动态的为这些属性赋值:
可以参考中的springboot @Value,或者@ConfigurationProperties
4.2.4 级联属性赋值
--Student ----name
----class
------name
在这里就形成了一种级联属性
配置文件尤其需要注意
<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>
这里面需要注意的点:
- 先要将clazz作为属性注入student,然后才能再次对clazz的属性name进行注入,
- clazz必须要有getter方法
4.2.5 注入数组
4.2.5.1 数组包含的是简单元素
<bean id="person" class="com.powernode.spring6.beans.Person">
<property name="favariteFoods">
<array>
<value>鸡排</value>
<value>汉堡</value>
<value>鹅肝</value>
</array>
</property>
</bean>
4.2.5.2 数组包含的是非简单类型
<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>
4.2.5.3 总结
总结:
- ===如果数组中是简单类型,使用value标签。===
- ===如果数组中是非简单类型,使用ref标签。===
4.3.6 注入List集合
List集合的特点:
- 有序可重复
与数组有所不同的是, beans.xml 中使用的标签:
<bean id="peopleBean" class="com.powernode.spring6.beans.People">
<property name="names">
<list>
<value>铁锤</value>
<value>张三</value>
<value>张三</value>
<value>张三</value>
<value>狼</value>
</list>
</property>
</bean>
注意:注入List集合的时候使用list标签,如果List集合中是简单类型使用value标签,反之使用ref标签。
4.3.7 注入set集合
Set集合特点:
- 无序且不可重复
有所不同的仍是 beans.xml 文件:
<bean id="peopleBean" class="com.powernode.spring6.beans.People">
<property name="phones">
<set>
<!--非简单类型可以使用ref,简单类型使用value-->
<value>110</value>
<value>110</value>
<value>120</value>
<value>120</value>
<value>119</value>
<value>119</value>
</set>
</property>
</bean>
同样的: set集合中元素是简单类型的使用value标签,反之使用ref标签.
4.3.8 注入Map集合
Map集合特点:
是由一个一个键值对来构成的 同时 无序 不允许重复的键
其中不同的仍然是 beans.xml 文件中bean对象的注入
<bean id="peopleBean" class="com.powernode.spring6.beans.People"> <property name="addrs"> <map> <!--如果key不是简单类型,使用 key-ref 属性--> <!--如果value不是简单类型,使用 value-ref 属性--> <entry key="1" value="北京大兴区"/> <entry key="2" value="上海浦东区"/> <entry key="3" value="深圳宝安区"/> </map> </property> </bean>
要点:
- 使用标签
- 如果key是简单类型,使用 key 属性,反之使用 key-ref 属性。
- 如果value是简单类型,使用 value 属性,反之使用 value-ref 属性。
4.3.9 注入properties
java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。
<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和空字符串 以及特殊符号
4.3 p空间注入
目的: 简化配置 使用p命名空间注入的前提条件有两个:
- 在xml文件头部添加==p命名空间==的配置信息:
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"/>
</beans>
==所以p命名空间实际上是对set注入的简化。==
4.4 c命名空间注入
c命名空间是简化了构造方法注入的
使用条件:
-
xml文件引入配置信息:xmlns:c="[http://www.springframework.org/schema/c" -
需要构造方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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="myTimeBean" class="com.powernode.spring6.beans.MyTime" c:year="1970" c:month="1" c:day="1"/>-->
<bean id="myTimeBean" class="com.powernode.spring6.beans.MyTime" c:_0="2008" c:_1="8" c:_2="8"/>
</beans>
注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型
4.5 util命名空间
使用util命名空间可以让 ==配置复用==
前提: 在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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
//下面的也是新的添加的配置信息
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<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.MyDataSource1">
<property name="properties" ref="prop"/>
</bean>
<bean id="dataSource2" class="com.powernode.spring6.beans.MyDataSource2">
<property name="properties" ref="prop"/>
</bean>
</beans>
4.6 基于XML的自动装配
Spring还可以完成自动化的注入,自动化注入又被成为自动装配,有两种方式
- 依据名字进行自动装配
- 依据类型进行自动转配
4.6.1 依据名字自动装配
UserDao类
public class UserDao {
public void insert(){
System.out.println("正在保存用户数据。");
}
}
UserService类
public class UserService {
private UserDao aaa;
// 这个set方法非常关键
public void setAaa(UserDao aaa) {
this.aaa = aaa;
}
public void save(){
aaa.insert();
}
}
beans.xml
<bean id="userService" class="com.powernode.spring6.service.UserService"
autowire="byName"/>
<bean id="aaa" class="com.powernode.spring6.dao.UserDao"/>
从这里可以看出来,需要在userService里面添加==autowire="byName"==
因为在UserService类中,UserDao 的属性名为 aaa,同时set方法为 setAaa , 由于是通过名字进行自动装配,所以这里注入UserDao Bean时,id就直接使用 aaa
实际上底层依据名字进行自动装配的时候,是依据==set方法== 来进行注入的,它会对set方法进行处理后来得到其在==beans.xml中的配置文件对应的bean的id==,再对其进行注入, 与属性名为什么无关
4.7.2 依据类型自动装配
AccountDao
public class AccountDao {
public void insert(){
System.out.println("正在保存账户信息");
}
}
AccountService
public class AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void save(){
accountDao.insert();
}
}
beans.xml
<bean id="accountService" class="com.powernode.spring6.service.AccountService" autowire="byType"/>
<bean class="com.powernode.spring6.dao.AccountDao"/>
依据类型进行自动装配也是基于 set方法
同时,不能在依据类型自动装配的时候,是不能够出现类型一样的bean的,
4.7 Spring 引入外部属性配置文件
第一步:
- 提供数据源类, 并提供相关属性
第二步:
- 类路径下新建jdbc.properties文件,并提供配置信息
第三步:
- 在spring配置文件中引入context命名空间
第四步:
- 在spring中配置使用jdbc.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"
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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<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>
</beans>
这里有几个关键点:
-
引入context命名空间
-
声明了需要引入配置文件的名字
<context:property-placeholder location="jdbc.properties"/> -
在bean属性注入的时候使用 ==value="{driver}"/>`