4.1 IoC 控制反转
●控制反转是一种思想。
●控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则。
●控制反转,反转的是什么?
○将对象的创建权利交出去,交给第三方容器负责。
○将对象和对象之间关系的维护权交出去,交给第三方容器负责。
●控制反转这种思想如何实现呢?
○DI(Dependency Injection):依赖注入
4.2 依赖注入
依赖注入实现了控制反转的思想。
Spring通过依赖注入的方式来完成Bean管理的。
Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
依赖注入:
●依赖指的是对象和对象之间的关联关系。
●注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系。
依赖注入常见的实现方式包括两种:
●第一种:set注入
●第二种:构造注入
新建模块:spring6-002-dependency-injection
4.2.1 set注入
set注入,基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。
运行结果:
TESTS PASSED:1 OF 1 TEST - 328MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JA
AVA.EXE
正在保存用户数据.
重点内容是,什么原理:
实现原理:
通过property标签获取到属性名:userDao
通过属性名推断出set方法名:setUserDao
通过反射机制调用setUserDao()方法给属性赋值
property标签的name是属性名。
property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,这是bean最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)
可以把set方法注释掉,再测试一下:
.POWERNODE.SPRING6.SERVICE.USERS
INVALID PROPERTY 'USERDAO' OF BEAN CLASS LCOM.
ON
BEAN PROPERTY 'USERDAO' IS NOT WRITABLE OR HAS AN INVALID
METHOD
SETTER
DOES
通过测试得知,底层实际上调用了setUserDao()方法。所以需要确保这个方法的存在。
我们现在把属性名修改一下,但方法名还是setUserDao(),我们来测试一下:
运行测试程序:
TESTS PASSED:1 OF 1 TEST -297MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EXE
正在保存用户数据.
通过测试看到程序仍然可以正常执行,说明property标签的name是:setUserDao()方法名演变得到的。演变的规律是:
●setUsername() 演变为 username
●setPassword() 演变为 password
●setUserDao() 演变为 userDao
●setUserService() 演变为 userService
另外,对于property标签来说,ref属性也可以采用标签的方式,但使用ref属性是多数的:
总结:set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。
4.2.2 构造注入
核心原理:通过调用构造方法来给属性赋值。
运行结果如下:
TESTS PASSED:1 OF 1 TEST - 344MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EX
.EXE
正在删除订单.
如果构造方法有两个参数:
spring配置文件:
执行测试程序:
TESTS PASSED: 1 OF 1 TEST - 456MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EXE
正在删除订单...
正在保存用户数据.
不使用参数下标,使用参数的名字可以吗?
执行测试程序:
TESTS PASSED:1 OF 1 TEST - 330MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EXE
正在删除订单.
正在保存用户数据.
不指定参数下标,不指定参数名字,可以吗?
执行测试程序:
TESTS PASSED:
ED:1 TESTEST-353MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JA
正在删除订单.
正在保存用户数据.
配置文件中构造方法参数的类型顺序和构造方法参数的类型顺序不一致呢?
执行测试程序:
TESTS PASSED:1 OF 1 TEST -316MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EXE
正在删除订单...
正在保存用户数据.
通过测试得知,通过构造方法注入的时候:
●可以通过下标
●可以通过参数名
●也可以不指定下标和参数名,可以类型自动推断。
Spring在装配方面做的还是比较健壮的。
4.3 set注入专题
4.3.1 注入外部Bean
在之前4.2.1中使用的案例就是注入外部Bean的方式。
外部Bean的特点:bean定义到外面,在property标签中使用ref属性进行注入。通常这种方式是常用。
4.3.2 注入内部Bean
内部Bean的方式:在bean标签中嵌套bean标签。
执行测试程序:
TESTS PASSED:1 OF 1 TEST -28
-281MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EXE
正在保存用户数据.
这种方式作为了解。
4.3.3 注入简单类型
我们之前在进行注入的时候,对象的属性是另一个对象。
那如果对象的属性是int类型呢?
可以通过set注入的方式给该属性赋值吗?
●当然可以。因为只要能够调用set方法就可以给属性赋值。
编写程序给一个User对象的age属性赋值20:
第一步:定义User类,提供age属性,提供age属性的setter方法。
第二步:编写spring配置文件:spring-simple-type.xml
第三步:编写测试程序
第四步:运行测试程序
TESTS PASSED:1 OF 1 TEST - 312MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAV
EXE
USER{AGE20}
需要特别注意:如果给简单类型赋值,使用value属性或value标签。而不是ref。
简单类型包括哪些呢?可以通过Spring的源码来分析一下:BeanUtils类
通过源码分析得知,简单类型包括:
●基本数据类型
●基本数据类型对应的包装类
●String或其他的CharSequence子类
●Number子类
●Date子类
●Enum子类
●URI
●URL
●Temporal子类
●Locale
●Class
●另外还包括以上简单值类型对应的数组类型。
经典案例:给数据源的属性注入值:
假设我们现在要自己手写一个数据源,我们都知道所有的数据源都要实现javax.sql.DataSource接口,并且数据源中应该有连接数据库的信息,例如:driver、url、username、password等。
我们给driver、url、username、password四个属性分别提供了setter方法,我们可以使用spring的依赖注入完成数据源对象的创建和属性的赋值吗?看配置文件
测试程序:
执行测试程序:
TS PASSED:1 TEST -312MS
C:\DEV\JAVA\JDK-17.0.4\BIN\JAVA.EXE
你学会了吗?
接下来,我们编写一个程序,把所有的简单类型全部测试一遍:
编写一个类A:
编写测试程序:
执行结果如下:
需要注意的是:
●如果把Date当做简单类型的话,日期字符串格式不能随便写。格式必须符合Date的toString()方法格式。显然这就比较鸡肋了。如果我们提供一个这样的日期字符串:2010-10-11,在这里是无法赋值给Date类型的属性的。
●spring6之后,当注入的是URL,那么这个url字符串是会进行有效性检测的。如果是一个存在的url,那就没问题。如果不存在则报错。
4.3.4 级联属性赋值(了解)
运行结果:
要点:
●在spring配置文件中,如上,注意顺序。
●在spring配置文件中,clazz属性必须提供getter方法。
4.3.5 注入数组
当数组中的元素是简单类型:
当数组中的元素是非简单类型:一个订单中包含多个商品。
测试程序:
执行结果:
要点:
●如果数组中是简单类型,使用value标签。
●如果数组中是非简单类型,使用ref标签。
4.3.6 注入List集合
List集合:有序可重复
执行结果:
注意:注入List集合的时候使用list标签,如果List集合中是简单类型使用value标签,反之使用ref标签。
4.3.7 注入Set集合
Set集合:无序不可重复
执行结果:
要点:
●使用<set>标签
●set集合中元素是简单类型的使用value标签,反之使用ref标签。
4.3.8 注入Map集合
执行结果:
要点:
●使用<map>标签
●如果key是简单类型,使用 key 属性,反之使用 key-ref 属性。
●如果value是简单类型,使用 value 属性,反之使用 value-ref 属性。
4.3.9 注入Properties
java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。
执行测试程序:
要点:
●使用<props>标签嵌套<prop>标签完成。
4.3.10 注入null和空字符串
注入空字符串使用:<value/> 或者 value=""
注入null使用:<null/> 或者 不为该属性赋值
●我们先来看一下,怎么注入空字符串。
执行结果:
●怎么注入null呢?
第一种方式:不给属性赋值
执行结果:
第二种方式:使用<null/>
执行结果:
4.3.11 注入的值中含有特殊符号
XML中有5个特殊字符,分别是:<、>、'、"、&
以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错。
解决方案包括两种:
●第一种:特殊符号使用转义字符代替。
●第二种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在CDATA区中的数据不会被XML文件解析器解析。
5个特殊字符对应的转义字符分别是:
特殊字符 |
转义字符 |
> |
> |
< |
< |
' |
' |
" |
" |
& |
& |
先使用转义字符来代替:
执行结果:
我们再来使用CDATA方式:
注意:使用CDATA时,不能使用value属性,只能使用value标签。
执行结果:
4.4 p命名空间注入
目的:简化配置。
使用p命名空间注入的前提条件包括两个:
●第一:在XML头部信息中添加p命名空间的配置信息:xmlns:p="http://www.springframework.org/schema/p"
●第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
执行结果:
把setter方法去掉:
所以p命名空间实际上是对set注入的简化。
4.5 c命名空间注入
c命名空间是简化构造方法注入的。
使用c命名空间的两个前提条件:
第一:需要在xml配置文件头部添加信息:xmlns:c="http://www.springframework.org/schema/c"
第二:需要提供构造方法。
执行结果:
把构造方法注释掉:
所以,c命名空间是依靠构造方法的。
注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型。
4.6 util命名空间
使用util命名空间可以让配置复用。
使用util命名空间的前提是:在spring配置文件头部添加配置信息。如下:
执行结果:
4.7 基于XML的自动装配
Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配。
4.7.1 根据名称自动装配
Spring的配置文件这样配置:
这个配置起到关键作用:
●UserService Bean中需要添加autowire="byName",表示通过名称进行装配。
●UserService类中有一个UserDao属性,而UserDao属性的名字是aaa,对应的set方法是setAaa(),正好和UserDao Bean的id是一样的。这就是根据名称自动装配。
执行结果:
我们来测试一下,byName装配是和属性名有关还是和set方法名有关系:
在执行测试程序:
通过测试得知,aaa属性并没有赋值成功。也就是并没有装配成功。
我们将spring配置文件修改以下:
执行测试程序:
这说明,如果根据名称装配(byName),底层会调用set方法进行注入。
例如:setAge() 对应的名字是age,setPassword()对应的名字是password,setEmail()对应的名字是email。
4.7.2 根据类型自动装配
执行结果:
我们把UserService中的set方法注释掉,再执行:
可以看到无论是byName还是byType,在装配的时候都是基于set方法的。所以set方法是必须要提供的。提供构造方法是不行的,大家可以测试一下。这里就不再赘述。
如果byType,根据类型装配时,如果配置文件中有两个类型一样的bean会出现什么问题呢?
执行测试程序:
测试结果说明了,当byType进行自动装配的时候,配置文件中某种类型的Bean必须是唯一的,不能出现多个。
4.8 spring引入外部属性配置文件
我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件中吗,这样用户修改起来会更加的方便。当然可以。
第一步:写一个数据源类,提供相关属性。
第二步:在类路径下新建jdbc.properties文件,并配置信息。
第三步:在spring配置文件中引入context命名空间。
spring-properties.xml
XML
复制代码
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="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/contexthttp://www.springframework.org/schema/context/spring-context.xsd">
</beans>
第四步:在spring中配置使用jdbc.properties文件。
spring-properties.xml
XML
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="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/contexthttp://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholderlocation="jdbc.properties"/>
<beanid="dataSource"class="com.powernode.spring6.beans.MyDataSource">
<propertyname="driver"value="${driver}"/>
<propertyname="url"value="${url}"/>
<propertyname="username"value="${username}"/>
<propertyname="password"value="${password}"/>
</bean>
</beans>
测试程序:
Java
复制代码
1
2
3
4
5
6
@Test
publicvoidtestProperties(){
ApplicationContextapplicationContext=newClassPathXmlApplicationContext("spring-properties.xml");
MyDataSourcedataSource=applicationContext.getBean("dataSource",MyDataSource.class);
System.out.println(dataSource);
}
执行结果: