一、IOC控制反转
IOC 思想:
IOC(控制反转)是一种依赖倒置原则的代码设计思路,他主要采用(DI)依赖注入的方式实现;
不使用IOC思想的传统模式:
在传统模式中,对象由程序员主动创建,控制权在程序员手中;程序可以做到正常的工作,但是有一个难以避免的问题;
如果用户需求变更,程序员要修改对应的代码,如果代码量不大还好,如果有大量代码的话,修改一次的成本就会很高;
这个问题就是因为耦合度过高引起的,修改一次需求或多或少都会造成代码的修改,工作量不说,维护起来的过程也是非常难受的;
- 就如上图这四个齿轮(对象)一样,互相啮合,如果有一方停止或者更换,其他的齿轮也就没有办法工作,这自然不是我们希望看到的;
为了解决对象间耦合过高的问题,软件专家Michael Mattson提出了ICO理论,来实现对象之间的“解耦”
那么应该如何达到理想的效果呢?
使用IOC思想后的模式:
IOC的主要思想是借助一个“第三方”来拆解原来的耦合对象,并将这些都与“第三方建立联系”,由第三方来创建、操作这些对象,进行达到解耦的目的;
因此IOC容器也就成了整个程序的核心,对象之间没有了直接联系,但都和IOC容器有了联系;IOC的思想最核心的地方在于,资源不由使用资源的双方管理,而由不适用资源的第三方去管理,这样带来的好处是:1.资源集中管理,实现资源的可配置和容易管理;2.降低了使用资源 双方的依赖程度,也就是我们所说的耦合度;
什么是控制反转:
这里我们引入一个场景;如果A对象要调用B对象。
传统模式中如何操作,大家很熟悉,在A对象中创建B对象的实例,就可以满足A对象调用B对象的需求;这就是我们在A对象中主动去创建B对象;
而引入IOC后,A对象如果调用B对象,IOC容器会创建一个B对象注入到A对象中,这样也可以满足A对象调用的需求;但是过程由我们自己主动创建变成了A对象被动接收IOC容器注入的B对象;
A对象依赖B对象的过程就由程序员主动创建B对象供其依赖,变为了被动的接收IOC容器注入的对象,控制权从程序员手中转交到了IOC容器手中,A对象获得依赖的过程也由主动变为被动,这就是所谓的控制反转;
二、什么是依赖注入(DI)
依赖注入是IOC思想中最主要实现方式,也就是上文提到的A对象如果想要调取B对象,IOC容器会创建一个B对象注入到A对象中,这样就可以满足A对象对B对象的依赖需求;这个行为就是依赖注入;
DI≠IOC
IOC的概念比DI更广一些,而DI是IOC的主要实现方式,这不意味这DI就是IOC,将二者混为一谈是不对的;
依赖注入方式(三种):
1.构造器方法注入:
public class Address {
private String address;
public Address() {
}
public Address(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
配置bean.xml
- 通过参数名赋值(推荐)
<bean id="address" class="com.kuang.pojo.Address">
<property name="address" value="diqui"/>
</bean>
注意*:在使用构造方法注入时需要空参构造,在bean.xml中<bean>的标签内可以使用<property>标签name对应类中的字段名称;若没有无参构造可以使用以下两种方式:
- 根据有参构造下标赋值
<bean id="hello" class="com.kuang.pojo.Address">
<constructor-arg index="0" value="陌路"/>
</bean>
以上方式是使用constructor-arg标签根据有参构造下标赋值;
- 通过参数类型赋值(不推荐,若参数类型重合会导致错误赋值)
<bean id="hello" class="com.kuang.pojo.Address">
<constructor-arg type="java.lang.String" value="陌路"/>
</bean>
以上方式是使用constructor-arg标签根据有参构造类型赋值;
constructor-arg标签的优缺点:
优点:创建时必须要指定构造方法中的全部参数,bean才能被创建,保证了对象创建出来之后,成员变量一定都有值
缺点:必须要指定全部参数,否则无法创建,使用该方式改变了对象的创建过程
2.set注入:
1.创建一个User类,给User一个Name属性;
配置bean.xml文件
<!--IOC创建对象的方式,默认使用无参的构造方法!-->
<bean id="user" class="com.kuang.pojo.User">
<property name="name" value="幺鸡"/>
</bean>
3.接口注入:
1.新建一个User类,实现settrt和getter方法
package com.bean;
public class User {
String username;
String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getpassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.新建一个接口,名字为UserDAO,里面包含一个Save方法;
package com.dao;
import com.bean.User;
public interface UserDAO {
public void save(User user);
}
3.新建一个UserDAO的实现类UserDAOImpl,实现save方法;
package com.dao;
import com.bean.User;
public class UserDAOImpl implements UserDAO {
@Override
public void save(User user) {
// Todo Auto-generated method stub
System.out.println(user.getUsername()+"被存储");
}
}
4.为了不将直接操作数据库的对象UserDAO暴露给用户,需添加service层;
package com.bean;
public interface UserService {
public void add(User user);
}
然后些这个接口的实现类UserServiceImpl:
package com.bean;
import com.dao.UserDAO;
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
//设值注入 为UserDAO的注入做准备
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
//实现负责业务逻辑的add方法
@Override
public void add(User user) {
// Todo Auto-generated method stub
userDAO.save(user);
}
}
Bean的作用域:
1.单例模式(Spring默认机制)
<bean id="user" class="com.kuang.pojo.User" p:name="绘画" p:age="12" scope="singleton"/>
2.原型模式:每次从原型中get的时候都会产生一个新的对象
<bean id="user" class="com.kuang.pojo.User" p:name="绘画" p:age="12" scope="prototype"/>
其余的作用域还有例如:request、session等;
bean作用域的类别与说明:
类别 | 说明 |
singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean以单例的方式存在 |
prototype | 每次调用getBean()时都会返回一个新的实例 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean,该作用域仅使用于WebApplicationContext环境 |
注意:Spring只会帮我们管理单例模式的完整生命周期,对于prototype的bean,Spring在创建好交给使用之后不会再管理后续的生命周期
Bean的生命周期:
1、bean的初始和销毁:
其实我们在IOC中创建的每一个bean对象都是有其特定的生命周期的,Spring的IOC容器中可以管理bean的生命周期,Spring允许在bean生命周期内特定的事件点执行指定的任务,如在bean初始化时执行的方法和bean被销毁时执行的方法;
SpringIOC容器对bean的生命周期的过程可以分为六步:
1.通过构造器或者工厂方法创建bean实例
2.为bean的属性设置值和对其他bean的引用
3.调用bean的初始化方法
4.bean可以正常使用
5.当容器关闭时,调用bean的销毁方法
那bean如何在配置时指定初始化和销毁方法呢?
<!-- 设置bean的生命周期
destory-method:结束调用的方法
init-method:起始时调用的方法
-->
<bean id="book01" class="com.spring.beans.Book" destroy-method="myDestory" init-method="myInit"></bean>
但这里还需要注意:
我们上面说了,单例的bean和多例的bean的创建事件时不同的,那么它们的初始方法和销毁方法的执行时间就稍稍不同
- 单例下bean的生命周期
容器启动-->初始化方法-->(容器关闭)销毁方法
- 多例下bean的生命周期
容器启动-->调用bean-->初始化方法-->容器关闭(销毁方法不执行)
2.bean的后置处理器:
什么时bean的后置处理器?
bean后置处理器允许在调用初始化方法前后对bean进行额外的处理,bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例;其中典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性;
bean后置处理器使用时需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor。
在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
postProcessBeforeInitialization(Object, String)调用前
postProcessAfterInitialization(Object, String)调用后
如以下是实现在该接口的后置处理器:
package com.spring.beans;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* 测试bean的后置处理器
* 在这里要注意一点是为了出现bean和beanName,而不是arg0、arg1,需要绑定相应的源码jar包
* */
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* postProcessBeforeInitialization
* 初始化方法执行前执行
* Object bean
* String beanName xml容器中定义的bean名称
* */
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// TODO Auto-generated method stub
System.out.println("【"+ beanName+"】初始化方法执行前...");
return bean;
}
/**
* postProcessAfterInitialization
* 初始化方法执行后执行
* Object bean
* String beanName xml容器中定义的bean名称
* */
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// TODO Auto-generated method stub
System.out.println("【"+ beanName+"】初始化方法执行后...");
return bean;
}
}
将该后置处理器加入IOC容器中
<!-- 测试bean的后置处理器 -->
<bean id="beanPostProcessor" class="com.spring.beans.MyBeanPostProcessor"></bean>
由于现在bean对象是单例实例,所以容器运行时就会直接创建bean对象同时也会执行该bean 的后置处理器方法和初始化方法,在容器被销毁时又会执行销毁方法;测试如下:
//*************************bean生命周期*****************
// 由于ApplicationContext是一个顶层接口,里面没有销毁方法close,所以需要使用它的子接口进行接收
ConfigurableApplicationContext iocContext01 = new ClassPathXmlApplicationContext("ioc1.xml");
@Test
public void test01() {
iocContext01.getBean("book01");
iocContext01.close();
}
运行结果
总结后置处理器的执行过程:
1.通过构造参数或工厂方法创建bean实例
2.为bean的属性设置值和对其他bean引用
3.将bean实例传给bean后置处理器的postProcessBeforeInitialization()方法
4.调用bean的初始化方法
5.将bean实例传递geibean后置处理器的postProcessAfterInitialization()方法
6.bean可以使用了
7.当容器关闭时调用bean的销毁方法
所以添加bean后置处理器后bean的生命周期为:
容器启动-->后置处理器before-->初始化方法-->后置处理器after-->(容器关闭)销毁方法