Spring详解(二)------IOC控制反转

  我相信提到 Spring,很多人会脱口而出IOC(控制反转)、DI(依赖注入)、AOP等等概念,这些概念也是面试官经常问到的知识点。那么这篇博客我们就来详细的讲解 IOC控制反转。

  ps:本篇博客源码下载链接:http://pan.baidu.com/s/1miwZIf2 密码:oquc

1、什么是 IOC?

  IOC-Inversion of Control,即控制反转。它不是什么技术,而是一种设计思想。

  传统的创建对象的方法是直接通过 new 关键字,而 spring 则是通过 IOC 容器来创建对象,也就是说我们将创建对象的控制权交给了 IOC 容器。我们可以用一句话来概括 IOC:

IOC 让程序员不在关注怎么去创建对象,而是关注与对象创建之后的操作,把对象的创建、初始化、销毁等工作交给spring容器来做。

2、分享Bromon的blog上对IoC与DI浅显易懂的讲解

  IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

  那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

3、Spring 容器创建对象的三种方式

第一步:创建工程,然后导入相应的 jar 包,如下图:(详情参见上面的源码下载)

  第二步:创建测试对象 HelloIoc

package com.ys.ioc;
//这是测试对象,我们通过 IOC 来创建对象
public class HelloIoc {
	
	public void sayHello(){
		System.out.println("Hello IOC");
	}
}

  传统的创建对象的方法:new 关键字

//传统的创建对象方法----new
	@Test
	public void testTradition(){
		HelloIoc hello = new HelloIoc();
		hello.sayHello();
	}

  这里通过 Spring 容器怎么来创建呢?

第一种方法:利用默认的构造方法

  在 src 目录下新建 applicationContext.xml 文件,这是 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.xsd">
    <!--
	创建对象的第一种方式:利用无参构造器
	id:唯一标识符
	class:类的全类名
	  -->
    <bean id="helloIoc" class="com.ys.ioc.HelloIoc" ></bean>   
    <!-- 别名属性  name:和bean的 id 属性对应 -->
	<alias name="helloIoc" alias="helloIoc2"/>
	
</beans>

  测试代码:

/**
	 *  Spring 容器利用构造函数创建对象
	 */
	@Test
	public void testCreateObjectByConstrutor(){
		//1、启动 spring 容器
		ApplicationContext context = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		//2、从 spring 容器中取出数据
		HelloIoc IOC = (HelloIoc) context.getBean("helloIoc");
		//3、通过对象调用方法
		IOC.sayHello();
		
		//利用配置文件 alias 别名属性创建对象
		HelloIoc IOC2 = (HelloIoc) context.getBean("helloIoc2");
		IOC2.sayHello();
	}

  我们可以在测试类 HelloIoc.java 中手动添加无参的构造方法,然后执行上面的测试代码,会发现构造方法会在 sayHello()方法执行之前调用。

第二种方法:利用静态工厂方法

首先创建静态工厂类 HelloStaticFactory.java

package com.ys.ioc;

public class HelloStaticFactory {
	public HelloStaticFactory(){
		System.out.println("HelloStaticFactory constructor");
	}
	//静态工厂方法
	public static HelloIoc getInstances(){
		return new HelloIoc();
	}

}

  接着在 applicationContext.xml 中进行如下配置:

<!--
    	创建对象的第二种方式:利用静态工厂方法
    	factory-method:静态工厂类的获取对象的静态方法
    	class:静态工厂类的全类名
      -->    
    <bean id="helloStaticFactory" factory-method="getInstances" class="com.ys.ioc.HelloStaticFactory"></bean>

  编写测试类:

/**
	 * Spring 容器利用静态工厂方法创建对象
	 */
	@Test
	public void createObjectStaticFactory(){
		ApplicationContext context = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		HelloIoc staticFactory =
				(HelloIoc) context.getBean("helloStaticFactory");
		staticFactory.sayHello();
	}

注意:spring容器只负责调用静态工厂方法,而这个静态工厂方法内部实现由程序员完成

第三种方法:利用实例工厂方法

首先创建实例工厂类 HelloInstanceFactory .java

package com.ys.ioc;

public class HelloInstanceFactory {
	public HelloInstanceFactory(){
		System.out.println("实例工厂方法构造函数");
	}

	//利用实例工厂方法创建对象
	public HelloIoc getInstance(){
		HelloIoc instanceIoc = new HelloIoc();
		return instanceIoc;
	}
}

  接着在 applicationContext.xml 中进行如下配置:

<!--
    	创建对象的第三种方式:利用实例工厂方法
    	factory-bean:指定当前Spring中包含工厂方法的beanID
    	factory-method:工厂方法名称
      -->  
    <bean id="instanceFactory" class="com.ys.ioc.HelloInstanceFactory"></bean>  
    <bean id="instance" factory-bean="instanceFactory" factory-method="getInstance"></bean>  

  最后编写测试类:

