Spring学习(三、Bean对象在Spring中的管理)
在大型的系统中,对象的生命周期交给程序管理,大型程序的对象生命周期管理就很麻烦。在Spring中,Spring使用了IOC设计思想,将对象的控制权抽出来,交给程序外的容器管理,减少了程序的代码行数,管理起来就简单了很多。下面是关于IOC和DI的简单介绍:
- IOC 是Inversion Of Control( 控制反转 )的缩写,它是一种设计思想,是指将对象的控制权由程序代码反转给外部容器。
- DI 是Dependency Injection( 依赖注入 )的缩写,它是控制反转的另一种说法,同时也为控制反转提供了实现方法。依赖注入是指调用类对其他类的依赖关系由容器注入,这就避免了调用类对其他类的过度依赖,降低了类与类之间的耦合。
一、Bean
Bean简单来说就是Spring容器管理的一个基本单位,所有的对象如果要使用Spring容器来管理,都要将该对象映射成Bean。这是我理解的Bean,具体概念可以看官网。
1.Bean的描述
Spring中常用描述Bean的属性如下:
-
class 指定Bean对应类的全路径
-
name 指定Bean对应对象的一个标识
-
scope 指定Bean对象的作用域
-
id id是Bean对象的唯一标识,Spring通常通过 id属性完成对Bean的配置,管理。
-
lazy-init 是否延时加载,默认值为false
-
init-method 对象初始化方法
-
destory- method 对象销毁方法
2.Bean的作用域
当Spring容器创建一个Bean实例时,不仅可以完成Bean的实例化,还可以为该Bean指定作用域。在Spring容器中,Bean的作用域是指Bean实例相对于其他Bean实例的请求可见范围。Spring支持五种作用域:
-
Singleton:单例模式,作用域为singleton的Bean在Spring容器中只会存在一个共享的Bean实例,所有对Bean的请求只要id与Bean的定义相匹配,则只会返回Bea的同一实例。
-
Prototype:每次从容器中调用Bean时,都会产生一个新的Bean实例。
-
Request:一个HTIP请求会产生一个Bean对象,也就是说,每一个HTTP请求都有自己的Bean实例,只在基于Web的SpringApplicatianC ontext中可用。
-
Session:限定一个Bean的作用域为HTTPsession的生命周期,只有基 于Web的SpringApplicationCorntext才能便用
-
global session:限定一个Bean的作用域为全局HTTPsession的生命周期,只有在Web应用中使用Sping时,该作用域才有效
其中,singleton 和prototype 是两种较为常用的作用域,如果不指定Bean的作用域,Spring会默认该Bean的作用域是singleton。
当Bean的作用域是singleton时,Spring容器只为该Bean创建一个实例,并且该实例可以被重复使用。Spring容器管理着Bean的生命周期,可以控制Bean的创建、初始化、销毁。由于创建和销毁Bean实例会带来一定的系统开销,因此,singleton作用域的Bean避免了反复创建和销毁实例造成的资源消耗。
当Bean的作用域是prototype时,每次调用Bean时Spring容器都会返回一个新的不同的实例,此时,Spring容器只负责创建Bean实例而不再跟踪其生命周期。
3.Bean的生命周期
-
Bean的生命周期是指Bean实例被创建、初始化和销毁的过程。Spring容器可以管理作用域为singleton的Bean的生命周期。对于作用域为prototype的Bean,Spring容器只负责创建实例而不负责跟踪其生命周期。
-
在Bean的生命周期中,有两个时间节点尤为重要,这两个时间节点分别是Bean实例初始化以后和Bean实例销毁之前,实际开发中,有时需要在这两个时间节点完成一些指定操作,例如,在Bean实例初始化之后申请某些资源、在Bean实例销毁之前回收某些资源等。
-
为了便于监控Bean生命周期中的特殊节点,Spring提供了相关的API。
当需要在Bean实例初始化后执行指定行为时,可以通过使用init-method属性或实现initializingBean接口的方式。
当需要在Bean实例销毁前执行指定行为时,可以通过使用destroy-method属性或实现DisposableBean接口的方式。
二、依赖注入的方式
依赖注入有三种方式,他们分别是构造器注入(对应类的构造函数)、属性注入(对应类的set方法)和接口注入。其中,构造器注入和属性注入是主要方式。
1.构造器注入
student类:
public class Student {
private String name;
private String behavior;
public Student(String name, String behavior) {
System.out.println("构造器构造Student");
this.name = name;
this.behavior = behavior;
}
public void init(){
System.out.println("Student初始化了,需要申请资源的快点");
}
public void destroy(){
System.out.println("Student即将销毁了,需要关闭资源的快点");
}
}
配置文件applicationContext.xml中添加Bean,顺便演示bean的属性用法。
<!--
id:唯一标识该类
class:指定该类的完全限定名
scope:指定Bean对象的作用域
lazy-init: sping启动时是否不实例化bean 默认时false
当为true时,不实例化bean,只有第一次索要bean时才实例化bean
-->
<bean id="student1" class="com.RearlRiver.pojo.Student" scope="singleton" lazy-init="true" init-method="init" destroy-method="destroy">
<constructor-arg name="name" value="张三" ></constructor-arg>
<constructor-arg name="behavior" value="学习Java"></constructor-arg>
</bean>
在bean标签下,通过使用标签,其中name代表类的属性,value代表给属性注入的值,使用构造器注入,类中必须有相应的构造函数。
2.属性注入
student类:
public class Student {
private String name;
private String behavior;
public void setName(String name) {
this.name = name;
}
public void setBehavior(String behavior) {
this.behavior = behavior;
}
}
配置文件applicationContext.xml中添加Bean
<bean id="student" class="com.RearlRiver.pojo.Student">
<property name="name" value="老王"></property>
<property name="behavior" value="学习Spring"></property>
</bean>
在bean标签下,通过使用标签,其中name代表类的属性,value代表给属性注入的值,使用属性注入,类中必须有相应的setXXX函数。
2.1使用属性注入集合
Gather类:
public class Gather {
private List<String> myList;
private Map<String, String> myMap;
private String[] myArray;
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
@Override
public String toString() {
return "Gather{" +
"myList=" + myList +
", myMap=" + myMap +
", myArray=" + Arrays.toString(myArray) +
'}';
}
}
配置文件applicationContext.xml中添加Bean
<bean id="gather" class="com.RearlRiver.pojo.Gather">
<property name="myList">
<list>
<value>list0</value>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="myMap">
<map>
<entry key="key0">
<value>myMap0</value>
</entry>
<entry key="key1" value="myMap1">
</entry>
<entry key="key2" value="myMap2">
</entry>
</map>
</property>
<property name="myArray">
<array>
<value>myArray0</value>
<value>myArray1</value>
<value>myArray2</value>
</array>
</property>
</bean>
2.2使用属性注入Spring容器中的其他Bean对象
Student类:
public class Student {
private String name;
private String behavior;
public void setName(String name) {
this.name = name;
}
public void setBehavior(String behavior) {
this.behavior = behavior;
}
}
School类:
public class School {
Student stu;
public void setStu(Student stu) {
this.stu = stu;
}
}
配置文件applicationContext.xml中添加Bean
<bean id="student" class="com.RearlRiver.pojo.Student">
<property name="name" value="老王"></property>
<property name="behavior" value="学习Spring"></property>
</bean>
<!--ref的值必须和Student类的bean的id对应-->
<bean id="school" class="com.RearlRiver.pojo.School">
<!--两种形式-->
<property name="stu" ref="student"/>
<property name="tea">
<ref bean="teacher"></ref>
</property>
</bean>
三、使用SpEL和 P命名空间注入
通过上面的注入方式,我们要写很多的配置,就很烦。使用SpEL和P命名空间就简洁了很多。当然,没有注解简洁。
<!--使用P:命名空间注入
注意引入:xmlns:p="http://www.springframework.org/schema/p"
-->
<bean id="student2" class="com.RearlRiver.pojo.Student" p:name="王五" p:behavior="玩游戏"/>
<!--使用SpEL注入
通过 Bean的 id引用Bean,调用对象的方法或引用对象的属性、计算表达式的值、匹配正则表达式等
-->
<bean id="school1" class="com.RearlRiver.pojo.School">
<property name="stu" value="#{student}"/>
<property name="tea" value="#{teacher}"/>
</bean>
四、测试
在之前搭建的环境中,将以上代码更新,在测试类中编写测试函数:
public void springBeansTest(){
// 通过读取配置文件获取ApplicationContext对象
ClassPathXmlApplicationContext applicationContext = neClassPathXmlApplicationContext("applicationContext.xml");
// 通过id值获取Student对象
Student student1 = applicationContext.getBean("student1", Student.class);
System.out.println("student1");
// 关闭容器,此时实例将被销毁
AbstractApplicationContext ac = (AbstractApplicationContext) applicationContext;
ac.registerShutdownHook();
}
其他bean的测试类似,只需要通过id拿到,输出看看是否正确。注意:我没重写toString方法,只会打印对象地址。