1. Inversion of Control (IoC)
控制反转,也称为依赖注入(Dependency Injection, DI)。这里的反转指获得依赖对象的过程被反转,传统模式中我们在类内部主动创建依赖对象(直接new一个依赖对象)。用控制反转的方式,我们不需要主动实例化依赖类,而是通过依赖注入来实现,即IoC容器帮我们创建好对象,当我们需要依赖对象时,向IoC容器获取。这种方式减少了类之间的耦合。
接下来以程序为例,首先我们不使用控制反转,即我们在类内部主动创建依赖对象(直接new一个依赖对象)
比如,现在有三个类:
-
AccountDaoImpl:账户持久层实现类,用户的具体操作对数据库进行增删改查
-
AccountServiceImpl:账户业务层实现类,用户通过这个类操作业务
-
Client:模拟一个表现层,用于调用业务层
/**
* 1 账户持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户");
}
}
/**
* 账户持久层接口
*/
public interface IAccountDao {
void saveAccount();
}
/**
* 2 账户业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
// 通过创建一个AccountDaoImpl来实际操作数据库,高耦合
private IAccountDao accountDao = new AccountDaoImpl();
// 假设现在的操作是保存账户
public void saveAccount(){
accountDao.saveAccount(); // 通过调用AccountDao中方法实现
}
}
/**
* 账户业务层接口
*/
public interface IAccountService {
void saveAccount();
}
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
// 通过创建一个AccountServiceImpl对象来开始具体业务,高耦合
IAccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
从上面代码可以看到,Client类需要主动创建AccountServiceImpl对象来展开业务,而AccountServiceImpl类需要主动创建AccountDaoImpl类来对数据库进行操作完成具体业务。这种行为使得类与类之间高耦合,具体表现:如果我们没有AccountDaoImpl类(在项目中删除AccountDaoImpl类),再运行程序,此时会报错:Error:(4, 28) java: 程序包com.itheima.dao.impl不存在。但这是一个编译错误,即程序并没有编译成功,这是我们不希望看到的,我们希望当缺失类时报的是运行时错误。
所以为了减少耦合,我们会使用工厂模式来创建对象,即在上述三个类的基础上,增加一个工厂类,用于创建AccountServiceImpl和AccountDaoImpl对象:
/**
* BeanFactory:根据配置文件创建service和dao对象
*/
public class BeanFactory {
// 定义一个Properties对象
private static Properties props;
// 使用静态代码块为Properties对象赋值
static{
try {
// 实例化对象
props = new Properties();
// 获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
} catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据beanName获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try{
// 根据beanName实例化对象
String beanPath = props.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();
} catch(Exception e){
e.printStackTrace();
}
return bean;
}
}
// bean.properties配置文件内容
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
同时更改Client和AccountDaoImpl类中代码
public class Client {
public static void main(String[] args) {
// IAccountService as = new AccountServiceImpl();
// 通过工厂类创建AccountServiceImpl类,减少耦合
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
as.saveAccount();
}
}
public class AccountServiceImpl implements IAccountService {
// private IAccountDao accountDao = new AccountDaoImpl();
// 通过工厂类创建AccountDaoImpl类,减少耦合
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
public void saveAccount(){
accountDao.saveAccount();
}
}
以上通过工厂模式减少了类与类之间的耦合,我们再删除AccountDaoImpl类运行代码,此时程序报运行时错误,抛出异常。
而spring通过配置XML文件来帮我们创建对象(bean对象,保存在IoC容器中),减少了类之间的耦合度,与工厂模式相比,spring配置起来更加方便,功能也更加强大。
public class Client {
/**
* 获取spring的IOC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
// 1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据id获取bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountService ass = (IAccountService) ac.getBean("accountService");
IAccountDao adao = (IAccountDao) ac.getBean("accountDao", IAccountDao.class);
System.out.println(as);
System.out.println(ass);
System.out.println(adao);
as.saveAccount();
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 把对象的创建交给spring来管理 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> </bean>
</beans>
粘贴spring doc中IoC的定义:
It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method.
翻译:这是一个对象仅1)通过构造函数参数、2)工厂方法的参数或3)对象实例构造后设置类成员或从工厂方法返回,来定义其依赖项(即该对象使用其他对象)的过程。
接下来我们用Bean的三种创建方式来帮助理解:
1. 使用类中默认构造函数创建bean对象,若类中无构造函数,则无法创建
<bean id="targetInstance" class="TargetClass"></bean>
2. 使用普通工厂类中的方法创建bean对象,并存入spring容器中
<!-- 首先创建工厂类,再根据工厂类中的方法创建目标类 -->
<bean id="factoryInstance" class="FactoryClass"></bean>
<bean id="targetInstance" factory-bean="factoryInstance" factory-method="FactoryClass.method"></bean>
3. 使用普通工厂类中的静态方法创建bean对象,并存入spring容器中
<!-- 通过工厂类中的静态方法创建目标类 -->
<bean id="targetInstance" class="FactoryClass" factory-method="FactoryClass.staticMethod"></bean>
所创建的bean对象默认为单例模式,其生命周期与IoC容器一致,当IoC容器被创建时,bean对象就被创建(即我们还未使用依赖对象时,对象已经被创建),IoC容器销毁时,bean对象也被销毁。可以通过bean标签的scope属性更改:
-
singleton:单例(默认值)
-
prototype:多例
-
request:作用于web应用的请求范围
-
session:作用于web应用的会话范围
-
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,就是session
Bean对象的生命周期
单例对象:
-
出生:当容器创建时创建bean对象
-
活着:只要容器还在,bean对象一直活着
-
死亡:容器销毁,bean对象也销毁
多例对象:
-
出生:当我们使用对象时spring框架创建bean对象
-
活着:bean对象只要在使用过程中就一直活着
-
死亡:当对象长时间不用,且没有别的对象引用时,有Java垃圾回收器回收
本文通过学习黑马spring教学视频所写,视频链接:https://www.bilibili.com/video/av47952931?p=1