一、IOC的概念和作用
1基本概念
这里我们先简单总结下前文中创建对象的两种方式
- 前文中两种不同的方式来创建对象就是一个典型的对比
- 前一种获取对象的方式是主动的,直接new
,将应用和资源直接绑定在一起
- 后一种方式,把创建对象交给工厂,我们只需要和工厂打交道即可
- 由工厂来控制资源和提供资源,减少程序间的耦合性
二、使用spring框架来实现前文的解耦案例
1.项目环境搭建
- 创建maven项目
- 复制上文的主要代码
- 设置
pom.xml
文件,导入spring
的jar包坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
- 导入后,可以发现相关jar包的信息
- 新建资源文件
bean.xml
- 导入约束
- 接下来把对象的创建交给spring来管理,在配置文件中进行配置
<!--
id:唯一标志
class:bean类的全限定类名
-->
<bean id="accountService" class="com.xpt.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.xpt.dao.impl.AccountDaoImpl"></bean>
- 创建对象,分为如下两步
- 获取
core
对象 - 根据配置文件中的
id
获取对象
- 获取
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao dao = (IAccountDao) ac.getBean("accountDao");
System.out.println(as);
System.out.println(dao);
}
}
- 可以看到上述的过程代码非常的少,从配置文件解析到对象创建全都由spring做了
2.ApplicationContext的3个常用实现类
ClassPathXmlApplicationContext
:加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了。(相较于下一种更推荐使用)FileSystemXmlApplicationContext
:可以加载磁盘任意路径下的配置文件,但必须有访问权限AnnotationConfigApplicationContext
:它是用于读取注解创建容器的。
3.核心容器的两个接口引发的问题
3.1ApplicationContext
它在创建核心容器时,对象创建的策略是采用立即加载的方式,也就是说,只要一读取完配置文件就能马上创建配置文件种配置好的对象。
- 验证:在bean类中定义一个空参的构造函数
public class AccountServiceImpl implements IAccountService {
IAccountDao accountDao = new AccountDaoImpl();
public AccountServiceImpl(){
System.out.println("对象已经被创建了");
}
public void saveAccount() {
accountDao.saveAccount();
}
}
Client
类中注释掉后面的对象获取
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// //2.根据id获取对象
// IAccountService as = (IAccountService) ac.getBean("accountService");
// IAccountDao dao = (IAccountDao) ac.getBean("accountDao");
// System.out.println(as);
// System.out.println(dao);
}
}
- 运行效果:
3.2BeanFactory
它在构建核心容器的时候,创建对象的策略是采用延迟加载的方式,也就是说,什么时候根据
id
获取对象了,什么时候才真正的创建对象。
- 演示:修改代码
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService) factory.getBean("accountService");
System.out.println(as);
- 断点调试
- 继续执行
3.3使用选择
上面演示了两种容器接口创建对象的不同时机,那在实际开发中如何选择呢?单例对象的情况下,对象只被创建一次,然后多次使用,这种时候肯定希望立即加载,所以ApplicationContext
更适用于单例对象,那么显然BeanFactory
更适用于多例对象使用
三、spring对bean的管理细节
1.创建bean的三种方式
1.1方式一:使用默认构造函数
在spring的配置文件中使用
bean
标签,配以id
和class
属性,且没有其他的属性和标签时。采用的就算默认构造函数创建bean
对象,如果此时类中没有默认构造函数,则对象无法创建
- 演示效果
- 配置文件
<bean id="accountService" class="com.xpt.service.impl.AccountServiceImpl"></bean>
- 设置bean类无默认构造函数
public class AccountServiceImpl implements IAccountService {
//增加参数 使当前类没有默认的构造函数
public AccountServiceImpl(String name){
System.out.println("对象已经被创建了");
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了");
}
}
- 测试类
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取对象
IAccountService as = (IAccountService) ac.getBean("accountService");
System.out.println(as);
}
}
- 运行结果报错
1.2方式二:使用实例工厂的方法创建对象
使用普通工厂中的方法创建对象
或者使用某个类中的方法创建对象,并存入spring容器
- 演示
- 配置
<bean id="instanceFactory" class="com.xpt.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 模拟实例工厂类
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
- 其余部分代码不变,之间运行测试类,结果成功创建对象
1.3方式三:使用静态工厂的静态方法来创建对象
使用静态工厂的静态方法来创建对象
或者使用某个类的静态方法创建对象,并存入spring容器
- 演示
- 配置
<bean id="accountService" class="com.xpt.factory.StaticFactory"
factory-method="getAccountService" ></bean>
- 静态工厂和静态方法
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
- 其余代码不变,直接运行测试类,成功创建对象
2.bean的作用范围
2.1默认作用范围( singleton:单例的)
默认作用范围是单例的,即创建的bean都是一个对象
- 演示
2.2设置多例的作用范围( prototype)
- xml配置
- 运行测试
2.3小结
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
- singleton:单例的(默认值)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
2.4global-session简单了解
3.bean对象的生命周期
3.1单例对象的生命周期
-
出生:当容器创建时对象出生
-
活着:只要容器还在,对象一直活着
-
死亡:容器销毁,对象消亡
-
总结:单例对象的生命周期和容器相同
-
演示
-
xml配置
- bean类中添加
init
和destroy
方法
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了");
}
public void init() {
System.out.println("对象创建");
}
public void destroy() {
System.out.println("对象销毁");
}
- 测试类改造:
- 结果:
3.2多例对象的生命周期
- 出生:当我们使用对象时spring框架为我们创建(延迟加载,spring会智能的判断对象是单例还是多例的,然后智能选择加载方式,这里回想前面核心容器的两个不同的加载方式)
- 活着:对象只要是在使用过程中就一直活着。
- 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
- 验证
- xml配置
- 其余部分不变,测试代码结果
四、spring的依赖注入
1.基本概念
- 依赖注入:Dependency Injection
- IOC的作用: 降低程序间的耦合(依赖关系)
- 依赖关系的管理:以后都交给spring来维护
- 在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明,依赖关系的维护:就称之为依赖注入。
2注入数据类型和方式
2.1三类数据
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
2.2三种注入方式
- 第一种:使用构造函数提供
- 第二种:使用set方法提供
- 第三种:使用注解提供
3第一种注入方式:使用构造函数提供
下面演示基于这种方式的配置过程
3.1bean类提供带参的构造函数
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了:"+name+","+age+","+birthday);
}
}
-
注意
-
现在上述的bean类中没有默认的构造函数,所以在配置的时候肯定无法使用默认的构造函数来创建bean对象
- 所以为了解决上面的问题就需要进行构造函数的依赖注入的配置
3.2配置方法
- 使用的标签:
constructor-arg
- 标签出现的位置:bean标签的内部
- 标签中的属性
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
- name:用于指定给构造函数中指定名称的参数赋值(推荐)
- 以上三个用于指定给构造函数中哪个参数赋值
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
3.2.1配置实例
3.3运行效果
- 代码
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取对象
IAccountService as = (IAccountService) ac.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
- 结果
- 顺利执行
3.4优势和弊端
- 优势: 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
- 弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
4第二种注入方式:使用set方法提供(更常用)
4.1bean类提供属性的set方法
- 只提供set方法即可
4.2配置方法
- 涉及的标签:property
- 出现的位置:bean标签的内部
- 标签的属性
- name:用于指定注入变量的名称
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
4.2.1配置实例
4.3运行效果
- 代码同上
4.4优势和弊端
- 优势:创建对象时没有明确的限制,可以直接使用默认构造函数
- 弊端: 如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
5.复杂数据类型(集合类型)的注入案例
使用set方式注入复杂数据类型
5.1含有复杂数据类型的bean类
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(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
5.2配置方法
- 配置实例
<bean id="accountService03" class="com.xpt.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>array</value>
</array>
</property>
<property name="myList">
<list>
<value>list</value>
</list>
</property>
<property name="mySet">
<set>
<value>set</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="mapA" value="valueA"></entry>
<entry key="mapB" value="valueB"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="propA">valueA</prop>
</props>
</property>
</bean>
5.2.1标签小结
- 用于给List结构集合注入的标签: list array set
- 用于个Map结构集合注入的标签: map props
- 结构相同,标签可以互换