(一)IOC的概念和作用
(二)前期准备
(三)基于XML的IOC环境搭建和入门
(四)ApplicationContext的三个实现类
(五)BeanFactory和ApplicationContext的区别
(六)Bean的细节:三种创建Bean对象的方式
(七)Bean的细节:作用范围
(八)Bean的细节:生命周期
(九)DI的概念和作用
(十)构造函数注入
(十一)set方法注入
(十二)注入复杂类型数据
(十三)业务层实现类调用持久层的方法
IOC:Inversion of Control,是控制反转的意思
我们之前想new谁就new谁,控制权在自己手上
现在我们把控制权交给了工厂,由工厂去创建对象,所以说控制反转了
IOC的作用:削减计算机程序的耦合(只能降低耦合,无法完全消除)
下载与目录结构简述:
官网:http://spring.io/
下载地址:http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring 目录结构:)
* docs :API 和开发规范
* libs :jar 包和源码
* schema :约束
我们其实只需要一个dist文件夹即可,里面包含了jar包、文档和约束
特别说明:
我们上课使用的版本是 spring5.0.2
spring5 版本是用 jdk8 编写的,所以要求我们的 jdk 版本是 8 及以上
同时 tomcat 的版本要求 8.5 及以上
如何使用:
我们重新新建一个普通的maven项目
然后把上一个项目的代码文件复制过来,并且把工厂类相关的代码去掉
可以看到,这个时候项目又变回原来的样子,可以正常运行,但是耦合又回来了
我们接下来开始真正的使用Spring框架
我们先导入maven坐标,如下:
jar包的对应关系如下:
(其中的apache的日志jar包已经被spring集成到自己的jar包里面去了)
依赖关系如下:
我们接下来在resources目录下创建一个xml文件
我们接下来去Spring的官网拷贝xml的约束
使用CTRL+F进行全局搜索
把约束的部分拷贝到xml里面
然后向map容器添加数据
最后尝试使用Spring的ioc容器获取对象
说明:
我们接下来看一下ApplicationContext的实现类
我们常用的实现类有三个,如下:
(其中第一个是用于注解开发的,我们后面再讲;我们先看后面两个)
//1.获取核心容器对象
ApplicationContext ac = new FileSystemXmlApplicationContext("G:\\JavaEE\\Spring_xml\\src\\main\\resources\\bean.xml");
//----------BeanFactory--------------
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService) factory.getBean("accountService");
System.out.println(as);
断点测试如下:
可以看到只有根据id创建对象时,才是真正的创建对象
那我们怎么知道spring的ioc容器为我们创建对象是单例的还是多例的呢?下面会详细介绍
其实我们现在还是无法调用业务层实现类的方法
现在看起来好像可以调用,其实是因为在业务层实现类中依然存在耦合
我们是直接new了一个持久层对象,我们应该把它去掉的,如下:
我们后面再详细介绍怎么调用业务层实现类的方法,现在我们先不要调用
(本篇博客最末尾会介绍,点击跳转 ----- 业务层实现类调用持久层的方法)
我们继续创建一个新的普通的maven项目
为了方便展示创建对象,删掉持久层,只留下业务层
我们接下来分别看看三种创建Bean对象的方式:
id
和class属性
之后,且没有其他属性和标签时采用的就是默认构造函数创建bean对象注意:如果我们不重写构造函数,其实是会有默认构造函数的;如果我们重写了带参构造函数,并且不重写一个空参构造函数,这种情况就属于没有默认构造函数,如下:
这种情况是会报错的
我们接下来模拟一个工厂类(它可能是存在于jar包中的),如下:
(通常jar包都会暴露一个工厂类,给调用者创建对象)
我们来运行一下
bean对象默认情况下是单例的,我们验证一下:
那我们要怎么修改bean对象的作用范围呢?
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
192.168.0.1
服务器发起请求,这个时候验证码以session的形式存在于192.168.0.1
服务器,并且以缓存的形式存在于客户端浏览器192.168.0.1
服务器满负载了,但是198.168.0.6
服务器是空闲的,我们通过负载均衡自然就会访问198.168.0.6
服务器 单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
我们来测试一下
我们先在业务层的实现类中添加初始化和销毁的方法
然后编写bean.xml
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy">bean>
运行效果如下:
为什么没有调用销毁方法呢?因为main函数执行完的话,所有的数据都被销毁
换而言之,还没来得及调用销毁方法就已经被销毁了
如果我们想手动关闭,却发现没有这个方法
原因是:我们使用了多态,接口是不具备close()
方法的
我们不要把它成接口,我们把它看成一个类(也就是它本身)就可以了
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
我们修改为多例模式
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy">bean>
我们其他的保持不变,直接运行试试
我们发现销毁方法没有被执行,这是为什么呢?
因为spring交给了Java的垃圾回收器进行回收,回收与否不能干涉
依赖注入:
Dependency Injection
IOC的作用:
降低程序间的耦合(依赖关系)
DI的作用:依赖关系的管理,依赖都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:
就称之为依赖注入
依赖注入:
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供(后面再介绍)
(在讲注入之前明确一点:经常变化的数据,不适合注入)
这里举例的变量为:name、age和birthday是为了迎合Account场景,其实我们知道这三个变量几乎每个用户都不一样,那每一个用户请求该接口的时候这三个变量都要发生改变,是不合适注入的
首先我们要在类中定义一些可注入的变量,如下:
接下来写一个构造方法
我们回来bean.xml已经发现报错了,因为已经没有默认构造函数了
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值(索引的位置是从0开始)
name(最常用):用于指定给构造函数中指定名称的参数赋值
=============以上三个用于指定给构造函数中哪个参数赋值=============================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据(它指的就是在spring的ioc核心容器中出现过的bean对象)
尝试用构造函数注入试试
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test">constructor-arg>
<constructor-arg name="age" value="18">constructor-arg>
<constructor-arg name="birthday" value="1970-01-01">constructor-arg>
bean>
由于xml只能输入字符串,所以spring会自动把String类型的"18"转成Integer类型的18
但是对于Date类型,spring就无能为力了,直接运行会报错,如下:
对于像Date这样的其他bean类型,需要在bean.xml里面声明,如下:
(同时需要ref标签来做引用)
此时运行的结果如下:
总结:
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功(因为没有默认空参构造)
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
由于其弊端太明显,我们一般不用这种方式,更多的是用set方法注入
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时所调用的set方法名称(如:setName --> name)
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的ioc核心容器中出现过的bean对象
复制刚才的实现类,并且加以修改,去掉构造方法,增加set方法,如下:
接下来编写bean.xml,如下:
<bean id="now" class="java.util.Date">bean>
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl2">
<property name="name" value="test">property>
<property name="age" value="21">property>
<property name="birthday" ref="now">property>
bean>
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行,导致该成员没有赋到值
我们来看看复杂类型(集合类型)的注入
AccountServiceImpl3.java:
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl3 implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount() {
System.out.println("myStrs:" + Arrays.toString(myStrs));
System.out.println("myList:" + myList);
System.out.println("mySet:" + mySet);
System.out.println("myMap:" + myMap);
System.out.println("myProps:" + myProps);
}
}
在尝试编写bean.xml的时候发现了很多子标签,如下:
我们先写一个数组
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
array>
property>
bean>
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
array>
property>
<property name="myList">
<list>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
list>
property>
<property name="mySet">
<set>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
set>
property>
<property name="myMap">
<map>
<entry key="testA" value="AAA">entry>
<entry key="testB">
<value>BBBvalue>
entry>
map>
property>
<property name="myProps">
<props>
<prop key="testC">cccprop>
<prop key="testD">dddprop>
props>
property>
bean>
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换
如下:
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
set>
property>
<property name="myList">
<array>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
array>
property>
<property name="mySet">
<list>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
list>
property>
<property name="myMap">
<props>
<prop key="testC">cccprop>
<prop key="testD">dddprop>
props>
property>
<property name="myProps">
<map>
<entry key="testA" value="aaa">entry>
<entry key="testB">
<value>BBBvalue>
entry>
map>
property>
bean>
运行效果:
所以我们实际开发中,只需要使用list结构和map结构即可
<bean id="dao" class="com.zzq.dao.impl.AccountDaoImpl">bean>
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl4">
<property name="accountDao" ref="dao">property>
bean>