欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring | IOC之Bean的三种实例化

程序员文章站 2022-06-04 23:43:56
...

快速目录

  • 1 bean的含义
  • 2 bean的命名
  • 3 bean的实例化
  • 4 参考资料

在Spring中,对象包装成Bean,供IOC容器使用。Spring的Beans组件已经明确Bean的定义和Bean的解析,我们的关注点是Bean的创建。在学习Bean的创建之前,先简单了解下Bean的含义和命名。

1 Bean的含义

Bean这个概念是随着Spring框架而出来的。早在1996年,有JavaBean这个概念,后来因为JavaBean太简易不能满足复杂运用程序的要求,因此1998年提出EJB(Enterprise JavaBean)这个概念,后来又因为EJB太过复杂,因此随着Spring框架提出Bean这个概念。详细的演变过程可以看下面参考资料。
那么Bean到底是什么?简单的说,被IOC容器管理的对象称之为Bean,反之没有被容器管理的对象不能称之为Bean,而是一个纯粹的对象(可以思考下这里对象和bean有什么区别?)。
那么如何判断对象是否被IOC容器管理?IOC容器管理的bean又从何而来?
这两个问题的答案藏在bean的配置和初始化中。详细看第三小节。

2 Bean的命名

bean的命名是指给bean声明一个标识符,bean标识符的作用在于为某个bean命名,定义全局(容器内)唯一标识符,方便区别于其他bean,便于在程序中获取该bean。这里的标识符可以用id来表示,也可以用name表示,可以用id和name一起表示,也可以不用id和name来表示。下面分别讨论不同情况下bean的命名方式:

2.1 不声明标识符的命名方式

通过xml配置文件实现:

<!-- 不指定标识符声明bean class表示全路径类名 --> 
<bean class="com.syx.chapter1.HelloApiImp"></bean>

这种命名方式的特点:

  • 代码中通过getBean<Class<>>获取该bean,参数为bean的class类;
    - 生成的对象是单例;
    我很好奇没有命名的bean会如何标识自己,于是尝试调用bean的toString()方法,并打印到控制台,结果为:
com.syx.chapter1.HelloApiImp@12843fce

这是很常见的类名加对象哈希码,不能说是它的标识符。后来在错误信息中无意发现Spring给他的默认名:

com.syx.chapter1.HelloApiImp#0

如果配置文件中有多个这样的bean,则产生的bean命名为数字相应+1。
即:com.syx.chapter1.HelloApiImp#1,com.syx.chapter1.HelloApiImp#2
所以getBean(Class<>)方法应该是按照默认命名方式去查找的,当有多个该class类的对象时,该方法获取bean时会报错

2.2 id标识符的命名方式

通过XML配置文件实现:

<!-- id 表示bean的名字,class表示全路径类名 -->  
<bean id="hello" class="com.syx.chapter1.HelloApiImp"></bean>

这种命名方式的特点:

  • 代码中通过getBean(String arg0,Class<> arg1)获取bean,arg0为id,arg1为类名;
    PS:思考一下,此时的bean是否可以通过getBean(class<>)方式获取?如果2.1小节的bean和此时的bean同时存在,又是否可以通过getBean(class<>)获取呢?如果不行,2.1小节中的bean该如何获取?
  • id在这个配置文件中必须唯一;
  • id后面只能跟一个值;
2.3 name标识符的命名方式

通过XML配置文件实现:

<!-- name 表示bean的名字,class表示全路径类名 -->
<bean name="hello1" class="com.syx.chapter1.HelloApiImp"></bean>

特点跟2.2小节一样。只有最后一点不同,即name后面可以跟多个名字,名字之间通过分号、空格或者逗号隔开,这里只有第一个name名为标识符,后面的都为别名,表示如下:

<bean name="hello1 ,hello1_1 ,hello1_2" class="com.syx.chapter1.HelloApiImp"></bean>

注:所有的name值在IOC容器中都必须是唯一的,包括别名。
PS:思考一下,出现以下情况,获取bean会不会报错?

<!-- id 表示bean的名字,class表示全路径类名 --> 
<bean id="hello" class="com.syx.chapter1.HelloApiImp"></bean>
<!-- name 表示bean的名字,class表示全路径类名 -->  
<bean name="hello" class="com.syx.chapter1.HelloApiImp"></bean>
2.4 id和name同为标识符的命名方式

通过XML配置文件实现:

<!-- id和name表示bean的名字,class表示全路径类名 -->
<bean id="hello2" name="hello2" class="com.syx.chapter1.HelloApiImp"></bean>

这种命名方式的特点:

  • 代码中通过getBean(String arg0,Class<> arg1)获取bean,arg0为id或者name;
  • id为标识符,name为别名;
  • id和name可以相同也可以不同,但与其他bean的id和name必须不同;
    这里不得不提出id和name之间的区别:在xml中id是一个真正的XML id属性,因此当其他的定义来引用这个id时就体现出id的好处了,可以利用XML解析器来验证引用的这个id是否存在,从而更早的发现是否引用了一个不存在的bean,而使用name,则可能要在真正使用bean时才能发现引用一个不存在的bean。

最后还要注意一下id和name标识符的命名规则,首先需要遵守XML的命名规范,再者既然是标识符,自然要符合java标识符的命名规则。具体规则看参考资料。

3 bean的实例化

上面讲到通过XML配置文件为bean命名,便于代码中获取该bean,那么IOC容器又是如何实例化bean的?传统的实例化就是通过new关键字生成一个对象,在IOC容器中有三种方式来实例化bean。

3.1 构造函数方式

