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头文件部分
然后看我们的主程序如何获取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 getBean(String var1, Class 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属性值和一个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实例*/
/*获取该实例*/
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的配置:
再次执行程序:
date:Thu Nov 02 13:39:09 CST 2017
date2:Thu Nov 02 13:39:10 CST 2017
显然,两个date的值并不相等,所以它们并不指向同一实例。当然,Spring中默认bean的作用域为singleton。
下面我们学习如何配置依赖关系,让容器为我们注入依赖,
根据注入方式的不同,Bean的依赖注入可以有如下两种形式:
- 设值注入:通过
元素驱动Spring执行setter方法实现。 - 构造注入:通过
元素驱动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类实例*/
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:
输出结果:
很显然,Spring在加载配置文件的时候,发现bean的配置中并没有
总的来说,两者都有优缺点,设置注入更容易理解和使用,构造注入只允许属性的值在构造实例的时候赋值,一旦实例构建完成,其属性将不具备修改的能力,使得依赖关系不易被破坏,但是大量重复臃肿的构造代码使得程序很笨重。一般建议以设值依赖为主,构造注入为辅。
Spring允许通过以下几种类型的的元素作为setter方法的参数传入类属性的setter方法中,
- 普通属性值
- 引用
- 内部bean
- 集合以及属性集
1、普通属性值
对于基本类型已经String类型的属性值,我们通过
直接将值填在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属性中呢?
我们通过
3、定义内部bean
如果某个bean不想被容器外部引用,而只想作为一个属性的值传入setter方法中,那么我们可以将它定义成内部bean。例如:
这样我们就配置了一个内部bean,该bean没有标识,用完就丢失对它的引用了。其实定义内部bean来给某个实例属性赋值和使用ref引用在本质上是一样的。
4、集合属性以及属性集
我们重新定义Person类如下:
public class Person {
private List address;
private Map creditCard;
private Set hobbies;
//省略setter方法
}
各种类型的集合的配置如下:
南京
上海
北京
乒乓球
网球
属性集的配置和map的配置类似,使用键值对的形式进行配置:
single
至此,我们简单的介绍了Spring的依赖注入的一些基本的内容,有关bean的更高级的使用将在后续文章中进行介绍。总结不到之处,望指出!