Spring(IOC控制反转和DI依赖注入)

一、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-->(容器关闭)销毁方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值