构造函数分两种:无参数构造函数(类默认的构造函数)和有参数构造函数
下面以代码来说明构造函数实例化bean。
首先新建一个简单的java项目,新建资源包resources,建立子目录bean;新建文件包lib,导入spring框架系列架包(这里导入最新的5.1.0版本的架包,注意5.0以上版本依赖JDK8.0及以上),然后建立一系列的包和类,以及xml配置文件,目录结构如下所示:
Spring | IOC之Bean的三种实例化
先定义一个person类:

package com.starry.bean.constructorInitialzation;
public class Person {
	private String name;
	private int age;
	public Person(){
		name = "zhangSan";
		age = 25;
	}
	public Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString(){
		return "person:" + name + ", " + age;
	}
}

类中声明默认不带参数的构造方法,以及带参数的构造方法,并重写了toString方法。

配置文件constructorInitialzation.xml声明一个bean:

<bean id="person" class="com.starry.bean.constructorInitialzation.Person"></bean>

Test类中代码:

public class Test {
	public static void main(String[] args) {
		ApplicationContext iocContainer = new ClassPathXmlApplicationContext("bean/constructorInitialzation.xml");
		Person person1 = iocContainer.getBean("person", Person.class);
		System.out.println(person1);
	}
}

运行结果为:

person:zhangSan, 25

很明显这是采用了无参数构造方法实例化person对象。

在配置文件constructorInitialzation.xml声明另外一个bean:

<bean id="person_1" class="com.starry.bean.constructorInitialzation.Person">
  	<constructor-arg index="0" value="liSi" />
  	<constructor-arg index="1" value="20" />
</bean>

注意:index表示构造函数中参数的顺序,从0开始。当然,这里index也可以换成name,即:

<bean id="person_1" class="com.starry.bean.constructorInitialzation.Person">
  	<constructor-arg name ="name" value="liSi" />
  	<constructor-arg name="age" value="20" />
</bean>

修改test类中getBean方法的第一个参数,可以得到结果:

person:liSi, 20

很明显这里通过有参数构造方法实例化person_1对象。

3.2 静态工厂方式

静态工厂顾名思义就是通过静态工厂实例化bean。首先给出静态工厂类:

import com.starry.bean.constructorInitialzation.Person;
public class PersonStaticFactory {
	public static Person getPersonBean(String name, int age){
		return new Person(name, age);
	}
}

getPersonBean是静态工厂方法,返回一个Person类的实例。

配置文件staticFactoryInitialzation.xml声明一个bean:

<bean id="personStaticFactory " class="com.starry.bean.staticFactoryInitialzation.PersonStaticFactory" factory-method="getPersonBean">
  	<constructor-arg name="name" value="liSi" />
  	<constructor-arg name="age" value="20" />
</bean>

bean标签中多了一个factory-method元素,值为代码中的工厂方法。这样也能实例化bean。

3.3 工厂方法方式

工厂方法表明实例化bean是普通工厂,而非静态工厂。首先声明工厂类和工厂方法:

import com.starry.bean.constructorInitialzation.Person;
public class PersonFactory {
	public Person getPersonBean(String name, int age){
		return new Person(name, age);
	}
}

getPersonBean是工厂方法,其方法是非静态的。

配置文件factoryInitialzation.xml声明一个bean:

<bean id="personFactory" class="com.starry.bean.factoryInitialzation.PersonFactory"></bean>
<bean id="person2" factory-bean="personFactory" factory-method="getPersonBean">
    <constructor-arg name="name" value="liSi" />
    <constructor-arg name="age" value="20" />
</bean>

配置文件中首先声明一个工厂的bean对象personFactory,然后通过personFactory和方法getPersonBean实例化bean对象person2。
以上实例化bean的三种方式,我们只是简单的修改配置文件,而在test文件中几乎不需要改动,着实体现IOC容器的灵活。

此时回到第一节结尾时的两个问题:如何判断对象是否被IOC容器管理?IOC容器管理的bean又从何而来?感觉冥冥之中已经回答了这个问题,但又很模糊,说不清;其实要理解这两个问题,还得先理解容器的本质,前面说到容器是bean关系的集合,bean在哪定义关系,哪里就是容器,虽然我们目前还没有学习到bean间的关系,但是bean在实例化的时候会确定好关系,因此bean实例化的地方就是容器。故体现在一个XML配置文件就是一个容器在XML文件内通过bean标签声明的对象就是被IOC容器管理的对象,换句话说,就是IOC容器管理的bean就是配置文件中声明的对象。

结束了?NO,既然学会了三种实例化bean的方式,那么应该要比较一下这三种方式有什么不同,分别在什么情况使用以及各有什么优缺点。详情见下表:

实例化bean的方式 优点 缺点 使用场景
构造函数方式 - - 大部分场景下使用
静态工厂方式 - - -
工厂方法方式 - - -

PS:这部分比较内容需要根据实例化原理判断优缺点,所以放到后面来总结。

4 总结

学习的时间总是过的很快,关于bean这部分的基础知识基本学完了。主要学到以下几个知识点:

  • bean产生的背景;
  • bean的本质含义;
  • bean的4种命名方式;
  • bean的3种实例化方法;

到此为止,只学习了单个bean的实例化,还没有学习bean与bean之间的关系是如何建立的,那就留着下一次学习吧。

源码下载

5 参考资料

javaBean理解:https://www.cnblogs.com/zterry/p/6863388.html
EJB理解:https://blog.csdn.net/jojo52013145/article/details/5783677
xml命名规范:https://blog.csdn.net/u012152619/article/details/42705023
JAVA标识符规范:https://www.cnblogs.com/ouysq/p/4474267.html