Spring框架从2004年发布的第一个版本以来,如今已经迭代到5.x,逐渐成为JavaEE开发中必不可少的框架之一,也有人称它为Java下的第一开源平台。单从Spring的本身来说,它贯穿着整个表现层,业务层与持久层,它并没有取代其他框架的意思,而更多的是从整体上管理这些框架,降低系统的耦合性。系列文章将逐渐完成对Spring的学习,本篇首先学习它的一个核心机制:依赖注入,主要涉及内容如下:

  • 理解依赖注入
  • 理解Spring容器
  • 配置和使用bean

一、理解依赖注入

     在正式介绍依赖注入之前,我们先通过一个简单的Spring程序感受下Spring的一个最常用的功能。首先,导入必需的四个Spring包和一个它依赖的日志包:

  • spring-beans-4.0.0.RELEASE.jar
  • spring-context-4.0.0.RELEASE.jar
  • spring-core-4.0.0.RELEASE.jar
  • spring-expression-4.0.0.RELEASE.jar
  • commons-logging-1.2.jar

然后创建两个类,

public class Person {
private String name;
private int age;
private Parents parents; public void setName(String name) {
this.name = name;
} public void setAge(int age) {
this.age = age;
} public void setParents(Parents parents) {
this.parents = parents;
}
}
public class Parents {
private String pFatherName;
private String pMotherName; public void setpFatherName(String pFatherName) {
this.pFatherName = pFatherName;
} public void setpMotherName(String pMotherName) {
this.pMotherName = pMotherName;
}
}

此时,我们的每个Person实例都在内部引用了一个Parents实例,那么当我们想要获取Person对象的时候,就需要首先初始化一个Parents实例给Person对象,程序如下:

public static void main(String[] args){
Parents p = new Parents();
p.setpFatherName("father");
p.setpMotherName("mother"); Person person = new Person();
person.setName("single");
person.setAge(22);
person.setParents(p); }

传统的写法,我们构建一个person实例需要这么多行代码,而Spring给我们带来的改变就是,通过构建Spring容器,所有的对象都会在容器中生成,外部只需要向容器索要对象即可。例如:

//新建Spring配置文件,命名为application.xml
//此处省略XML头文件部分
<bean id="parents" class="MyPackage.Parents">
<property name="pFatherName" value="father"/>
<property name="pMotherName" value="mother"/>
</bean> <bean id="person" class="MyPackage.Person">
<property name="name" value="single"/>
<property name="age" value="22"/>
<property name="parents" ref="parents"/>
</bean>

然后看我们的主程序如何获取person实例:

public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) context.getBean("person");
}

一共两行代码,第一行代码用于创建Spring容器,第二行代码用于获取容器中名为person的Person实例。在Spring配置文件加载的时候,容器会初始化所有bean,也就是说所有配置在容器中的bean都会被创建实例并保存在容器中等待调用。依赖注入就是说,在容器加载结束之后,所有实例的属性都被容器注入相应的值,不需要程序员手动去赋值。而在外部获取该实例的时候不需要知道容器是如何为各个属性注入值的。如果某个bean关联了其他bean,那么容器也会为它自动注入其他bean的引用。

依赖注入主要有两种方式,一种是设值注入,另一种是构造注入,我们将在介绍bean的配置的时候详细学习。

二、理解Spring容器

     Spring容器就相当于一个大的工厂一样,所有的bean都是工厂中的产品。BeanFactory是Spring容器的最基本的接口,它负责配置、创建和管理Bean,该接口中有如下几个方法:

  • Object getBean(String var1):返回容器中id为var1的实例
  • <T>T getBean(String var1, Class<T> var2):返回容器中id为var1的实例,并转换该实例类型为var2
  • boolean containsBean(String var1):返回容器中是否包含id为var1 的实例
  • Class<?> getType(String var1):返回容器中id为var1的实例的所属类型

ApplicationContext是BeanFactory的子接口,它扩展了BeanFactory,提供更加完善的容器操作,对于大部分的JavaEE应用来说,使用ApplicationContext作为Spring容器。它也有几个常用的实现类:

  • FileSystemXmlApplicationContext:表示从文件系统中加载配置文件并创建Spring容器
  • ClassPathXmlApplicationContext:表示从应用的类路径(src)下加载配置文件并创建Spring容器
  • XmlWebApplicationContext和AnnotationConfigWebApplicationContex:它们主要应用于web应用中

