Spring学习day2-Spring配置开发(基于xml配置文件)
上篇我们提到过: 通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。而我们将 java对象交给Spring管理的这个过程被称为Spring的配置
Spring的配置开发主要有两种:一种是 基于配置文件的开发,还有一种是 基于注解的开发,今天我们就来学习一下Spring的XML配置开发
a) Bean元素(对象)
i.对象构造
Spring构造对象实例有三种方式,空参构造、静态工厂构造、动态工厂构造(后两种不是很常用所以这里就先不讲)
我们先在User类中创建一个没有参数的构造器,在里面随意输出一些内容
public User() {
System.out.println("没有参数的构造方法");
}
然后我们直接运行上次测试的代码我们可以看到一下效果
不难看出Spring默认创建对象的方法就是通过空参构造器来创建
同时需要注意的是:
1.如果类中只有带参数的构造器,按上述方式获取对象是是会出错的
抛出异常:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘user’ defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.wfh.bean.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.wfh.bean.User.()
2.applicationContext中的所有对象是在加载容器时创建的,如下图:
我们可以看到,当我们加载容器对象后,即使我们没有向它索要对象,他也执行了空参构造方法。所以如果配置的bean较多,那么在创建容器的时候会产生内存过大的问题,这种情况在机器硬件性能较为落后的时候体现的更加明显
当然你也可以通过延时加载的方法使容器在加载时先不创建对象,在我们向它索要的时候再创建,具体操作如下:
<!--设置延时加载为true-->
<bean name="user" class="com.wfh.bean.User" lazy-init="true"></bean>
①
②
ii.Scope属性:singleton、prototype
Scope属性声明对象为单例或是多例以及生命周期,它有两种参数:singleton(单例)、prototype(多例的)
一般情况下我们都是以单例模式开发,只有某些特殊情况下需要用多例开发
singleton:
singleton是scope的默认值,即向容器索要的所有对象都是同一个对象,测试实例如下:
<!--applicationContext.xml-->
<bean name="user" class="com.wfh.bean.User" lazy-init="true"></bean>
测试代码:
@Test
public void Test1(){
//根据xml文件加载一个容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
User u1=(User) ac.getBean("user");
User u2=(User) ac.getBean("user");
System.out.println(u1==u2);
}
输出结果:
没有参数的构造方法
true
我们可以看到容器只执行了一次构造方法,说明其内部之创建了一个对象,这就是单例的含义
prototype:
prototype是多例的意思,即向容器索要对象时,容器都会新建一个实例并返回,我们也可以测试一下(把singleton改成prototype,同样用上述测试方法测试即可)
输出结果:
没有参数的构造方法
没有参数的构造方法
false
显然,spring使用了两次构造方法构造了不同的对象,所以用==操作符返回值为false
iii.初始化方法和销毁方法
顾名思义,初始化方法和销毁方法分别是对象刚刚被创建和被销毁后执行的方法,我们有办法知道创建的时间,但是对象是什么时候被销毁的呢,答案是在容器对象被关闭的时候,容器中的所有对象就被销毁了。我们可以尝试给User类添加初始化方法和销毁方法来测试一下
public void userInit(){
System.out.println("Init");
}
public void userDestroy(){
System.out.println("Destroy");
}
<bean name="user" class="com.wfh.bean.User" lazy-init="true" init-method="userInit" destroy-method="userDestroy">
测试(由于ApplicationContext接口没有close方法,所以这里直接用ClassPathXmlApplicationContext类)
@Test
public void Test1(){
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
User u1=(User) ac.getBean("user");
ac.close();
}
输出:
没有参数的构造方法
Init
Destroy
b) 属性注入
i.Set方法注入
我们的User类中有一些成员变量u_id、u_username等,如果我们想要构造对象实例时这些成员变量有值该怎么做呢?我们可以使用property标签为这些变量赋值
<bean name="user" class="com.wfh.bean.User" lazy-init="true" init-method="userInit" destroy-method="userDestroy">
<property name="u_id" value="1"/>
<property name="u_username" value="Jack"/>
<property name="u_password" value="123456"/>
</bean>
测试:
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
User u1=(User) ac.getBean("user");
System.out.println(u1);
输出:
没有参数的构造方法
User{u_id=1, u_username='Jack', u_password='123456'}
这里需要注意的是这些变量都是通过类中的set方法进行设置的,如果类中的某一个参数没有设置set方法,那么容器创建对象时就会出错
如果我们需要注入的不是基本数据类型而是我们写的某个类,那么这个时候我们需要用引用注入的方式设置,如:现在我们新建了一个类Animal,同时我们在User添加一个Animal类型的pet成员变量然后重写一下toString方法
public class Animal {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "Animal{" +
"type='" + type + '\'' +
'}';
}
}
然后我们需要在ApplicationContext文件中定义一个Animal对象用于引用
<bean name="user" class="com.wfh.bean.User" lazy-init="true">
<property name="u_id" value="1"/>
<property name="u_username" value="Jack"/>
<property name="u_password" value="123456"/>
<property name="pet" ref="cat"/>
</bean>
<bean name="cat" class="com.wfh.bean.Animal">
<property name="type" value="小黄猫"/>
</bean>
可以直接用原来的方法测试一下:
ii.构造函数注入
参数的注入还有另一种方式,也就是通过对应的构造函数注入,比如我们要为Animal类添加一个带参数的构造方法
public Animal(String type) {
this.type = type;
}
然后回到配置文件我们会发现报了错
原因是我们没有通过构造函数设置参数,也没有设置空参数构造方法。我们可以用constructor-arg标签进行构造函数注入参数
<bean name="cat" class="com.wfh.bean.Animal">
<constructor-arg name="type" value="小黄猫"/>
</bean>
这样配置以后输出就与原来一样了
iii.容器类型的注入:Array、List、Map
如果我们创建的bean对象中含有容器成员、那么我们需要用另外一些方式为这些成员变量赋初值;
我们先创建一个新的Javabean对象MyCollection,在其中创建私有数组、List、Map、Properties等变量、然后定义get、set方法和toString方法
public class MyCollection {
private Object[] array;
private List list;
private Map map;
private Properties prop;
@Override
public String toString() {
return "MyCollection{" +
"array=" + Arrays.toString(array) +
", list=" + list +
", map=" + map +
", prop=" + prop +
'}';
}
public Object[] getArray() {
return array;
}
public void setArray(Object[] array) {
this.array = array;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Properties getProp() {
return prop;
}
public void setProp(Properties prop) {
this.prop = prop;
}
}
然后打开applicationContext文件为MyCollection配置bean标签
<bean name="collection" class="com.wfh.bean.MyCollection"></bean>
Array、List:
在没有构造函数的前提下,我们同样可以通过上面讲到的set方法注入的方式用property标签注入
<!--数组注入-->
<property name="array">
<array>
<value>123</value>
<value>abc</value>
</array>
</property>
然后回到测试函数里写个代码测试一下:
@Test
public void Test1(){
//根据xml文件加载一个容器对象
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
MyCollection collection = (MyCollection) ac.getBean("collection");
System.out.println(collection);
System.out.println("第1个元素是"+collection.getArray()[0].getClass().getSimpleName());
System.out.println("第2个元素是"+collection.getArray()[1].getClass().getSimpleName());
}
运行结果:
MyCollection{array=[123, abc], list=null, map=null, prop=null}
第1个元素是String
第2个元素是String
可以看到如果我们在做容器注入时,用value标签添加进去的数组元素都是字符串类型的,如果需要添加其他类型的元素我们使用引用标签ref
由于List与数组的配置方式一模一样,只需把array标签换成list标签即可,其他用法都一样,这里就不做过多解释了
Map:
在容器配置文件中,map标签有一个entry标签,用于存放键值对
entry标签有如图所示的这些属性,顾名思义key或者value都可以为String或者引用类型,我们也来测试一下
<!--Map注入-->
<property name="map">
<map>
<entry key="username" value="Jack"/>
<entry key="password" value="123456"/>
</map>
</property>
测试代码:
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
MyCollection collection = (MyCollection) ac.getBean("collection");
System.out.println(collection);
输出结果:
MyCollection{array=null, list=null, map={username=Jack, password=123456}, prop=null}