揭秘:Spring中Bean的那些管理方式
点击关注公众号,Java干货及时送达
作者 | 汪伟俊
出品 | 公众号:Java技术迷(JavaFans1024)
前面我们已经简单地使用了Spring框架,成功地使用配置文件将对象放入了IOC容器,接下来我们补充一些其它内容。
其它注入方式
现在已知的三种注入方式分别是构造方法注入、setter方法注入和接口注入,其实,Spring还提供了一种属性的注入方式,那就是p名称空间
,看例子:
"user" class="com.wwj.spring.demo.User"
p:name="zs" p:age="20" p:sex="1" p:pet-ref="pet"/>
使用p名称空间需要在xml文件开头添加Schema定义:
xmlns:p="http://www.springframework.org/schema/p"
使用方法非常简单,只要符合p:属性名
格式即可。
还有一些非常特殊的场景,比如给某个属性注入一个null值,该如何做呢?
"user" class="com.wwj.spring.demo.User">
"name" value="null"/>
"sex" value="null"/>
"age" value="null"/>
很显然这样是行不通的,首先后面两个null会报错,因为null字符串不能转为Integer类型,而第一个null虽然不报错,但它也违背了我们的需求,它实质上是一个null字符串,而不是一个null值,所以要是想注入一个null值,我们需要用到
标签,如下:
"user" class="com.wwj.spring.demo.User">
<constructor-arg name="name">
<constructor-arg name="sex">
<constructor-arg name="age">
又比如,我想注入一个<张三>
字符串到name属性中,很显然,因为<
、>
是xml文件中定义的符号,所以肯定是会出现问题的:我们需要对其进行转义:
"user" class="com.wwj.spring.demo.User">
<constructor-arg name="name">
<value>]]>value>
"sex" value="1"/>
"age" value="20"/>
使用的方式即可传入特殊字符。
级联注入
Sping同时支持级联注入,就是在一个对象中若是还有对象类型的属性,则可以级联注入的方式对属性进行赋值,如:
public class User {
private String name;
private Integer age;
private Pet pet;
}
则级联方式注入写法如下:
"pet" class="com.wwj.spring.demo.Pet"/>
"user" class="com.wwj.spring.demo.User">
"name" value="zs"/>
"age" value="22"/>
"pet" ref="pet"/>
"pet.name" value="xg"/>
"pet.age" value="5"/>
注入集合类型
这里我们再介绍一种特殊的注入类型,它就是集合,因为集合的种类很多,有List、Set、Map等等,但是它们的注入方式是类似的,所以直接贴出代码了,首先是Bean类:
public class User {
private String[] strings;
private List list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
}
然后是配置:
"user" class="com.wwj.spring.demo.User">
<property name="strings">
<value>1value>
<value>2value>
<value>3value>
<property name="list">
<value>1value>
<value>2value>
<value>3value>
<property name="set">
<value>1value>
<value>2value>
<value>3value>
<property name="map">
"1" value="1"/>
"2" value="2"/>
"3" value="3"/>
<property name="properties">
<prop key="1">1prop>
<prop key="2">2prop>
<prop key="3">3prop>
虽然每一种类型都有对应的标签,比如数组使用array
标签注入,List使用list
标签注入,而Set使用set
标签注入,但事实上,我们完全可以随意使用这些标签进行注入,比如:
"user" class="com.wwj.spring.demo.User">
<property name="strings">
<value>1value>
<value>2value>
<value>3value>
<property name="list">
<value>1value>
<value>2value>
<value>3value>
在数组中使用list
标签,和在list中使用array
标签是完全没有问题的,但为了规范起见,最好还是使用类型对应的标签吧。
工厂Bean
现在我们已经大概了解Spring是如何创建我们的对象的, Spring会从配置文件中读取配置,并根据配置反射创建出对象放入IOC容器中,Spring为了增强扩展性, 提供了一种方式使得我们可以在创建对象之前进行定制化,实现方式是让被创建的类实现FactoryBean接口,如下:
public class User implements FactoryBean {
private String name;
private Integer age;
@Override
public Object getObject() throws Exception {
Pet pet = new Pet("sg", 5);
return pet;
}
@Override
public Class> getObjectType() {
return Pet.class;
}
}
这一接口给程序提供了大大的灵活性,当我们在配置文件中配置一个User对象时:
"user" class="com.wwj.spring.demo.User"/>
Spring将会反射创建这个User对象,但Spring发现这个对象实现了FactoryBean接口,就会去调用对象中的getObject方法,所以最终创建的对象是由这个getObject方法决定的,FactoryBean是Spring框架的重要组成部分,感兴趣的同学可以翻阅源码了解它的作用。
Bean的作用域
在Spring中,Bean是有作用域的,而且一共有5种作用域,分别如下:
1.singleton:在IOC容器中仅存在一个Bean实例,Bean以单例的形式存在2.prototype:每次从容器获取Bean时,都会创建一个新的对象返回3.request:每次http请求都会创建一个新的对象4.session:同一个Session域共享一个Bean实例5.application:同一个application域共享一个Bean实例
而默认Spring中的Bean是单例的,也就是说,我们如果没有对Bean的作用域进行任何配置,则它就是单例的。
若是想配置Bean的作用域,也非常简单,只需设置scope属性值即可:
"user" class="com.wwj.spring.demo.User" scope="prototype">
"name" value="zs"/>
"age" value="22"/>
我们不妨来测试一下:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
User user = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user == user2);
}
若Bean的作用域为prototype
,则每次从容器获取Bean时,都会创建一个新的对象,所以结果一定为false:
false
而如果将作用域改为singleton
或者不作配置,则结果为true,其它三种作用域使用不多,知道怎么配置即可。
Bean的生命周期
我们已经了解到,Spring是一个高度可扩展的框架,所以Spring在很多地方都提供了扩展点,例如Bean的生命周期,如同人的生命一样,Bean也会经历从出生到消亡的过程,如下:
public class User {
private String name;
private Integer age;
public User() {
System.out.println("构造方法被调用");
}
public void init() {
System.out.println("初始化方法被调用");
}
public void destroy() {
System.out.println("销毁方法被调用");
}
}
在User类中有一个init方法和destroy方法,它们分别会在对应的生命周期过程中被Spring调用,不过现在Spring还不认识这两个方法,需要我们进行配置:
"user" class="com.wwj.spring.demo.User" init-method="init" destroy-method="destroy">
"name" value="zs"/>
"age" value="22"/>
我们可以来测试一下生命周期方法的调用顺序:
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
User user = context.getBean("user", User.class);
}
运行结果:
构造方法被调用
初始化方法被调用
destory方法只有容器关闭时才会调用,即:容器被关闭时才会去销毁对象,如下:
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
User user = context.getBean("user", User.class);
context.close();
}
运行结果:
构造方法被调用
初始化方法被调用
销毁方法被调用
这里需要注意的一点是,构造方法是在初始化方法之前调用的。
本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载