Spring IOC中的Bean对象
一、Bean是什么
突然发现提到了好多次Bean,居然忘记了讲Bean是什么。没事,现在讲也不晚。Java中的Bean是一种规范,是一种特殊的java类。所以我们先来看看Bean的规范。
- Bean必须生成public class类。
- 所有属性必须封装,Bean类的属性都为private。
- 属性值应该通过一组方法(getXxx 和 setXxx)来访问和修改。
- Bean类必须有一个空的构造函数(默认构造函数)。
这些就是Bean的基本规范,我们可以进行扩充,但是这些最基本的都是要满足的,否则就算不上一个标准的Bean。下面我来举一个标准的Bean的例子,注意上面的四个要素。
public class User { private String id; private String name; public User(){} public User(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
是的,Bean就是这样的一种特殊的类,这个东西必须要牢记,因为后面会经常用到。
二、Bean对象的三种构造方式
自从有了spring之后,对象的构造不用我们再去操心了,一切交给spring完成就行。但是,框架再高级,他的底层依旧是在做一些很简单的事,这些事可能我们过去也经常自己来做。因此,学习spring我们既要知其然也要知其所以然,下面我们就来看看有哪些构造Bean对象的方式。顺便了解一下他们的原理是什么。
1. 构造方法构造
这是我们用的最多的一种构造方式。你肯定很好奇,在以前的案例中,为啥我们在配置文件里面写一句:
<bean id="circle" class="com.demo.Circle"/>
然后这个Bean对象就被spring构造出来放入IOC容器中了?其实这东西没啥高大上的,实际上就是spring利用反射调用了这个类的默认构造方法而已。就是这么简单。
那么问题来了,如果我们对象中有属性(暂时假定属性只包含基本类型和String,对象属性会在后面讲),我们又该怎么来构造呢?这个配置文件应该怎么去写呢?很简单,每个Bean对象中都由setter方法(不记得的可以去复习一下上面写的的Bean的结构),框架可以通过调用setter方法,把需要的值传递给属性,这就是属性注入。比如我们试着构造一个本文开头提到了一个Bean的对象:
<bean id="user" class="com.beans.User"> <property name="id" value="666"></property> <property name="name" value="666"></property> </bean>
这段配置主要干了什么事呢?首先,就像往常一样,调用默认构造方法,构造出一个对象,然后调用两个setter方法,分别给两个属性赋值。用传统代码表示就是这样:
User user = new User(); user.setId("666"); user.setName("666");
当然,平时我们自己很少这么做,如果需要给属性赋值,我们可以直接重载构造方法,通过传参的方式在创建对象的同时直接给属性赋值,这样可以减少代码量。下面我们来看看怎样让框架调用Bean的有参构造方法:
<bean id="user" class="com.beans.User" > <constructor-arg name="id" value="666"></constructor-arg> <constructor-arg name="name" value="666"></constructor-arg> </bean>
这段配置就相当于是直接对有参构造方法进行调用,这里的arg是arguement(参数)的缩写,所以constructor-arg就是构造方法的参数列表的意思,name很显然就是参数名了,value就是我们要填入的参数值。所以我们就要为参数列表中所有的参数进行填值,就像我们过去做的一样:
User user = new User("666", "666");
现在你应该已经了解了spring框架究竟干了什么了吧。看似高大上的框架做的事也不过如此嘛,最基本的东西是永远逃不掉的。
2. 静态工厂构造
这种方式一般只有特定场景会使用,所以我们就简单看看就行。这里的静态工厂和我们前面讲的工厂模式差不多,首先我们要有一个工厂:
public class UserFactory { public static User createPerson(){ return new User(); } public static User createPerson(Integer id,String name){ return new User(id,name); } }
下面我们来看看配置文件如何书写:
<bean id="user" class="com.beans.factory.UserFactory" factory-method="createPerson"> <constructor-arg name="id" value="666"></constructor-arg> <constructor-arg name="name" value="666"></constructor-arg> </bean>
使用静态工厂方法创建Bean实例需要为<bean />元素指定除id外如下属性:
- class:指定静态工厂的全类名(相当于指定工厂的地址)
- factory-method:指定由静态工厂的哪个方法创建该Bean实例(指定由工厂的哪个车间创建Bean)
- 方法参数:如果静态工厂方法需要参数,则使用<constructor-arg />元素传入。
3. 实例工厂构造
实例工厂和静态工厂唯一的区别就是我们需要先实例化工厂对象,才能构造我们需要的Bean对象:
<!-- 先构造工厂对象,class指定该工厂的实现类,工厂对象负责产生其他Bean实例 --> <bean id="userFactory" class="com.beans.factory.UserFactory"/> <!-- 再引用工厂对象来配置其他Bean --> <bean id="user" factory-bean="userFactory" factory-method="createPerson"> <constructor-arg name="id" value="666"></constructor-arg> <constructor-arg name="name" value="666"></constructor-arg> </bean>
调用实例化工厂需要为<bean />指定除id外如下属性:
- factory-bean :该属性指定工厂Bean的id
- factory-method:该属性指定实例工厂的工厂方法。
- 方法参数:如果静态工厂方法需要参数,则使用<constructor-arg />元素传入。
实例工厂和静态工厂平时用的不算特别多,平时开发时用的最多的还是最开始说的构造方法构造。现在有个重要的问题,如果Bean对象的属性是一个对象呢?这就是我们下面要讲的――依赖注入(DI)
三、依赖注入
讲spring IOC,不能不讲DI,这两个东西基本上可以说是相辅相成、唇亡齿寒的,所以现在我们就来看看IOC和DI的关系。控制反转――IOC(Inversion of Control)的意思是创建对象的控制权进行转移。以前创建对象的主动权和创建时机是由自己把控,而现在这种权力转移到了spring。
IOC的最重要的一个功能是在系统运行中,动态的向某个对象提供它所需要的其他对象作为属性。这一点是通过DI(Dependency Injection,依赖注入)来实现的。下面我来举一个依赖注入的例子:
比如A类需要使用JBDC,以前我们要在A类中编写代码来自己new一个Connection对象(这边咱先不考虑数据库连接池)。现在有了 spring,我们就只需要用配置文件告诉spring,A类中需要一个Connection对象,至于这个Connection怎么构造,何时构造,A类不需要知道。在运行时,spring会在适当的时候构造一个Connection对象,然后注入到A类当中,这样就完成了对各个对象之间关系的控制。A类需要依赖Connection这个属性才能正常运行,而这个Connection对象是由spring注入到A中的,依赖注入的名字就这么来的。
看了这么多,是不是发现其实依赖注入和我们上文讲的属性注入其实是同一类东西,都是动态地给对象的属性赋值,只不过这里的属性是一个对象,上文讲的的属性是简单类型而已。依赖注入听起来很高端,实际上就是给对象的对象属性赋值而已。
四、Bean的生命周期
讲了如何编写配置文件,那么你一定好奇在spring内部每时每刻这个Bean的状态应该是怎么样的。于是我们就来看看spring中Bean的生命周期。不过,有一点要提前说明,Spring只帮我们管理单例模式(singleton)Bean的完整生命周期,对于 多例模式(prototype)的Bean,Spring 在创建好交给使用者之后则不会再管理后续的生命周期了。单例模式和多例模式放在后面讲。
来看看我们的Bean对象是如何产生的。这个图乍一看很吓人,其实里面许多东西都是一些扩展点,他们穿插于Bean的生命周期中,我们一开始不需要去折腾这些扩展点,我们的关注点应该在Bean的生命周期本身。
其实,Bean的生命周期一共只有四个阶段,分别是:
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
要彻底搞清楚Spring的生命周期,首先要把这四个阶段牢牢记住。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段,我们可以自己在这两个函数里面书写我们需要的逻辑。在这四个阶段之间穿插的各种扩展点,以后再讲。
首先我们先来看前三个,他们的主要逻辑都在doCreateBean方法中,顺序调用三个方法,这三个方法与三个生命周期阶段一一对应:
//PS:下面的代码已经删去暂时不用了解的部分,只留下核心部分 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // 实例化 instanceWrapper = createBeanInstance(beanName, mbd, args); } // Initialize the bean instance. Object exposedObject = bean; try { // 属性赋值 populateBean(beanName, mbd, instanceWrapper); // 初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); } }
有了这三个,再加上一个销毁,组成了Bean的最重要的四个生命周期阶段。如下图所示,当然,下图除了这四个基本的生命周期阶段之外,还加上了一些扩展点:
初学时我们只需要知道这些扩展点的存在即可,至于他们具体该怎么用,后面用到的时候会讲。我们在这里需要明白的是四个最基本的生命周期。其他的扩展点平时开发很少用到,但是在读一些Java中间件源码的时候,这些扩展点就必须得弄明白了。
Ioc中Bean的作用域
在Spring中,可以在< bean >元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring值为每个在IOC容器里声明的bean创建唯一一个实例,整个Ioc容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean。该作用域被称为singleton,它是所有bean的默认作用域。
当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,Ioc容器在获取bean的实例时创建bean的实例对象
bean的作用范围和生命周期
单例对象: scope=“singleton”
- 一个应用只有一个对象的实例。他的作用范围就是整个引用
- 生命周期:
- 对象出生:当应用加载,创建容器时,对象创建
- 对象活着:只要容器在,对象一直活着
- 对象死亡:当应用卸载,销毁容器时,对象销毁
多例对象:scope=“prototype”
- 每次访问对象时,都会重新创建对象实例
- 生命周期:
- 对象出生:当使用对象时,创建新的对象实例
- 对象活着:只要对象在使用中,就一直活着
- 对象死亡:当对象长时间不用时,就被java的垃圾回收器回收了。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://solo-solo.blog.csdn.net/article/details/105966366