单例bean和多例bean: 单例是指一个类的实例对象在一个程序周期中只能存在一个。多例则是可以存在多个实例对象。
在bean的xml定义中我们可以通过设置scope属性来指定一个bean是单例还是多例。其中prototype表示多例,singleton表示多例。
我们分别看一下两者具体用法。首先是单例:
<bean id="student_yqs" class="upc.yqs.Student" scope="singleton">
<property name="name" value="yqs"></property>
<property name="age" value="18"></property>
</bean>
我将这个bean的scope属性设置成了singleton,表示它是一个单例,接下来我们通过测试程序测试一下:
@Test
public void singletonTest() {
Student stu1 = (Student) context.getBean("student_yqs");
Student stu2 = (Student) context.getBean("student_yqs");
System.out.println("stu1的地址:" + System.identityHashCode(stu1) + " stu2的地址:" + System.identityHashCode(stu2));
}
输出:
stu1的地址:757004314 stu2的地址:757004314
我们发现两个对象其中是指向同一个对象,这就是单例模式的作用。
那么如果我们为同一个类定义两个bean,并且都指定为单例,那么会发生什么呢。
我们修改一下xml,增加一个bean,但是指向同一个类,并且都设置为singleton
<bean id="student_yqs" class="upc.yqs.Student" scope="singleton">
<property name="name" value="yqs"></property>
<property name="age" value="18"></property>
</bean>
<bean id="student_bbb" class="upc.yqs.Student" scope="singleton">
<property name="name" value="bbb"></property>
<property name="age" value="17"></property>
</bean>
接下来我们再写一个测试程序试试:
@Test
public void singletonTest2() {
Student stu_yqs1 = (Student) context.getBean("student_yqs");
Student stu_yqs2 = (Student) context.getBean("student_yqs");
Student stu_bbb1 = (Student) context.getBean("student_bbb");
Student stu_bbb2 = (Student) context.getBean("student_bbb");
System.out.println("stu_yqs1的地址:" + System.identityHashCode(stu_yqs1));
System.out.println("stu_yqs2的地址:" + System.identityHashCode(stu_yqs2));
System.out.println("stu_bbb1的地址:" + System.identityHashCode(stu_bbb1));
System.out.println("stu_bbb2的地址:" + System.identityHashCode(stu_bbb2));
}
输出:
stu_yqs1的地址:757004314
stu_yqs2的地址:757004314
stu_bbb1的地址:996796369
stu_bbb2的地址:996796369
会发现两个id得到的对象其实不是同一个,但是同一个id不管获取几次都是同一个id。也就是说bean里面的单例不是传统意义上的单例,传统意义的单例是针对一个class来讲的,而是针对一个bean来讲是一个单例。
多例的话同理,我们将scope属性设置成prototype即可(注意默认值是singleton)。这样子我们每次get到的bean都将会是不同的对象。有了上面的基础,这点比较容易理解,就不具体演示了。
带构造函数的bean的定义
如果我们想要利用构造函数来初始化我们的bean,那么就可以使用<constructor-arg>标签来对构造函数的参数进行传值。
比如我的学生类有这么一个构造函数:
public Student(String name, int age) {
this.name = name;
this.age = age;
}
那么想要使用这个构造函数,我们只需要这样子定义:
<bean id="student_asd" class="upc.yqs.Student" scope="singleton">
<constructor-arg name="name" value="asd"/>
<constructor-arg name="age" value="5"/>
</bean>
注意,我们定义的时候必须要和这个类中的某个构造函数匹配上才可以,否则会报错,就比如我把上面xml里面第二个constructor-arg给它删除了,那就会报错,因为Student类里面不存在只有一个age的构造函数。
如果有两个构造函数的参数数量一致,那么为了避免歧义,我们可以显式的指明参数类型:
<bean id="student_asd" class="upc.yqs.Student" scope="singleton">
<constructor-arg type="java.lang.String" name="name" value="asd"/>
<constructor-arg type="java.lang.Integer" name="age" value="5"/>
</bean>
参数特殊类型的构造函数的bean定义
上面讲到的其实都是一些简单的数据,如果遇到自定义类作为参数,或者List,Map作为参数,那么就需要一些特殊的语法来帮助我们实现。
1、自定义类作为参数
假设Student类有这么一个构造函数:
public Student()
{
this.name="null";
this.age=-1;
}
public Student(Student npy)
{
this();
this.npy=npy;
}
我们传入一个学生类对象作为参数,npy就是表示这个学生的男/女朋友(我实在想不出学生还有什么其它的一对一关系了,难道是父子??)
那么我们在定义bean的时候就可以利用ref属性(代替value)来传入一个Student对象:
<bean id="student_yqs" class="upc.yqs.Student" scope="prototype">
<property name="name" value="yqs"></property>
<property name="age" value="18"></property>
</bean>
<bean id="student_yyy" class="upc.yqs.Student">
<constructor-arg type="upc.yqs.Student" name="npy" ref="student_yqs"/>
</bean>
ref里面写的就是beanID,这里这么写的话就表示yyy的npy是yqs了。
我们写个测试程序试试看:
@Test
public void constructorTest2()
{
Student stu_yyy=(Student) context.getBean("student_yyy");
System.out.println(stu_yyy.npy);
}
输出:
Student{name='yqs', age=18}
符合我们的预期
2、List作为参数
比如Student有这么一个构造函数:
public Student(List<Student> friends)
{
this.friends = friends;
}
那么我们定义时就可以这么做:
<bean id="student_yyq" class="upc.yqs.Student">
<constructor-arg type="java.util.List" name="friends">
<list>
<ref bean="student_yqs"></ref>
<ref bean="student_yyy"></ref>
<ref bean="student_asd"></ref>
</list>
</constructor-arg>
</bean>
写一个测试程序:
@Test
public void constructorTest3()
{
Student stu_yyq=(Student) context.getBean("student_yyq");
System.out.println(stu_yyq.friends);
}
输出:
[Student{name='yqs', age=18}, Student{name='null', age=-1}, Student{name='asd', age=5}]
也是没什么问题。
3、map、set作为参数
道理比较相似,不具体讲了:
<bean id="student_msq" class="upc.yqs.Student">
<constructor-arg type="java.util.Map" name="map">
<map>
<entry key="111" value="5"></entry>
<entry key="222" value="6"></entry>
<entry key="333" value="7"></entry>
<entry key="444" value="8"></entry>
</map>
</constructor-arg>
<constructor-arg type="java.util.Set" name="set">
<set>
<value>9</value>
<value>10</value>
<value>11</value>
</set>
</constructor-arg>
</bean>
懒加载
在定义bean时如果我们将其lazy_init属性设为true(只有当scope="singleton"时这个属性才会生效),那么这个bean就会变成懒加载,也就是当我们使用getBean(或者其它bean中用ref引用了这个bean)时,这个bean才会被加载。设置为false(默认也为false)的话,程序一运行就会被加载。
init-method:容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法(scope=singleton时只会调用一次,scope=prototype时每次getBean都会调用)
destroy-method:容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
参数的值填的是该bean指向的类的成员方法的名字(可以是静态类)
偷个懒,不贴代码了,大家自己试试。