揭秘:Spring中Bean的那些管理方式

Java技术迷

共 4035字,需浏览 9分钟

 ·

2022-02-11 18:58

点击关注公众号,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技术迷专栏作者 投稿,未经允许请勿转载

1、致歉!抖音Semi Design承认参考阿里Ant Design

2、对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!

3、Redis存储结构体信息,选hash还是string?

4、扫盲 docker 常用命令

5、最全分布式Session解决方案

6、21 款 yyds 的 IDEA插件

7、真香!用 IDEA 神器看源码,效率真高!

点分享

点收藏

点点赞

点在看

浏览 64
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报