Spring | IOC之Bean的三种实例化
快速目录
- 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配置文件,目录结构如下所示:
先定义一个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
上一篇: 快速入门Vue
下一篇: php.ini 修改无效有关问题