一般来说,首选ApplicationContext作为Spring容器,除非在一些对内存要求比较苛刻的应用中才考虑使用BeanFactory。

三、配置和管理Bean

     在Spring的配置文件中,我们使用bean标签配置一个实例对象。例如:

<bean id="person" class="MyPackage.Person">

</bean>

通常来说,我们配置bean的时候会指定一个id属性值和一个class属性值,id属性用于标识该实例,是该实例在容器中的唯一标识,class属性指向该实例的类型。显然框架会利用反射根据这个class属性值调用newInstance方法创建该类的一个实例对象。

下面我们学习下bean的作用域,

容器中的bean支持以下几种不同范围的作用域:

  • singleton:单例模式,在整个容器中,该bean只会被创建一次。
  • prototype:该模式指定,每次外部调用getBean获取该实例的时候,都会创建一个新的实例对象。
  • request:在同一次http请求中,程序请求该bean将会得到同一个实例。
  • session:在同一次http会话期间,程序对该bean的请求将会得到同一个实例。
  • global session:在全局的session会话期间对应同一个实例。

Spring中默认bean的作用域为singleton,即每个bean只会在容器中被创建一次。例如:

/*配置一个Date实例*/
<bean id="date" class="java.util.Date" scope="singleton"> </bean>
/*获取该实例*/
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Date date = (Date) context.getBean("date");
Thread.sleep(1000);
Date date2 = (Date) context.getBean("date"); System.out.println("date:"+date);
System.out.println("date2:"+date2);

输出结果:

date:Thu Nov 02 13:35:15 CST 2017
date2:Thu Nov 02 13:35:15 CST 2017

显然,这两次获取的date是同一实例对象,我们修改bean的配置:

<bean id="date" class="java.util.Date" scope="prototype">

</bean>

再次执行程序:

date:Thu Nov 02 13:39:09 CST 2017
date2:Thu Nov 02 13:39:10 CST 2017

显然,两个date的值并不相等,所以它们并不指向同一实例。当然,Spring中默认bean的作用域为singleton。

下面我们学习如何配置依赖关系,让容器为我们注入依赖,

根据注入方式的不同,Bean的依赖注入可以有如下两种形式:

  • 设值注入:通过<property..>元素驱动Spring执行setter方法实现。
  • 构造注入:通过<constructor-arg...>元素驱动Spring执行有参构造器完成注入。

我们首先看看设值注入的使用,

/*定义一个person类*/
public class Person {
private String name;
private int age; public void setName(String name) {
System.out.println("调用setname方法注入name的值");
this.name = name;
} public void setAge(int age) {
System.out.println("调用setage方法注入age的值");
this.age = age;
}
}
/*配置一个person类实例*/
<bean id="person" class="MyPackage.Person">
<property name="name" value="single"/>
<property name="age" value="22"/>
</bean>
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);

从容器中获取person实例,输出结果:

可以看到,当容器初始化完毕的时候,会通过反射创建每个bean元素所对应的实例,遇到property元素,Spring则会反射调用该实例的setter方法初始化这些属性值,也可以叫做注入依赖。这种方式的注入依赖,唯一需要的是类所对应的属性必须配置一个setter方法。

构造注入方式:

/*向person中增加一个有参构造器*/
public Person(String name,int age){
System.out.println("调用构造器注入属性值");
this.name = name;
this.age = age;
}

配置bean:

<bean id="person" class="MyPackage.Person">
<constructor-arg name="name" value="single"/>
<constructor-arg name="age" value="22"/>
</bean>

输出结果:

很显然,Spring在加载配置文件的时候,发现bean的配置中并没有<costructor-arg>元素,那么它将会驱动默认的构造器创建一个类实例,否则将<costructor-arg>元素的个数驱动具有相对应的构造器。

总的来说,两者都有优缺点,设置注入更容易理解和使用,构造注入只允许属性的值在构造实例的时候赋值,一旦实例构建完成,其属性将不具备修改的能力,使得依赖关系不易被破坏,但是大量重复臃肿的构造代码使得程序很笨重。一般建议以设值依赖为主,构造注入为辅。

