bean的知识点有以下五个部分
第一部分,介绍bean的基础,bean的概念,bean的基础配置,名称,注入方式等。
第二部分,介绍bean自动注入。
第三部分,介绍bean的依赖关系,循环依赖,前置。
第四部分,介绍bean的作用域。
第五部分,介绍bean的生命周期。
1、基础
1.1 概念
bean的概念,引用原著:
A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container
这段话包含两个要素
A bean is an object,bean是Java语言中的任何一个对象。Spring配置文件中的bean标签本质就是建立它与类之间的关系,标签内部属性与Java对象属性之间的关系。所以在配置bean时,一定熟悉与之关联的对象。
IOC container的作用:它用来管理,创建,配置bean。
bean本质上是Java类的抽象,配置bean的过程,本质就是将Java类抽象为bean的过程。
bean的class属性为Java类的全名称。
bean的属性为Java类的属性。属性的数据类型若为一般数据类型,有相应的转换器,若为其他对象类型,将其他类型注入到容器中,使用ref引用。
bean的配置对应某些方法的参数,例如构造器,实现方式有三种,根据参数的顺序,类型,名称。
1.2 命名
Bean的命名有三种方式,id,name,alias。
id:bean的唯一标识,不能重复,一般都是使用此方式为bean命名。
name:bean的名称,IOC容器允许bean的名称重复,一个bean可以拥有多个名称。不指定时,类名称首字母小写作为name属性的默认值。
alias:bean的别名,一个类可以有多个别名。别名最好有意义。
<!-- naming bean, bean的命名 --> <!-- id方式 --> <bean id="user" class="learn.springcore.bean.User"/> <!-- name方式 --> <bean name="manager" class="learn.springcore.bean.User"/> <bean name="vip" class="lean.springcore.bean.User"/> <!-- alias方式 --> <alias name="manager" alias="boss"/> <alias name="manager" alias="projectManager"/>
1.3 注入
注入方式有三种,构造器,set方法,工厂方法。
1.3.1 构造器
当使用类的默认构造器时,设置bean的class属性为类全名即可。
当使用类的其他构造器时,需要在bean内部配置constructor-arg标签,它代表构造器的参数。参数的映射方式有三种方式,参数的顺序,参数的名称,参数的类型。
// 假设用户存在三个构造器,默认构造器,User(String name),User(String name, int age) <!-- naming bean, bean的命名 --> <!-- 默认构造器方式,class属性为类全名即可 --> <bean id="user" class="learn.springcore.bean.User" /> <!--只有单个名称的构造器,使用参数名称映射,name属性的值为参数的名称 --> <bean id="user" class="learn.springcore.bean.User"> <constructor-arg name="name" value="jack" /> </bean> <!-- 第三个构造器,使用参数顺序映射,0映射为名称参数,1映射为年龄参数 --> <bean id="user" class="learn.springcore.bean.User"> <constructor-arg index="0" value="jack" /> <constructor-arg index="1" value="18" /> </bean> <!-- 第三个构造器,使用参数类型映射,参数的数据类型不予许重复,string映射为名称参数,int映射为年龄参数 --> <bean id="user" class="learn.springcore.bean.User"> <constructor-arg type="java.lang.String" value="jack" /> <constructor-arg type="java.lang.Integer" value="18" /> </bean>
1.3.2 set方法
set方法本质就是注入属性值。
当为基本数据类型时,使用property子标签,其中name为属性名,value为属性值
<property name=”fieldName” value=”fieldVal”>
当为对象时,使用ref子标签,其中bean为对象的名称(ID,name,alias中的任意一个)。此时bean引用的对象必须已注入到容器中。
<ref bean="ID "></ref>
1.3.2.1 集合
注入集合类型的属性。
1.3.2.1.1 List
<property name="someList"> <list> <value>element</value> <ref bean="date"></ref> </list> </property>
1.3.2.1.2 Set
<property name="someSet"> <set> <value>element</value> <ref bean="date"></ref> </set> </property>
1.3.2.1.3 Map
<property name="someMap"> <map> <entry key="entry" value="just something"/> <entry key="birthDate" value-ref="date"/> </map> </property>
1.3.2.1.4 Properties
<property name="someProperties"> <props> <prop key="name">jack</prop> <prop key="age">21</prop> </props> </property>
1.3.2.2 null
注入null值,示例如下:
<property name="nullProp"> <null/> </property> <property name="name" value=""/>
1.3.2.3 P 前缀 & C前缀
P字母代表property,它是属性的意思,为了更方便配置bean的属性
C字母代表constructor,它是构造器的意思,为了更方便配置bean构造器的参数。
使用它们,首先需要引入schema。
<!-- p的schema--> xmlns:p="http://www.springframework.org/schema/p" <!-- c的schema--> xmlns:c="http://www.springframework.org/schema/c"
其次需要建立映射关系,
当属性为基本数据类型时,p:propName它会映射为对象的propName属性,它的值为属性的值。
当属性为引用数据类型时,p:propName-ref它会映射为对象的propName属性,它的值为容器中引用对象bean的ID或name。
1.3.3 工厂方法
静态工厂方法方式下,class属性为工厂方法的类全名,factory-method为静态方法名称,它的返回值为注入bean的类型
<bean id="user" class="learn.springcore.factory.UserFactory" factory-method="getInstance"/>
此时UserFactory类存在getInstance方法,它必须是static修饰,返回值为User对象。
非静态方法方式,class属性不指定,factory-bean属性为工厂类bean标签的ID或者name属性,factory-method为工厂类的方法。此时工厂类必须已注入到容器中。
<bean id="user" factory-bean="userFactory" factory-method="getInstance"/>
1.4 延迟加载
默认情况下,伴随着容器的创建,会触发容器中所有bean的创建。若想实现按需创建,即在获取该bean的时,才触发bean的创建过程,此时可以设置lazy-init的值为true。
当bean被其他bean依赖时,该属性会被忽略。例如beanA依赖bean B,此时设置beanB的lazy-init属性为true。容器的创建过程会触发beanA的创建过程,而beanA的创建过程会触发所有它依赖类的创建过程,此时即使beanB的lazy-init为true,它也会被创建。
2、 自动注入
spring提供一种自动注入方式。它有四种方式。
No:相当于禁用自动配置功能,所有的依赖都需要手动去配置。
byName:根据属性名称,例如当User对象存在date属性时,此时若容器中存在id为date的bean标签,会自动获取date,并将返回的对象作为User对象date属性的值
<bean id="user" class="learn.springcore.bean.User" p:birthDate-ref="date"/> <bean id="date" class="java.util.Date"/>
这种方式使用ref引用到id为date的bean标签。手动配置。
<bean id="user" class="learn.springcore.bean.User" /> <bean id="birthDate" class="java.util.Date"/>
此时在创建User对象,设置birthDate属性时,会获取标识为birthDate的bean。并将返回的对象作为birthDate的属性值。标识可以为id,name,alias。
byType:根据属性的数据类型,例如User对象存在birthDate属性,它的数据类型为java.util.Date时,当容器中存在java.util.Date类型的对象时,会将该对象作为属性值。
constructor:与byType相似,只不过它只适用于构造器的参数。
无论是byType,还是byName,当容器中存在多个匹配项时,会报错。
当启用自动注入功能时(autowire的属性值不为no),需要查找匹配的bean时,可以设置当前bean标签autowire-candidate属性为false,忽略当前bean。
例如指定自动注入方式为byName时,创建User对象,设置birthDate属性的值,此时会在容器中查询标识为birthDate的bean,当设置autowire-candidate属性为false时,<bean ** autowire-candidate="false"/>,查找过程会忽略该bean。
3、依赖关系
3.1 循环依赖
假设存在A依赖B,B又依赖A的情形,
当它们以构造器方式去配置依赖关系时,会存在错误,这是因为相互在等待对方构造器的调用。
当以Set方式去配置时,此时需要首先去创建对象A,或者是对象B,然后在调用它的set方法,此时不会出错,是因为IOC容器首先会去调用A的构造器,或者是B的构造器,然后调用a.setB或者是b.setA,这两个方法之间不存在任何关系,完全可以把构造器的返回的对象A或者B作为这两个方法的参数。
当以工厂方法参数去配置时,是同理的,因为彼此可以调用默认的构造器,在调用完构造器之后,然后才去调用工厂方法。
3.2 前置依赖
当bean B的创建过程作为bean A创建过程的前置条件时,并且此时A与B不存在依赖关系。即要保证bean B在bean A 之前被创建。此时可以通过设置beanA的depends-on属性,它的值为beanB的ID,此时在创建beanA时,总会先创建beanB,保证创建bean的顺序。
这种情况通常发生在bean之间存在间接依赖关系。
3.3 单例依赖原型
当singleton的beanA依赖prototype的beanB时,要达到每次获取beanB不同,只能通过方法注入。本示例中User类为beanA,Address类为beanB。
第一种set方式,beanA实现ApplicationContextAware接口,在获取beanB时通过applicationContext对象获取
public class User implements ApplicationContextAware { // applicationContext对象, private ApplicationContext applicationContext; // 用户的家庭地址 private Address homeAddress; // 通过applicationContext获取,不再是this.homeAddress public Address getHomeAddress() { return this.applicationContext.getBean("address", Address.class); } public void setApplicationContext(ApplicationContext arg0) throws BeansException { this.applicationContext = arg0; } }
第二种方式使用lookup-method子标签,此时该方法只能是抽象方法
public abstract Address getHomeAddress(); <bean id="address" class="com.bean.Address" scope="prototype"/> <!-- 定义User类,它的作用域为singleton --> <bean id="singleton_user" class="com.bean.User"> <lookup-method name="getHomeAddress" bean="address"/> </bean>
其中name属性为方法的名称,方法的返回值为Address类型。Bean属性值为Address类的bean ID。
个人建议采用第一种方式,因为第二种必须包含抽象方法,类必须为抽象类。
4、作用域
作用域有两种,内置,自定义。
4.1 内置
4.1.1 Singleton
对于同一个IOC容器,每次获取对象,都返回相同的实例。它是默认的作用域。
设计模式中的单例是指在每个JVM中只存在一个实例,而这里是指每个IOC容器,即每一个applicationContext对象。
4.1.2 Prototype
对于同一个IOC容器,每次获取对象,都会返回不同的实例。
// 获取prototype的User实例 User user3 = ac.getBean("prototype_user",User.class); User user4 = ac.getBean("prototype_user",User.class); System.out.println(user3 == user4); // 返回false
当singleton的beanA依赖singleton的beanB时,beanA和beanB都只会创建一次,并且beanB在beanA之前创建
当singleton的beanA依赖prototype的beanB时,理论上每次获取beanA都应该返回不同的beanB,但实际上每次获取得到的beanB是相同的,是因为对象beanA只会创建一次,依赖关系在创建过程中已经决定。
// 获取User实例 User user1 = ac.getBean("singleton_user",User.class); User user2 = ac.getBean("singleton_user",User.class); System.out.println(user1.getHomeAddress() == user2.getHomeAddress());
当prototype的beanA依赖prototype的beanB时,每次获取都会创建beanA和beanB,并且beanB在beanA之前创建。
当prototype的beanA依赖singleton的beanB时,每次获取都会创建beanA,如果beanB不存在,则创建beanB,在beanA之前创建。之后都会使用已创建的beanB。
4.1.3 web相关
request的作用域只有在Web application下才会有效果。
Session也只有在web application下才有用,对应Session的作用域。
Application对应web application的Application作用域。
HttpRequest,Session,Application这些都是Servlet的九大内置对象。
webSocket对应webSocket的作用域。
4.2 自定义
编写自定义作用域的步骤只有两步:
第一步,编写自定义XXScope,实现Scope接口
第二步,注册XXScope,通过ConfigurableBeanFactory的registerScope方法,它的第一个参数为作用域的名称,第二个参数为作用域对象。
Spring框架中有simpleThreadScope,但是没有注册,可以尝试注册线程作用域作为练习。
5、生命周期
5.1 概念
Bean的生命周期如下图
配置bean的生命周期,有三种方式:LifeCycle callback, BeanNameAware & ApplicationContextAware, 其他Aware接口。
Bean的生命周期分为三个阶段,创建,使用,销毁。
每个阶段的步骤如下:
创建:创建对象包含两个层面,第一层面是指程序层面,调用它的构造器,以及设置它的依赖等。第二层面是指用户层面,执行用户自定义的init方法。最典型的示例就是Servlet。
- 加载类,准备环境。
- 环境已就绪,准备创建对象。触发BeanPostProcessor的xxbefore方法
- 调用对象的构造器,若存在@PostContruct注解,在调用构造器之前执行此注解下的方法。之后执行构造器
- 程序层面的创建完成,执行用户自定义的init方法,若存在InitializingBean接口,在init方法调用之前执行此接口中的方法。
- 对象创建完毕。触发BeanPostProcessor的xxAfter方法。
使用:
销毁:
- 销毁之前,若存在destroy方法,执行。若存在@Predestroy注解,在destroy方法之前执行此注解下的方法。@Predestroy,@PostConstruct都是Java的注解,非spring框架注解,它们的含义与destroy,init方法完全相同,存在一个即可。
- 调用完之后,触发DisposableBean的destroy方法。
注:
- 要使用注解,必须配置<context:annotation-config />
- BeanPostProcessor,InitializingBean,DisposableBean必须在容器中,且在对象创建之前存在才生效。
5.2 示例
下面演示User bean的生命周期。
配置
<!-- 开启注解 --> <context:annotation-config/> <!-- BeanInitialization --> <bean id="initalization" class="learn.springcore.bean.lifeCycle.TestBeanInitialization"/> <!-- DisposableBean --> <bean id="disposable" class="learn.springcore.bean.lifeCycle.TestDisposableBean"/> <!-- BeanPostProcessor --> <bean id="postProcessor" class="learn.springcore.bean.lifeCycle.TestBeanPostProcessor"/> <!-- 实体User类 --> <bean id="user" class="learn.springcore.bean.User" init-method="init" destroy-method="destroy"/>
代码
// 获取applicationContext对象 AbstractApplicationContext ac = new ClassPathXmlApplicationContext("spring/application.xml");; // 获取User实例 User user = ac.getBean("singleton_user", User.class); // 关闭容器 ac.close();
创建阶段效果
// 创建阶段,运行User实体类的构造器 2020-04-13 15:31:59.615 learn.springcore.bean.User [main] INFO: **************User Constructor Start*************** 2020-04-13 15:31:59.615 learn.springcore.bean.User [main] INFO: user constructor execution 2020-04-13 15:31:59.615 learn.springcore.bean.User [main] INFO: *************User Constructor End**************** // 创建阶段,注入User实体类的post process before 2020-04-13 15:31:59.617 learn.springcore.bean.lifeCycle.TestBeanPostProcessor [main] INFO: **************post process before Initialization Start*************** 2020-04-13 15:31:59.617 learn.springcore.bean.lifeCycle.TestBeanPostProcessor [main] INFO: postProcessBeforeInitialization execution 2020-04-13 15:31:59.617 learn.springcore.bean.lifeCycle.TestBeanPostProcessor [main] INFO: *************post process before Initialization End**************** // 创建阶段,User实体类中有@PostConstructor 2020-04-13 15:31:59.617 learn.springcore.bean.User [main] INFO: **************post construct Start*************** 2020-04-13 15:31:59.617 learn.springcore.bean.User [main] INFO: post construct execution 2020-04-13 15:31:59.617 learn.springcore.bean.User [main] INFO: *************post construct End**************** // 创建阶段,User实体类中的init方法 2020-04-13 15:31:59.618 learn.springcore.bean.User [main] INFO: **************init Start*************** 2020-04-13 15:31:59.618 learn.springcore.bean.User [main] INFO: init execution 2020-04-13 15:31:59.618 learn.springcore.bean.User [main] INFO: *************init End**************** // 创建阶段,注入User实体类的post process after 2020-04-13 15:31:59.618 learn.springcore.bean.lifeCycle.TestBeanPostProcessor [main] INFO: **************post process after Initialization Start*************** 2020-04-13 15:31:59.618 learn.springcore.bean.lifeCycle.TestBeanPostProcessor [main] INFO: postProcessAfterInitialization execution 2020-04-13 15:31:59.618 learn.springcore.bean.lifeCycle.TestBeanPostProcessor [main] INFO: *************post process after Initialization End****************
销毁阶段效果
// 销毁阶段,运行User中@Predestroy注解的方法 2020-04-13 15:31:59.634 learn.springcore.bean.User [main] INFO: **************pre destroy Start*************** 2020-04-13 15:31:59.634 learn.springcore.bean.User [main] INFO: pre destroy execution 2020-04-13 15:31:59.634 learn.springcore.bean.User [main] INFO: *************pre destroy End**************** // 销毁阶段,运行User配置的destroy方法 2020-04-13 15:31:59.634 learn.springcore.bean.User [main] INFO: **************destroy Start*************** 2020-04-13 15:31:59.634 learn.springcore.bean.User [main] INFO: destroy execution 2020-04-13 15:31:59.634 learn.springcore.bean.User [main] INFO: *************destroy End**************** // 销毁阶段,运行DisposableBean的destroy方法 2020-04-13 15:31:59.634 learn.springcore.bean.lifeCycle.TestDisposableBean [main] INFO: **************Disposable Bean Start*************** 2020-04-13 15:31:59.634 learn.springcore.bean.lifeCycle.TestDisposableBean [main] INFO: Disposable Bean execution 2020-04-13 15:31:59.634 learn.springcore.bean.lifeCycle.TestDisposableBean [main] INFO: *************Disposable Bean End****************
注入InitializingBean,disposableBean也会触发BeanPostProcessor中的方法。