IOC(DI) - 控制反转(依赖注入)
所谓的IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不再需要关注对象的创建和生命周期的管理,而是在需要时由Spring框架提供,这个由spring框架管理对象创建和生命周期的机制称之为控制反转。而在 创建对象的过程中Spring可以依据配置对对象的属性进行设置,这个过称之为依赖注入,也即DI。
IOC的入门案例
创建一个java项目
spring并不是非要在javaweb环境下才可以使用,一个普通的java程序中也可以使用Spring。
导入Spring的libs目录下IOC相关的jar包
计算机生成了可选文字:SpringDay01 01 IOC First D:XssmXSprlngDavOlXSprln
commons-logging 1.0.4.jar 1 个 其 他 包 spring beans-4.3.25.RELEASE.jar
spr.ng context 4.3.25.RELEASE.Jar spr.ng context support 4.3.25.RELEASE.Jar
spr.ng core 4.3.25.RELEASE.Jar spr.ng expresslon 4.3.25.RELEASE.Jar
spring test-4.3.25.RELEASE.jar 6 个 Spring 相 关 包
创建Spring的配置文件
Spring采用xml文件作为配置文件,xml文件名字任意,但通常都取名为applicationContext.xml,通常将该文件放置在类加载的目录里下(src目录),方便后续使用。
<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类,并在spring中进行配置交由spring来管理
<?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-3.2.xsd">
<bean id="person" class="cn.tedu.beans.Person"></bean>
在程序中通过Spring容器获取对象并使用
/**
- SpringIOC方式创建并管理bean
*/
@Test
public void test02(){
//1.初始化Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.通过Spring容器获取bean
Person p = (Person) context.getBean("person");
p.eat();
p.say();
//3.关闭Spring容器
((ClassPathXmlApplicationContext)context).close();
}
IOC的实现原理
在初始化一个Spring容器时,Spring会去解析指定的xml文件,当解析到其中的标签时,会根据该标签中的class属性指定的类的全路径名,通过反射创建该类的对象,并将该对象存入内置的Map中管理。其中键就是该标签的id值,值就是该对象。
之后,当通过getBean方法来从容器中获取对象时,其实就是根据传入的条件在内置的Map中寻找是否有匹配的键值,如果有则将该键值对中保存的对象返回,如果没有匹配到则抛出异常。
计算机生成了可选文字:
ApplicationContext context = new ClassPathXmlApplicationContext (“applicationContext. xml”)
〈 bean i d: person01" class: cn. tedu. domain. Person01" 〉 〈 /bean 〉
Class clz = Class.forName("cn.tedu.domain.Person01 " ) 冫
0b ect 0b’ = clz.newlnstance Person01 P Map( person01 •obj
(Person01) context. getBean(“person01”) Map( person01 •obj
(Person01) map. get (“person01”) , Person01 P P. eat 0 ; / / 吃 .
P. say() ; / / 说 ( (ClassPathXmIAppl icationContext) context) . close 0 Map(
person01 •obj map. clear 0 map = null 关 闭 容 器 本 身
由此可以推测而知:
默认情况下,多次获取同一个id的bean,得到的将是同一个对象。
不可以配置多个id相同的bean
可以配置多个id不同但class相同的bean
IOC获取对象的方式
通过context.getBean()方法获取bean时,可以通过如下两种方式获取:
传入id值
传入class类型
通过class方式获取bean时,如果同一个类配置过多个bean,则在获取时因为无法确定到底要获取哪个bean会抛出异常。
而id是唯一的,不存在这样的问题,所以建议大家尽量使用id获取bean。
/**
-
获取对象的方式
-
通过id获取bean
-
如果找不到,抛异常NoSuchBeanDefinitionException
-
如果找到唯一的,返回对象
-
因为id不重复,不可能找到多个
-
通过class获取bean
-
如果找不到,抛出异常NoSuchBeanDefinitionException
-
如果找到唯一,返回对象
-
如果找到多个,抛出异常NoUniqueBeanDefinitionException
*/
@Test
public void test04(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取对象方式1:通过id获取
//Person p = (Person)context.getBean("person");
//p.eat();
//p.say();
//获取对象方式2:通过class获取
Person p = context.getBean(Person.class);
p.eat();
p.say();
((ClassPathXmlApplicationContext)context).close();
}
SpringIOC在通过class获取bean时,如果找不到该类型的bean还会去检查是否存在该类型的子孙类型的bean,如果有则返回,如果找不到或找到多个则抛出异常。这符合java面向对象思想中的多态的特性。
@Test
public void test02() {
//1.初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//2.获取Bean
JavaTeacher jt = (JavaTeacher) context.getBean(Teacher.class);
System.out.println(jt);
}
别名标签
在 Spring中提供了别名标签可以为配置的起一个别名,要注意的是这仅仅是对指定的起的一个额外的名字,并不会额外的创建对象存入map。
/**
-
别名标签
-
可以通过别名标签为bean的id起一个别名,此后除了可以通过别名指代id
*/
@Test
public void test05(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//Person p = (Person) context.getBean("person");
Person p = (Person) context.getBean("pers");
System.out.println(p);
((ClassPathXmlApplicationContext)context).close();
}
Spring创建对象的方式
通过类的无法构造方法创建对象
在入门案例中使用的就是这种方式。
当用最普通方式配置一个时,默认就是采用类的无参构造创建对象。
在Spring容器初始化时,通过上配置的class属性反射得到字节码对象,通过newInstance()创建对象
Class c = Class .forName(“类的全路径名称”)
Object obj = c.newInstance()
这种方式下spring创建对象,要求类必须有无参的构造,否则无法通过反射创建对象,会抛出异常。
public class Person {
public Person(){
System.out.println("Person被创建了..");
}
}
/**
-
SpringIOC创建对象方式1 - 反射创建对象
-
bean必须有无参构造才可以
*/
@Test
public void test01() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p = (Person) context.getBean("person");
System.out.println(p);
((ClassPathXmlApplicationContext)context).close();
}
通过指定构造器创建对象
Spring默认调用clz.newInstance()方法创建对象,而这个方法本质上是调用无参构造器创建对象。如果一个类没有无参构造器,则此时会抛出异常。
可以通过配置bean的参数,实现控制spring容器通过指定构造器创建对象。
public class Person {
public Person(String name,int age){
System.out.println("Person init.."+name+".."+age+".."+this);
}
}
@Test
public void test01(){
//1.初始化Spring容器
ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
//2.获取bean
Person person = (Person) context.getBean("person");
System.out.println(person);
//3.关闭容器
((ClassPathXmlApplicationContext)context).close();
}
通过工厂创建对象
如果创建出来的对象需要经过若干设置后才能使用,spring也支持通过工厂创建bean。
所谓的工厂设计模式就是通过一个工厂类将创建对象的细节封装起来,之后通过工厂创建对象,简化创建对象的过程。
通过静态工厂创建对象
目标类
计算机生成了可选文字:public class NetConn public void load()f
System.out.println( " 加 载 配 置 文 件 一 " ) public void ping()f
System.out.println(" 测 试 网 络 连 通 性 . public void conn()f
System.out.println(" 连 接 网 络 . public void login()f System.out.println(" 登 陆 .
public void sendData()f System.out.println(" 发 送 数 据 .
工厂类
计算机生成了可选文字:public class NetConnStaticFactory private NetConnStaticFactory()O
public static Netconn getlnstance()f new NetConn(); NetConn c 0 n n
conn.load(); conn.ping(); conn.conn(); conn.login(); conn;
通过静态工厂配置bean
计算机生成了可选文字:〈 bean id:"netConn’
class: "cn . tedu . domain . NetConnStaticFactory
factory-method:’ getlnstance’ / 〉
测试方法
计算机生成了可选文字:@Test public void teste2()f / / . 剪 s r 讠 ” g 容 器
ApplicationContext context 卜 new ClassPathXm1ApplicationContext / / 2 . bea ”
(NetConn) context .getBean( s: "netConn’ ) ; NetConn c 0 n n
0 n n · sendData(); / / 3 . 卖 / 万 容 器
((ClassPathXm1App1icationContext)context) .close();
原理图
计算机生成了可选文字:静 态 工 厂 配 置 Bean / / 7 . 始 化 r 讠 四 容 ApplicationContext context
加 载 Sp ’ ing 配 置 文 件 发 标 签 < bean id: “netConn ’
new ClassPathXm1App1icationContext ( " applicationContext2 . xml” )
class: “ ’ n . tedu . domain . NetConnStaticFactory•
factory-method: " getlnstance" / >
发 现 上 面 配 置 了 fa ( to ’ y . method 知 道 这 是 一 个 静 态 工 厂
调 用 静 态 工 厂 类 的 指 定 静 态 方 法 创 建 对
NetConn conn = NetConnStatlcFactory.getlnstance(), 加 载 配 置 又 件 . .
试 网 络 连 通 性 . . 连 接 网 络 . . 登 陆 . . 将 创 建 出 来 的 对 象 存 入 Sp ’ ing 内 部 M ap
map.put(“netConn”,conn); / / 2 . an (NetConn) context.getBean(“netConn”);
NetConn c on n 从 Sp ’ ing 内 部 Map 中 获 取 对 井 返 回 return map.get(“netConn”);
conn.sendData();
实例工厂创建对象
实例工厂和静态工厂类似,只不过实例工厂提供的方法不是静态的。
Spring需要先创建出实例工厂的对象,在调用实例工厂对象上指定的普通方法来创建对象。所以实例工厂也需要配置到Spring中管理。
目标类
计算机生成了可选文字:public class NetConn public void load()f
System.out.println( " 加 载 配 置 文 件 一 " ) public void ping()f
System.out.println(" 测 试 网 络 连 通 性 . public void conn()f
System.out.println(" 连 接 网 络 . public void login()f System.out.println(" 登 陆 .
public void sendData()f System.out.println(" 发 送 数 据 .
工厂类
计算机生成了可选文字:* 实 伢 工 厂 public class NetConnInstanceFactory {
public NetConn getlnstance()f new NetConn(); NetConn c 0 n n conn.load();
conn.ping(); conn.conn(); conn.login(); conn;
通过实例工厂配置bean
计算机生成了可选文字:〈 bean id:"netConnInstanceFactory
. tedu . domain . NetConnInstanceFactory’ / 〉 〈 bean id:"netConn’
factory-bean:"netConnInstanceFactory factory-method:’ getlnstance’ / 〉
测试方法
计算机生成了可选文字:* 实 伢 工 厂 @Test public void teste3()f / / . 剪 s r 讠 ” g 容 器
ApplicationContext context new ClassPathXm1ApplicationContext / / 2 . bea ”
(NetConn) context .getBean( s: "netConn’ ) ; NetConn c 0 n n
c 0 n n · sendData(); / / 3 . 卖 / 万 容 器
((ClassPathXm1App1icationContext)context) .close();
原理图
计算机生成了可选文字:实 例 工 厂 配 置 Bean / / 7 . 始 化 r 讠 四 容 ApplicationContext context
加 载 Sp ’ ing 配 置 文 件 发 标 签
new ClassPathXm1App1icationContext ( " applicationContext2 . xml" ) ;
class: “ ’ n . tedu . domain . NetConnInstanceFactory" / 〉
< bean id:“netConnInstanceFactory” factory-method: “ getlnstance" / 〉
< bean id:"netConn’ factory-bean:“netConnInstanceFactory”
发 反 射 创 建 netConnInstanceFactory, 井 存 入 Springs 器 内 部 Map
Class clz = class.forName(“cn.tedu.domain.NetConnlnstanceFactory”)
Object factory = cl 乙 new nstance(), map.put(“netConnlnstanceFactory”,factory)
发 发 现 配 了 fa ( to . bean 和 fa ( to . method , 知 道 是 一 个 实 例 工 厂
NetConnlnstanceFactory factory = map.get(“netConnlnstanceFactory”)
NetConn conn = factory.getlnstance(), 加 载 配 置 又 件 . . 试 网 络 连 通 性 . .
连 接 网 络 . . 登 陆 . . 将 创 建 出 来 的 对 存 入 Sp ’ ing 内 部 Map map.put("netConn ,conn);
/ / 2 . an (NetConn) context.getBean(“netConn”); NetConn c on n
从 Sp ’ ing 内 部 Map 中 获 取 对 井 返 回 return map.get(“netConn”); conn.sendData();
Spring工厂创建对象
Spring内置了工厂接口FactoryBean,也可以通过实现这个接口来开发Spring工厂
目标类
计算机生成了可选文字:public class NetConn public void load()f
System.out.println( " 加 载 配 置 文 件 一 " ) public void ping()f
System.out.println(" 测 试 网 络 连 通 性 . public void conn()f
System.out.println(" 连 接 网 络 . public void login()f System.out.println(" 登 陆 .
public void sendData()f System.out.println(" 发 送 数 据 .
工厂类
/**
- Spring工厂
*/
public class NetConnSpringFactory implements FactoryBean {
/**
* 生产目标对象的方法
*/
@Override
public NetConn getObject() throws Exception {
NetConn conn = new NetConn();
conn.load();
conn.ping();
conn.conn();
conn.login();
return conn;
}
/**
* 获取目标对象类型的方法
*/
@Override
public Class<?> getObjectType() {
return NetConn.class;
}
/**
* 用来控制目标对象是否是单例的
*/
@Override
public boolean isSingleton() {
return true;
}
}
通过Spring工厂配置bean
测试方法
/**
- Spring工厂
*/
@Test
public void test04(){
//1.初始化Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext4.xml");
//2.获取bean
NetConn conn = (NetConn) context.getBean("netConn");
conn.sendData();
//3.关闭容器
((ClassPathXmlApplicationContext)context).close();
}
原理图
计算机生成了可选文字:Spring 工 厂 配 置 Bean / / 7 . 始 化 r 讠 四 容 ApplicationContext context
加 载 Sp ’ ing 配 置 文 件 发 标 签
new ClassPathXm1App1icationContext ( " applicationContext2 . xml" )
< bean id:“netConn” class:“cn.tedu.domain.NetConnSpringFactory”/)
试 图 反 射 创 建 对 · 此 时 发 现 这 个 类 实 现 过 Fa 。 ryBean 接 口 , 知 道 这 是 一 个 Sp ’ ing 工 厂
创 建 目 标 对 Class clz = class.forName(“cn.tedu.domain.NetConnSpringFactory”)
NetConnSpringFactory factory = cl 乙 new nstance(),
NetConn conn = factory.getObject(), 加 载 配 置 又 件 . . 试 网 络 连 通 性 . . 连 接 网 络 . .
登 陆 . . 将 创 建 出 来 的 对 象 存 入 Sp ’ ing 内 部 M ap map.put("netConn ,conn);
/ / 2 . 衄 an (NetConn) context.getBean(“netConn”); NetConn c on n
从 Sp ’ ing 内 部 Map 中 获 取 对 象 井 返 回 return map.get(“netConn”); conn.sendData();
单例和多例
Spring容器管理的bean在默认情况下是单例的,也即,一个bean只会创建一个对象,存在内置 map中,之后无论获取多少次该bean,都返回同一个对象。
Spring默认采用单例方式,减少了对象的创建,从而减少了内存的消耗。
但是在实际开发中是存在多例的需求的,Spring也提供了选项可以将bean设置为多例模式。
<?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-3.2.xsd">
<!--
scope属性控制当前bean的创建模式:
singleton:则当前bean处在单例模式中,默认就是此模式
prototype:则当前bean处在多例模式中
-->
<bean id="cart" class="cn.tedu.beans.Cart" scope="prototype"></bean>
bean在单例模式下的生命周期:
bean在单例模式下,spring容器启动时解析xml发现该bean标签后,直接创建该bean的对象存入内部map中保存,此后无论调用多少次getBean()获取该bean都是从map中获取该对象返回,一直是一个对象。此对象一直被Spring容器持有,直到容器退出时,随着容器的退出对象被移除出容器。
bean在多例模式下的生命周期:
bean在多例模式下,spring容器启动时解析xml发现该bean标签后,只是将该bean进行管理,并不会创建对象,此后每次使用 getBean()获取该bean时,spring都会重新创建该对象返回,每次都是一个新的对象。这个对象spring容器并不会持有,什么时候销毁取决于用户程序本身。
待办事项 实验:通过断点调试模式 ,观察spring单例和多例的bean执行构造的过程
1
略
懒加载机制
Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少时问题不大,但一旦bean非常多时,spring需要在启动的过程中花费大量的时间来创建bean 花费大量的空间存储bean,但这些bean可能很久都用不上,这种在启动时在时间和空间上的浪费显得非常的不值得。
所以Spring提供了懒加载机制。所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗。
懒加载机制只对单例bean有作用,对于多例bean设置懒加载没有意义。
懒加载只是延后了对象创建的时机,对象仍然是单例的。
懒加载的配置方式:
为指定bean配置懒加载
<?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-3.2.xsd"
>
<bean id="cart" class="cn.tedu.beans.Cart" lazy-init="true"></bean>
为全局配置懒加载
<?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-3.2.xsd"
default-lazy-init="true"
>
<bean id="cart" class="cn.tedu.beans.Cart"></bean>
**如果同时设定全局和指定bean的懒加载机制,且配置不相同,则对于该bean局部配置覆盖全局配置。
待办事项 实验:通过断点调试,验证懒加载机制的执行过程
package cn.tedu.beans;
public class Cart {
public Cart() {
System.out.println("Cart init...");
}
}
<?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-3.2.xsd"
>
<bean id="cart" class="cn.tedu.beans.Cart" lazy-init="true"></bean>
@Test
/**
- SpringIOC 懒加载机制
*/
public void test10(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Cart cart1 = (Cart) context.getBean("cart");
Cart cart2 = (Cart) context.getBean("cart");
System.out.println(cart1 == cart2);
}
配置初始化和销毁的方法
在Spring中如果某个bean在初始化之后 或 销毁之前要做一些 额外操作可以为该bean配置初始化和销毁的方法 ,在这些方法中完成要功能。
待办事项 实验:通过断点调试模式,测试初始化方法 和 销毁方法的执行
package cn.tedu.beans;
public class ProdDao {
public ProdDao() {
System.out.println("ProdDao 被创建。。。");
}
public void init(){
System.out.println("init。。连接数据库。。。。。");
}
public void destory(){
System.out.println("destory。。断开数据库。。。。。");
}
public void addProd(){
System.out.println("增加商品。。");
}
public void updateProd(){
System.out.println("修改商品。。");
}
public void delProd(){
System.out.println("删除商品。。");
}
public void queryProd(){
System.out.println("查询商品。。");
}
}
<?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-3.2.xsd"
>
<bean id="prodDao" class="cn.tedu.beans.ProdDao"
init-method="init" destroy-method="destory"></bean>
@Test
/**
- SpringIOC 初始化和 销毁方法
*/
public void test11(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ProdDao prodDao = (ProdDao) context.getBean("prodDao");
prodDao.addProd();
context.close();
}
**Spring中关键方法的执行顺序:
在Spring创建bean对象时,先创建对象(通过无参构造或工厂),之后立即调用init方法来执行初始化操作,之后此bean就可以哪来调用其它普通方法,而在对象销毁之前,spring容器调用其destory方法来执行销毁操作。