Spring允许通过以下几种类型的的元素作为setter方法的参数传入类属性的setter方法中,

  • 普通属性值
  • 引用
  • 内部bean
  • 集合以及属性集

     1、普通属性值

     对于基本类型已经String类型的属性值,我们通过

<property name="" value="" />

直接将值填在value中即可,Spring调用XML的解析器将所有的String自动转换为对应的参数类型并传入setter方法中。

     2、引用类型

     我们定义一个Person类和一个Parents类:

public class Parents {
private String pFatherName;
private String pMotherName;
//省略setter方法
} public class Person {
private String name;
private int age;
private Parents parents;
//省略setter方法
}

我们的Person实例内部有一个Parents 类型的属性,那么容器在注入的时候该如何将一个Parents 类型的实例注入到Person的parents属性中呢?

<bean id="parents" class="MyPackage.Parents">
<property name="pFatherName" value="father"/>
<property name="pMotherName" value="mother"/>
</bean> <bean id="person" class="MyPackage.Person">
<property name="name" value="single"/>
<property name="age" value="22"/>
<property name="parents" ref="parents"/>
</bean>

我们通过<property>元素的ref属性配置这种引用依赖,首先容器会创建一个名为parents的Parents实例并为其内部的各个属性注入数值,接着容器会创建一个名为person的Person实例,并注入其普通属性值。当为其parents属性注入值的时候,传入的是已存在的实例parents的引用。

     3、定义内部bean

     如果某个bean不想被容器外部引用,而只想作为一个属性的值传入setter方法中,那么我们可以将它定义成内部bean。例如:

<bean id="person" class="MyPackage.Person">
<property name="name" value="single"/>
<property name="age" value="22"/>
<property name="parents">
<bean class="MyPackage.Parents">
<property name="pFatherName" value="father"/>
<property name="pMotherName" value="mother"/>
</bean>
</property>
</bean>

这样我们就配置了一个内部bean,该bean没有标识,用完就丢失对它的引用了。其实定义内部bean来给某个实例属性赋值和使用ref引用在本质上是一样的。

     4、集合属性以及属性集

     我们重新定义Person类如下:

public class Person {
private List<String> address;
private Map<String,String> creditCard;
private Set<String> hobbies;
//省略setter方法
}

各种类型的集合的配置如下:

<bean id="person" class="MyPackage.Person">
<property name="address">
<list>
<!--每个value,ref,bean都对应于list中的一个元素-->
<value>南京</value>
<value>上海</value>
<value>北京</value>
</list>
</property>
<property name="creditCard">
<map>
<!--每个entry元素对应于一个键值对,map中的-->
<entry key="中国银行" value="3231243234"></entry>
<entry key="江苏银行" value="5453454434"></entry>
</map>
</property>
<property name="hobbies">
<set>
<!--每个value,ref,bean都对应于list中的一个元素-->
<value>乒乓球</value>
<value>网球</value>
</set>
</property>
</bean>

属性集的配置和map的配置类似,使用键值对的形式进行配置:

<property name="properties">
<props>
<prop key="hello">single</prop>
</props>
</property>

至此,我们简单的介绍了Spring的依赖注入的一些基本的内容,有关bean的更高级的使用将在后续文章中进行介绍。总结不到之处,望指出!

