IOC的概念和作用
这两行代码就明显的揭露出IOC的含义
// private IAccountDao dao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
这两种代码代表两种截然不同创建对象的方式
当我们用new创建对象的时候 我们APP直接和资源取得联系 他们直接有必然的联系 资源独立和应用独立变得很难 它有明显的依赖关系
当我们用第二种方式创建对象的时候 APP断开了与资源的联系 而是找工厂要资源 让工厂与资源取得联系 并把想要的对象转给应用 从而实现了资源与应用必然的依赖关系
基于这种思想 就是我们所说的IOC
![](https://img.haomeiwen.com/i13543313/837dfd250aa1a56c.png)
为什么叫控制反转而不是降低依赖呢?
我们在编写Service的时候 用new 或者 工厂 都是自己决定的AccountServiceImpl 这个类可以有自主使用的权力 它在寻找Dao的时候可以自主选择自己想要的Dao的(通过 new)
但是它把自主选择Dao的权利交给了BeanFactory 然后由固定的名称找到bean对象 这个bean对象是不是我们能用的 我们就不得而知了 我们不能自主控制 通过 控制权发生了转移 所以是控制反转
带来的好处就是 降低耦合
Spring中IOC的前期准备
IOC只能解耦 降低程序间的依赖关系
前面讲了工厂模式来解耦 现在使用配置的方式来实现这些
准备Spring的开发包
下载地址: https://repo.spring.io/libs-release-local/org/springframework/spring/
打开它可以看见目录结构
![](https://img.haomeiwen.com/i13543313/820944822b73b9df.png)
doc:API和开发规范
libs:jar包和源码
schema:约束
![](https://img.haomeiwen.com/i13543313/4059211588dc27b6.png)
spring基于XML的IOC环境搭建和入门
新建一个工程
![](https://img.haomeiwen.com/i13543313/92f98851f81bcbb1.png)
之前代码里的dao和service都能用,copy过来,删掉Factory,原来使用工厂创建对象 改为用new创建
![](https://img.haomeiwen.com/i13543313/846d58bbcee413fe.png)
搭建环境
![](https://img.haomeiwen.com/i13543313/1f49e26ab6fc6d23.png)
导入denpendency以后 多了这些jar包 spring把你可能会用到的jar包都导进来了
![](https://img.haomeiwen.com/i13543313/137e9c9e1384880e.png)
![](https://img.haomeiwen.com/i13543313/249e7edaa8ea7ba8.png)
通过 IDEA maven 的 show dependencies 功能 可以看到 beans core aop expreesion 这四个核心容器
简单来说核心容器就是个map 里面放着封装的对象
![](https://img.haomeiwen.com/i13543313/26e983ae6989e142.png)
仍然需要一个配置文件,所以创建beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
接下来让对象的创建让spring来管理
![](https://img.haomeiwen.com/i13543313/8701f11ae27503be.png)
接下来 在Client这个类中,之前的代码需要改造下.
这个类要做到两件事:
1.获取核心容器对象
2.根据ID获取Beans
获取核心容器的 ApplicationContext类 有两个实现类 分别是
①ClassPathXmlApplicationContext
②FileSystemXmlApplicationContext
这两个都是基于Xml配置的
![](https://img.haomeiwen.com/i13543313/ceb8dd18437f73d6.png)
public class Client {
/**
* 获取核心容器对象并据ID获取对象
* @param args
*/
public static void main(String[] args) {
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
//根据id获取Bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("acccountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
getBean的另外一种方式,传入一字节码,根据字节码来获取对象类型,另外一种是Object类型 我们自己强转.
![](https://img.haomeiwen.com/i13543313/7f93dc0374e80c9a.png)
ApplicationContext的三个实现类
①ClassPathXmlApplicationContext:它可以加载类路径下的所有配置文件,要求配置文件必须在类路径下.不在的话加载不了
②FlieSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须要有访问权限)
③AnotationXmlApplicationContext:它是用注解创建容器的
所以用FileSystemXmlApplicationContext来获取核心容器的话,改动仅需把
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\WorkSpace\\day01_eesy_03spring\\src\\main\\resources\\beans.xml");
这里改动即可
public class Client {
/**
* 获取核心容器对象并据ID获取对象
* @param args
*/
public static void main(String[] args) {
//获取核心容器对象
//ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\WorkSpace\\day01_eesy_03spring\\src\\main\\resources\\beans.xml");
//根据id获取Bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
BeanFactory和ApplicationContext的区别
核心容器的两个接口引发出的问题
ApplicationContext:它在构建核心容器的时候,采用的立即加载的方式,也就是说只要一读取完配置文件就马上加载.
BeanFactory:它在构建核心容器的时候,采用的是延迟加载的方式,也就是说什么时候根据id获取对象了,什么时候才创建.
验证 ApplicationContext 的加载:
在Service中增加一个构造函数
public class AccountServiceImpl implements IAccountService {
/**
* 模拟保存账户
*/
private IAccountDao accountDao = new AccountDaoImpl();
public AccountServiceImpl() {
System.out.println("AccountServiceImpl创建了");
}
@Override
public void saveAccount() {
}
}
在Client中打上断点 可以看到刚读取完配置文件,对象就被创建了
![](https://img.haomeiwen.com/i13543313/e9ee547300c1b53d.png)
BeanFactory接口:
首先 先定义BeanFactory factory = null; Alt + Ctrl + B 可以看见BeanFactory的实现类,我们选择了 XmlBeanFactory,虽然它已经过时了
![](https://img.haomeiwen.com/i13543313/7d592f6666fbb907.png)
于是 BeanFactory factory = new XmlBeanFactory();
XmlBeanFactory 需要一个参数对象 ,ctrl + 左键 可以看到 需要一个Resource对象
![](https://img.haomeiwen.com/i13543313/3d504820f3af3b0f.png)
![](https://img.haomeiwen.com/i13543313/4a251f762c373932.png)
![](https://img.haomeiwen.com/i13543313/b7d66786ede4f7eb.png)
![](https://img.haomeiwen.com/i13543313/29854516c8e64b62.png)
查看类的实现 是一个很好的思维方式,从老师这里学到的.
选择了 ClassPathResource 我们需要去类路径寻找我们的bean,xml
再往后的步骤都一样了
public static void main(String[] args) {
/*--------------------BeanFactory------------------------*/
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
//通过id获取Bean对象
IAccountService as = (IAccountService) factory.getBean("accountService");
System.out.println(as);
}
打断点 看执行的时候什么时候创建对象 可以看到 再读完配置文件 配置好工厂后 都没有创建好对象 通过id获取Bean对象的时候 才真正创建了对象
![](https://img.haomeiwen.com/i13543313/97213a646071c8df.png)
所以两种创建对象的时间点是不一样的
立即加载 在工厂模式下 Service和Dao由于没有类成员,不存在线程安全的问题,所以在此基础上,可以直接选用单例模式创建对象,既然是单例模式,对象只会创建一次,所以可以使用立即加载的方式.只要容器创建,就马上创建对象,适用于单例对象.
延迟加载 什么时候用,什么时候才真正的创建对象 适用于多例模式
如果在一加载容器的时候就创建对象,第二次使用的时候又再创建一次对象,不如在什么时候用的时候什么时候创建更合适
Spring 可以根据配置上的不同 可以改变对象创建的方式
BeanFactory 是个顶层接口 功能不是那么完善 实现类和子接口会在BeanFactory的功能上进行拓展,所以实际开发中使用applicationContext比较多
applicationContext 如何判定单例还是多例
spring中bean的细节之三种创建Bean对象的方式
准备一个新的工程
![](https://img.haomeiwen.com/i13543313/6cec90bb2ee02fa6.png)
找到03里的一些源码直接复制到04工程中,bean.xml也复制一份.删掉dao.(为了更简洁)
Spring对Bean的管理细节
-
创建Bean的三种方式
-
使用默认构造函数创建
在Spring的配置文件中 使用bean标签中 配以id和class属性以后 没有其他的属性和标签时 采用的就是使用默认构造函数创建对象 此时如果类中没有默认的构造函数 则对象无法创建.
在原来的代码中 已存在 AccountServiceImpl类的构造函数 在原有的默认构造函数上添加一个变量 让它不再是默认的构造函数 此时点击运行 报错信息为:No default constructor found;
验证默认构造函数创建Bean对象
,
这个时候在bean,xml 中 也可以看到配置文件中的报错 它没有找到这个类的默认构造函数
beam,xml中也在报错
- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象并存入Spring容器
-
/**
* 模拟一个工厂类(该类存在于jar包中 我们无法通过修改源码的方式来提供默认构造函数),,
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
模拟一个工厂类(该类存在于jar包中 我们无法通过修改源码的方式来提供默认构造函数)
在实际开发中 有可能遇上别人写好的类 这中类存在jar包中 属于字节码文件 我们无法修改 同时里面可能提供一个方法,比如说 getAccountService 这个方法可以得到一个 accountService ,如果这个类存在与Jar包,如何获得这个service对象?
在bean.xml中,我们通过instanceFactory这个id 通过反射创建这个类的对象 ,但我们是需要用这个工厂对象吗?不是要用这个类中方法的返回值的对象吗?
<bean id="instanceFactory" class="itheima.factory.InstanceFactory" ></bean>
我们要用的是accoutService,只不过accountService不再是通过我们accountServiceImpl来得到的 ,我们要用工厂类的方法 于是,完整版bean,xml的配置是
<bean id="instanceFactory" class="itheima.factory.InstanceFactory" ></bean>
<!--指定工厂beans 和 方法-->
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 使用静态工厂中的静态方法创建对象(或者某个类中的静态方法创建对象并存入spring容器)
Copy一份InstanceFactory的代码,只需要把方法 加一个static的修饰即可.
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
此时在beam.xml中,通过bean 的id反射得到实例对象是staticFactory,而不是service的实例,所以需要配置factory-method 这个属性来获得这个方法返回的service实例
<bean id="accountService" class="itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
-
Bean对象的作用范围
- bean的作用范围:
bean标签的scope属性
作用:指定bean的作用范围
- bean的作用范围:
取值:
- singleton:单例的(默认的)(常用)
- prototype:多例的(常用)
- request:作用于web的请求请求范围
- session:作用于wdb的会话的请求范围
- globe-session:作用于集群环境的范围
单例的
<bean id="accountService" class="itheima.factory.StaticFactory" factory-method="getAccountService" scope="singleton"></bean>
多例的
<bean id="accountService" class="itheima.factory.StaticFactory" factory-method="getAccountService" scope="singleton"></bean>
在Client中再实例化一个对象,然后看看单例与多例的区别
public static void main(String[] args) {
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
//通过id获取Bean对象
IAccountService as = (IAccountService) factory.getBean("accountService");
IAccountService as1 = (IAccountService) factory.getBean("accountService");
System.out.println(as == as1);
//as.saveAccount();
}
}
单例的结果:
AccountServiceImpl创建了
true
多例的结果:
AccountServiceImpl创建了
AccountServiceImpl创建了
false
-
Bean对象的生命周期
-
单例的
1.创建:容器创建时 对象出生
2.活着:只要容器还在 对象一直活着
3.销毁:容器销毁,对象消亡.
在 AccountServiceImpl 中 新增 init() 和destory()方法
public class AccountServiceImpl implements IAccountService {
/**
* 模拟保存账户
*/
public AccountServiceImpl() {
System.out.println("AccountServiceImpl创建了");
}
@Override
public void saveAccount() {
System.out.println("service中的saveAccount执行了");
}
public void init(){
System.out.println("对象初始化了");
}
public void destory(){
System.out.println("对象销毁了");
}
}
在Client中执行
public static void main(String[] args) {
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
//通过id获取Bean对象
IAccountService as = (IAccountService) factory.getBean("accountService");
//IAccountService as1 = (IAccountService) factory.getBean("accountService");
//System.out.println(as == as1);
as.saveAccount();
}
}
得到结果:
AccountServiceImpl 创建了
AccountServiceImpl 对象初始化了
service中的saveAccount执行了
为什么desrory方法没有执行 main方法是一切应用程序的入口 当main方法结束后 当前应用中线程占用的内存全部释放 也包括我们的容器 但是此时还没有调用销毁方法 就已经释放了内存
如果想让销毁方法出现 需要手动释放
(然后我发现我的Client中使用的是BeanFactory这个接口来获取核心容器的,下面改成为用ApplicationContext的方式)
public static void main(String[] args) {
//获取核心容器对象
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取Bean对象
IAccountService accountService = (IAccountService) classPathXmlApplicationContext.getBean("accountService");
accountService.saveAccount();
//手动关闭容器
classPathXmlApplicationContext.close();
}
}
输出结果
AccountServiceImpl 创建了
AccountServiceImpl 对象初始化了
service中的saveAccount执行了
AccountServiceImpl对象销毁了
![image.png](https://img.haomeiwen.com/i13543313/4d005be8bc2641b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
如果 以这样的方式获取核心容器对象,是得不到close方法的.
ApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
因为我们把ClassPathXmlApplicationContext看成了接口类型,如果看成父类对象的时候,只能调用父类方法,(多态性)所以要让这个对象是自己的对象.
所以用了ClassPathXmlApplicationContext自己的对象
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
- 多例的
1.创建:使用的时候创建
2.活着:在使用过程中就一直活着
3.销毁:当对象长时间不用且没有别的对象引用时,由Java的垃圾回收期来执行回收.
spring的依赖注入
-
依赖注入是指 Dpendency Injection
-
IOC的作用 是降低程序间的耦合
依赖关系的管理 是指依赖关系以后都交给spring来维护,在当前类所需要用到其他类都由spring为我们提供,我们只需要在配置文件中说明依赖关系的维护,这就称之为依赖注入 -
依赖注入
- 能注入的数据有三类
- 基本类型和String
- 其他bean类型 (在配置文件中或者注解配置过的bean
- 复杂类型/集合类型
- 能注入的数据有三类
-
注入的方式有三种
-
构造函数注入
-
由set方法提供
-
由注解提供
- 构造函数注入
使用的标签是 constructor-arg
标签出现的位置:bean标签内部
标签中的属性
type :用于要指定所注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型(如果参数1和参数3是同一类型,它不会知道是赋值给哪一个参数,所以不能独立的实现注入的功能)
index :用于指定要注入的数据给构造函数中指定位置的参数赋值,索引的位置是从0开始(但这个方式要记住参数的位置,所以有点麻烦)
name : 用于指定给构造函数中指定名称的参数赋值(常用)
==================以上三个用于指定给构造函数中哪个参数赋值===================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据,它指的就是在spring的Ioc核心容器中出现过的bean对象.
- 构造函数注入
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功 弊端:改变了类或者bean对象的实例化方式,使我们在创建对象时如果用不到这些数据也必须提供
-
老规矩,新建一个工程,spring01_eesy_05DI,copy上一个工程的代码.先在AccountServiceImpl中创建构造函数
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl() {
System.out.println("AccountServiceImpl 创建了");
}
public AccountServiceImpl(String name,Integer age,Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
System.out.println("AccountServiceImpl 创建了"+name+"=="+age+"==="+birthday);
}
在bean,xml中,为参数赋值
第一个参数
<constructor-arg name="name" value="小强"></constructor-arg>
第二个参数赋值的时候,赋值的12,和Interge类型并不匹配,在xml中 value 的值都是字符串,是spring它把这些数据类型帮我们转换了.
![](https://img.haomeiwen.com/i13543313/a89a65bb4901f796.png)
第三个参数 Date 赋值,如果写成2019-09-22 是无法为Date类型赋值的,因为它是字符串类型
所以构造函数赋值,只能赋值基本类型和字符串
<constructor-arg name="birthday" value="2019-09-2"></constructor-arg>
那要如何为Date类型赋值呢?可以用引用.
我们可以配置一个日期对象,它会通过class的全限定名反射创建对象并存入spring容器中,通过now id取出
<bean id ="now" class="java.util.Date"></bean>
所以 beam.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="小强"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id ="now" class="java.util.Date"></bean>
</beans>
-
Set方法注入(更常用)
- 涉及的标签:property
- 出现的位置:bean标签内部
- 标签的属性:
- name:用于指定注入时所调用的set方法名称,
- value:用于提供基本类型和Sting类型的数据
- ref:用于指定其他bean类型数据.它指的就是在spring的Ioc核心容器中出现过的bean对象.
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:如果有某个成员必须有值,则获取对象是有可能set方法方法没有执行.
Copy一个AccountServiceImpl,为成员变量生成set方法.
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl2() {
System.out.println("AccountServiceImpl2 创建了");
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount() {
System.out.println("service中的saveAccount执行了"+name + age + birthday);
}
}
在Beans.xml中,写法如下
<bean id="now" class="java.util.Date"></bean>
<bean id="accountService2" class="itheima.service.impl.AccountServiceImpl2">
<property name="name" value="xiaoming"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
- name:用于指定注入时所调用的set方法名称,如果把setName 改成为setUsername的话,beam.xml中的自动联想也从name变成username,由此可见name属性用于指定注入时所调用set方法名称,
执行结果
AccountServiceImpl2 创建了
service中的saveAccount执行了xiaoming18Mon Sep 23 10:35:44 GMT+08:00 2019
- 复杂类型/集合类型的注入
再复制一个accountserviceImpl3,加上数组,list,map,set.properties等数据的成员变量,并生成set方法.
public class AccountServiceImpl3 implements IAccountService {
private String[] mystrs;
private List<String> mylist;
private Map<String, String> mymap;
private Set<String> myset;
private Properties myprops;
public AccountServiceImpl3() {
System.out.println("AccountServiceImpl2 创建了");
}
public void setMystrs(String[] mystrs) {
this.mystrs = mystrs;
}
public void setMylist(List<String> mylist) {
this.mylist = mylist;
}
public void setMymap(Map<String, String> mymap) {
this.mymap = mymap;
}
public void setMyset(Set<String> myset) {
this.myset = myset;
}
public void setMyprops(Properties myprops) {
this.myprops = myprops;
}
public void saveAccount() {
System.out.println("service中的saveAccount执行了");
//数组类型需要tostring方法转换一下 否则直接打印是内存地址
System.out.println(Arrays.toString(mystrs));
System.out.println(mylist);
System.out.println(mymap);
System.out.println(myset);
System.out.println(myprops);
}
}
在beam.xml中,因为都不是基本类型和String类型,所以<property>标签里的value赋值也不再有意义
<property name="list" value="xiaoming"></property>
<properties>标签内,有更多的子标签提供使用.
![](https://img.haomeiwen.com/i13543313/4186859171af4258.png)
array类型,array标签,value赋值
<property name="mystrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
list类型,list标签,value赋值
<property name="mylist">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
set类型,set标签,value赋值
<property name="mylist">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
其中,如果把list,array,set的标签呼唤,也能正常运行,
map类型,用map标签,里面有entry标签,entry有key和value两个属性.
<property name="mymap">
<map>
<entry key="AAA" value="111"></entry>
<entry key="BBB" value="222"></entry>
<entry key="CCC" value="333"></entry>
</map>
</property>
\之前这样的写法是有问题的,输出的结果格式会不一样,然后发现自己写错了
<property name="myset">
<set>
<value>
AAA,BBB,CCC
</value>
</set>
</property>
properties类型 ,里面有<props>标签,props里有<prop>的子标签,赋值只有一个属性,是<key>
<property name="myprops">
<props>
<prop key="AAA">aaa</prop>
<prop key="BBB">bbb</prop>
<prop key="CCC">ccc</prop>
</props>
</property>
如果map标签与prop标签呼唤,执行结果也不会有问题.
<property name="myprops">
<map>
<entry key="AAA" value="111"></entry>
<entry key="BBB" value="222"></entry>
<entry key="CCC" value="333"></entry>
</map>
</property>
<property name="mymap">
<props>
<prop key="AAA">aaa</prop>
<prop key="BBB">bbb</prop>
<prop key="CCC">ccc</prop>
</props>
</property>
网友评论