Spring基础-004-Spring对IoC的实现

86 阅读7分钟

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命名空间注入的前提条件有两个:

  1. 在xml文件头部添加==p命名空间==的配置信息: xmlns:p="[http://www.springframework.org/schema/p"
  2. 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命名空间是简化了构造方法注入的

使用条件:

  1. xml 文件引入配置信息: xmlns:c="[http://www.springframework.org/schema/c"

  2. 需要构造方法

<?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文件中添加配置信息

image.png

<?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还可以完成自动化的注入,自动化注入又被成为自动装配,有两种方式

  1. 依据名字进行自动装配
  2. 依据类型进行自动转配

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>

这里有几个关键点:

  1. 引入context命名空间

  2. 声明了需要引入配置文件的名字 <context:property-placeholder location="jdbc.properties"/>

  3. 在bean属性注入的时候使用 ==value="key==<propertyname="driver"value="{key}'== `<property name="driver" value="{driver}"/>`