Spring框架学习之依赖注入的更多相关文章

  1. Spring框架中的依赖注入

    依赖注入(DI : Dependency Injection)是基于.xml配置文件内节点的书写. 注入类型: 1.设置注入,调用了Bean的setXXX()进行值注入 普通属性(value值表示要显 ...

  2. spring 控制反转与依赖注入原理-学习笔记

    在Spring中有两个非常重要的概念,控制反转和依赖注入:控制反转将依赖对象的创建和管理交由Spring容器,而依赖注入则是在控制反转的基础上将Spring容器管理的依赖对象注入到应用之中: 所谓依赖 ...

  3. Spring学习(一)---依赖注入和控制反转

    Spring Spring是一个从实际开发中抽出来的框架,因此它完成了大量开发中的通用步骤,留给开发者的仅仅是与特定应用相关的部分,从而大大提高了企业应用的开发效率. Spring为企业应用的开发提供 ...

  4. Spring学习笔记--依赖注入

    依赖注入和控制反转:http://baitai.iteye.com/blog/792980出自李刚<轻量级 Java EE 企业应用实战> Java应用是一种典型的依赖型应用,它就是由一些 ...

  5. Spring学习-spring核心机制-IOC依赖注入

    转载自:http://www.cnblogs.com/chenssy/archive/2012/11/11/2765266.html 今天复习一下spring两大特性之一:IOC依赖注入,看了一下大佬 ...

  6. Spring框架学习一

    Spring框架学习,转自http://blog.csdn.net/lishuangzhe7047/article/details/20740209 Spring框架学习(一) 1.什么是Spring ...

  7. Spring框架学习1

    AnonymouL 兴之所至,心之所安;尽其在我,顺其自然 新随笔 管理   Spring框架学习(一)   阅读目录 一. spring概述 核心容器: Spring 上下文: Spring AOP ...

  8. Spring框架学习之IOC(二)

    Spring框架学习之IOC(二) 接着上一篇的内容,下面开始IOC基于注解装配相关的内容 在 classpath 中扫描组件 <context:component-scan> 特定组件包 ...

  9. Spring框架学习之IOC(一)

    Spring框架学习之IOC(一) 先前粗浅地学过Spring框架,但当时忙于考试及后期实习未将其记录,于是趁着最近还有几天的空闲时间,将其稍微整理一下,以备后期查看. Spring相关知识 spri ...

随机推荐

  1. MySQL集群(三)mysql-proxy搭建负载均衡与读写分离

    前言 前面学习了主从复制和主主复制,接下来给大家分享一下怎么去使用mysql-proxy这个插件去配置MySQL集群中的负载均衡以及读写分离. 注意:这里比较坑的就是mysql-proxy一直没有更新 ...

  2. layer子层给父层页面元素赋值,以达到向父层页面传值的效果

    父层: jsp中: //页面上添加一个隐藏的输入框待用于被子层设置value,从而将子层的数据传递到此页面 <input type="hidden" id="get ...

  3. 使用VUE模仿BOSS直聘APP

    一.碎碎念: 偶尔在群里看到一个小伙伴说:最近面试的人好多都说用vue做过一个饿了么.当时有种莫名想笑. 为何不知道创新一下?于是想写个DEMO演练一下.那去模仿谁呢?还是BOSS直聘(跟我没关系,不 ...

  4. Linux Expect自动化交互脚本简介

    相关资料 维基百科:Expect SourceForge:The Expect Home Page TCL脚本言语简介 由于Expect是建立在TCL语言基础上的一个工具,因此首先检查一些TCL常见语 ...

  5. Hive基础(3)---Fetch Task(转)

    我们在执行hive代码的时候,一条简单的命令大部分都会转换成为mr代码在后台执行,但是有时候我们仅仅只是想获取一部分数据而已,仅仅是获取数据,还需要转化成为mr去执行吗?那个也太浪费时间和内存啦,所以 ...

  6. bzoj2730(矿场搭建)

    矿场搭建,不知道为什么,莫名其妙T了在212上,额,zyh数据真的坑. bzoj200轻松跑过啊. 就是点双联通分量缩点,然后标记割点,一个块如果有>=2个割点,则不需要挖矿洞, 如果只有一割点 ...

  7. Python自学笔记-lambda函数(来自廖雪峰的官网Python3)

    感觉廖雪峰的官网http://www.liaoxuefeng.com/里面的教程不错,所以学习一下,把需要复习的摘抄一下. 以下内容主要为了自己复习用,详细内容请登录廖雪峰的官网查看. 匿名函数 通过 ...

  8. c# datetime与 timeStamp(unix时间戳) 互相转换

    /// <summary> /// Unix时间戳转为C#格式时间 /// </summary> /// <param name="timeStamp" ...

  9. KMP算法的细节问题

    preface: 想必,很多人都知道D.E.Knuth与V.R.Pratt和J.H.Morris同时提出所谓的狂拽酷炫屌炸天的KMP算法,在对字符串的匹配(或是字符串的查找)方面表现出比较好的效率,该 ...

  10. hadoop之 hadoop 2.2.X 弃用的配置属性名称及其替换名称对照表

    Deprecated Properties  弃用属性 The following table lists the configuration property names that are depr ...