/**
	 * Spring 容器利用实例工厂方法创建对象
	 */
	@Test
	public void createObjectInstanceFactory(){
		ApplicationContext context = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		HelloIoc staticFactory =
				(HelloIoc) context.getBean("instance");
		staticFactory.sayHello();
	}

4、Spring 容器创建对象的时机

第一种:默认情况下,启动 spring 容器便创建对象(遇到bean便创建对象)

  测试:

  第一步:我们在 HelloIoc.java 中添加默认构造方法:

  第二步:在 applicationContext.xml 文件中添加 bean(由于上面我们通过三种方式来创建对象了,里面已经有三个bean了)

  第三步:启动 Spring 容器,查看无参构造函数的打印次数

  控制台打印结果如下:

第二种:在spring的配置文件bean中有一个属性 lazy-init="default/true/false"

     ①、如果lazy-init为"default/false"在启动spring容器时创建对象(默认情况)

     ②、如果lazy-init为"true",在context.getBean时才要创建对象

   我们测试 lazy-init=“true”的情况

  我们测试通过断点调试:

  然后继续往下面执行:

   在第一种情况下可以在启动spring容器的时候,检查spring容器配置文件的正确性,如果再结合tomcat,如果spring容器不能正常启动,整个tomcat就不能正常启动。但是这样的缺点是把一些bean过早的放在了内存中,如果有数据,则对内存来是一个消耗。

  反过来,在第二种情况下,可以减少内存的消耗,但是不容易发现错误

5、spring的bean中的scope:"singleton/prototype/request/session/global session"

一、默认scope的值是singleton,即产生的对象是单例的

   applicationContext.xml 文件中配置:

<bean id="helloIoc" scope="singleton" class="com.ys.ioc.HelloIoc" ></bean>

  验证:

//spring 容器默认产生对象是单例的 scope="singleton"
	@Test
	public void test_scope_single_CreateObject(){
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		HelloIoc hello1 = (HelloIoc) context.getBean("helloIoc");
		HelloIoc hello2 = (HelloIoc) context.getBean("helloIoc");
		System.out.println(hello1.equals(hello2)); //true
	}

二、scope=“prototype”

  多例模式,并且spring容器启动的时候并不会创建对象,而是在得到 bean 的时候才会创建对象

   applicationContext.xml 文件中配置:

<bean id="helloIoc" scope="prototype" class="com.ys.ioc.HelloIoc" ></bean>

  验证:

//spring 容器默认产生对象是单例的 scope="prototype"
	@Test
	public void test_scope_prototype_CreateObject(){
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		HelloIoc hello1 = (HelloIoc) context.getBean("helloIoc");
		HelloIoc hello2 = (HelloIoc) context.getBean("helloIoc");
		System.out.println(hello1.equals(hello2)); //false
	}

总结:在单例模式下,启动 spring 容器,便会创建对象;在多例模式下,启动容器并不会创建对象,获得 bean 的时候才会创建对象

5、Spring 容器生命周期

  创建 SpringLifeCycle.java

package com.ys.ioc;
/**
 * Spring 容器的生命周期
 * @author hadoop
 *
 */
public class SpringLifeCycle {
	public SpringLifeCycle(){
		System.out.println("SpringLifeCycle");
	}
	//定义初始化方法
	public void init(){
		System.out.println("init...");
	}
	//定义销毁方法
	public void destroy(){
		System.out.println("destroy...");
	}
	
	public void sayHello(){
		System.out.println("say Hello...");
	}
}

  applicationContext.xml 

<!-- 生命周期 -->
  	<bean id="springLifeCycle" init-method="init" destroy-method="destroy" class="com.ys.ioc.SpringLifeCycle"></bean> 

  测试:

//spring 容器的初始化和销毁
	@Test
	public void testSpringLifeCycle(){
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		SpringLifeCycle hello = (SpringLifeCycle) context.getBean("springLifeCycle");
		
		hello.sayHello();
		
		//销毁spring容器
		ClassPathXmlApplicationContext classContext = (ClassPathXmlApplicationContext) context;
		classContext.close();
	}

  控制台打印如下:

  分析:spring 容器的声明周期

       1、spring容器创建对象           2、执行init方法           3、调用自己的方法           4、当spring容器关闭的时候执行destroy方法

  注意:当scope为"prototype"时,调用 close() 方法时是不会调用 destroy 方法的

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

【Golang语言社区】GO1.9 map并发安全测试

var m sync.Map //全局 func maintest() { // 第一个 YongHuomap := make(map[st...

5478
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

3015
来自专栏java 成神之路

使用 NIO 实现 echo 服务器

5617
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

3007
来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.4K7
来自专栏一个爱瞎折腾的程序猿

sqlserver使用存储过程跟踪SQL

USE [master] GO /****** Object: StoredProcedure [dbo].[sp_perfworkload_trace_s...

2940
来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

5988
来自专栏一个会写诗的程序员的博客

Spring Reactor 项目核心库Reactor Core

Non-Blocking Reactive Streams Foundation for the JVM both implementing a Reactiv...

2822
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3685
来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

8738

扫码关注云+社区