Spring的bean创建详解
IoC容器,又名控制反转,全称为Inverse of Control,其是Spring最为核心的一个组件,其他的组件如AOP,Spring事务等都是直接或间接的依赖于IoC容器的。本文主要讲解IoC容器所管理的bean的几种创建方式,并且详细讲解了xml配置中相关参数的配置。
在IoC容器中,bean的获取主要通过BeanFactory
和ApplicationContext
获取,这里ApplicationContext
实际上是继承自BeanFactory
的,两者的区别在于BeanFactory
对bean的初始化主要是延迟初始化的方式,而ApplicationContext
对bean的初始化是在容器启动时即将所有bean初始化完毕。如下是BeanFactory
的主要接口:
Object getBean(String name) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
String[] getAliases(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
可以看到,BeanFactory
中主要提供的是一些查询bean的方法,而bean的创建和管理实际上是由BeanDefinitionRegistry
来进行的。BeanDefinitionRegistry
会为其管理的每个bean都创建一个BeanDefinition
实例,该实例中主要包含当前bean的名称,类型,是否抽象类,构造函数参数等信息。BeanDefinition
有两个主要的实现类RootBeanDefinition
和ChildBeanDefinition
,这里RootBeanDefinition
主要用于创建并且注册一个bean到BeanDefinitionRegistry
中,ChildBeanDefinition
则主要用于预处理具有parent/child的bean定义。如下图为IoC容器管理bean的主要类结构图,这里DefaultListableBeanFactory
是BeanFactory
和BeanDefinitionRegistry
的一个默认实现类:
IoC容器创建bean主要有三种方式:硬编码,元数据和配置文件。这里硬编码方式也即显示的使用上面的类图关系将bean以及它们之间的依赖关系注册到IoC容器中;元数据方式即使用Java注解和spring自动扫描的功能配置bean;配置文件的方式主要有两种:xml和properties文件,这里主要讲解使用更广泛的xml文件的方式。
这了以零售超市的例子来讲解bean的创建,SuperMarket表示零售超市,其有DrinkProvider合FruitProvider两个供应商,并且这两个供应商分别有两个实现类Milk和Apple。如下是各个类的结构:
public class SuperMarket {
private DrinkProvider drink;
private FruitProvider fruit;
public SuperMarket() {}
public SuperMarket(DrinkProvider drink, FruitProvider fruit) {
this.drink = drink;
this.fruit = fruit;
}
public void setDrink(DrinkProvider drink) {
this.drink = drink;
}
public void setFruit(FruitProvider fruit) {
this.fruit = fruit;
}
@Override
public String toString() {
return "drink: " + drink + ", fruit: " + fruit;
}
}
public interface DrinkProvider {}
public class Milk implements DrinkProvider {
@Override
public String toString() {
return "this is milk";
}
}
public interface FruitProvider {}
public class Apple implements FruitProvider {
@Override
public String toString() {
return "this is an apple";
}
}
1. 硬编码
根据上面对IoC容器对bean进行管理的几个类的讲解,这里硬编码的方式实际上很好实现,如下是bean创建的代码:
public class BeanApp {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory beanFactory = bindViaCode(beanRegistry);
SuperMarket superMarket = beanFactory.getBean(SuperMarket.class);
System.out.println(superMarket);
}
private static BeanFactory bindViaCode(BeanDefinitionRegistry beanRegistry) {
AbstractBeanDefinition fruit = new RootBeanDefinition(Apple.class);
AbstractBeanDefinition drink = new RootBeanDefinition(Milk.class);
AbstractBeanDefinition superMarket = new RootBeanDefinition(SuperMarket.class);
beanRegistry.registerBeanDefinition("fruit", fruit);
beanRegistry.registerBeanDefinition("drink", drink);
beanRegistry.registerBeanDefinition("superMarket", superMarket);
// 使用构造方法对属性进行设值
ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
argumentValues.addIndexedArgumentValue(0, drink);
argumentValues.addIndexedArgumentValue(1, fruit);
superMarket.setConstructorArgumentValues(argumentValues);
// 使用setter方法对属性进行设值
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("fruit", fruit);
propertyValues.addPropertyValue("drink", drink);
superMarket.setPropertyValues(propertyValues);
return (BeanFactory) beanRegistry;
}
}
如下是输出结果:
drink: this is milk. , fruit: this is an apple.
在示例中,我们首先声明了一个DefaultListableBeanFactory实例,需要注意,DefaultListableBeanFactory既实现了BeanFactory接口,也实现了BeanDefinitionRegistry接口,因而这里将该实例传入bindViaCode()
方法作为bean注册器使用。在bindViaCode()
方法中,我们首先为每个需要创建的bean创建一个BeanDefinition对其进行管理,然后将每个BeanDefinition注册到BeanDefinitionRegistry中。注册完之后,我们使用ConstructorArgumentValues类来指定创建的三个bean之间的相互依赖关系(这里我们也提供了使用setter方法对属性进行设值的代码)。从最后的输出我们可以看出,SuperMarket,Milk和Apple三个类都成功创建了。
2. 元数据
元数据的方式也即注解方式,Spring IoC主要提供了两个注解用于bean的创建和属性的注入,即@Component
和@Autowired
。这里@Component
用在类声明上,用于告知Spring,其需要为当前类创建一个实例,实例名为当前类名首字母小写的形式。@Autowired
则用在属性上,Spring检测到该注解之后就会在IoC容器中查找是否有与该属性相匹配的类或子类实例,有的话就注入到当前属性中,否则就会报错。如下是使用元数据方式创建的bean的示例,示例的类结构中部分代码与前述类结构一致,这里对其进行了省略:
@Component
public class SuperMarket {
@Autowired
private DrinkProvider drink;
@Autowired
private FruitProvider fruit;
// getter和setter,以及toString()等方法
}
@Component
public class Milk implements DrinkProvider {
}
@Component
public class Apple implements FruitProvider {
}
可以看到,这里创建了分别创建了Milk,Apple和SuperMarket的实例,并且将Milk和Apple实例通过@Autowired
注入到SuperMarket实例中了。这里需要注意的是,对于IoC容器而言,单纯使用了上述注解还不能让其自动创建这些bean,还需要通过配置文件用来指明需要对哪些包下的类进行扫描,以检测相关的注解,并注册相应的实例。如下是xml文件的配置方式:
<context:component-scan base-package="com.market"/>
如下是测试驱动类的代码:
public class BeanApp {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/market/application.xml");
SuperMarket superMarket = beanFactory.getBean(SuperMarket.class);
System.out.println(superMarket);
}
}
结果输出如下:
drink: this is milk, fruit: this is an apple
3. 配置文件
xml配置文件是bean实例化使用最为广泛的一种方式,其主要包括两种形式的bean创建:构造方法和属性注入。这里我们会对着两种方式进行详细讲解,并且还会讲解如何注入List,Set,Map等类型属性值的方式,另外,我们也会讲解具有初始化顺序的bean的初始化和具有父子类关系的bean的初始化等方式。
1. 构造方法注入
构造方法注入主要使用constructor-arg
标签,具体使用方式有以下几种类型
- 引用类型
<bean id="drink" class="com.market.Milk"/>
<bean id="fruit" class="com.market.Apple"/>
<bean id="superMarket" class="com.market.SuperMarket">
<constructor-arg ref="drink"/>
<constructor-arg ref="fruit"/>
</bean>
这里首先创建Milk和Apple类的对象,然后在创建SuperMarket对象时,向其构造函数传入了先前创建的Milk和Apple对象。这里ref节点用于表示当前参数是引用的其他的bean。
- 数值类型
public class SequenceFile {
private int dependency1;
private String dependency2;
public SequenceFile(int dependency1) {
this.dependency1 = dependency1;
}
}
<bean id="sequenceFile" class="com.market.SequenceFile">
<constructor-arg value="123"/>
</bean>
这里使用constructor-arg的value节点来为只有一个参数的构造函数指定值。由于SequenceFile只有一个构造函数,因而这里IoC容器知道应该使用该构造函数,并且会进行强制类型转换以使参数值符合参数类型。
- 指定参数类型
public class SequenceFile {
private int dependency1;
private String dependency2;
public SequenceFile(String dependency2) {
this.dependency2 = dependency2;
}
public SequenceFile(int dependency1) {
this.dependency1 = dependency1;
}
}
<bean id="sequenceFile" class="com.market.SequenceFile">
<constructor-arg value="123" type="int"/>
</bean>
这里有两个只有一个参数的构造函数,此时如果配置文件还是按照上一示例中的配置,那么IoC容器是不知道应该使用哪个构造函数的,因而其会默认使用第一个构造函数,也就是dependency2会被注入123。这里如果使用type节点指定了参数类型为int,那么IoC容器就会找只有一个参数,并且参数类型为int类型的构造函数进行bean的实例化,这里也就是dependency1会被初始化为123。
- 指定参数顺序
public class SequenceFile {
private int dependency1;
private String dependency2;
public SequenceFile(int dependency1, String dependency2) {
this.dependency1 = dependency1;
this.dependency2 = dependency2;
}
}
<bean id="sequenceFile" class="com.market.SequenceFile">
<constructor-arg value="abc" index="1"/>
<constructor-arg value="123" index="0"/>
</bean>
这里SequenceFile有一个包含两个参数的构造函数,在声明bean指定参数的时候,如果不指定当前注入的参数对应于构造函数的第几个参数,那么IoC容器就会按照声明的顺序为构造函数的参数注值,这往往是有问题的。示例中我们使用index节点为当前的参数值指定了对应的构造函数的参数位,注意构造函数的参数索引是从0开始的。
2. 属性注入
属性注入也就是使用setter方法注入,注入的参数名与setter方法后缀部分是一致的,而与实际参数名无关。setter方法注入在类的声明上主要有两个地方需要注意:①如果配置文件没有显示使用显示的声明构造函数,那么类中一定要声明默认的构造函数;②类中一定要包含有要注入属性的setter方法。如下是一个setter方法进行数值注入的示例:
public class SequenceFile {
private int dependency;
public void setDependency(int dependency) {
this.dependency = dependency;
}
}
<bean id="sequenceFile" class="com.market.SequenceFile">
<property name="dependency" value="123"/>
</bean>
setter方法也可以进行引用注入,如下所示:
<bean id="fruit" class="com.market.Apple"/>
<bean id="drink" class="com.market.Milk"/>
<bean id="superMarket" class="com.market.SuperMarket">
<property name="drink" ref="drink"/>
<property name="fruit" ref="fruit"/>
</bean>
这里属性注入的使用方式和构造函数中参数的注入方式在配置文件的配置上基本是一致的,这里就不再赘述其具体的使用。
3. List,Set,Map和Properties
对于集合参数的注入,无论是构造函数还是属性注入,其使用方式是一致的,只需要在相应的参数声明节点下使用集合标签即可。这里集合类型与标签对应方式如下:
集合类型 | xml标签 |
---|---|
List | |
Set | |
Map | |
Properties |
如下是一个声明集合参数的示例:
public class MockDemoObject {
private List<Integer> param1;
private String[] param2;
private Set<String> param3;
private Map<Integer, String> param4;
private Properties properties;
private Object param5;
public void setParam1(List<Integer> param1) {
this.param1 = param1;
}
public void setParam2(String[] param2) {
this.param2 = param2;
}
public void setParam3(Set<String> param3) {
this.param3 = param3;
}
public void setParam4(Map<Integer, String> param4) {
this.param4 = param4;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setParam5(Object param5) {
this.param5 = param5;
}
}
<bean id="mdBean" class="com.market.MockDemoObject">
<property name="param1">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="param2">
<list>
<value>string1</value>
<value>string2</value>
<value>string3</value>
</list>
</property>
<property name="param3">
<set>
<value>abc</value>
<value>def</value>
<value>hij</value>
</set>
</property>
<property name="param4">
<map>
<entry key="1" value="string1"/>
<entry key="2" value="string2"/>
<entry key="3" value="string3"/>
</map>
</property>
<property name="properties">
<props>
<prop key="author">zhangxufeng</prop>
<prop key="age">26</prop>
</props>
</property>
<property name="param5">
<null/>
</property>
</bean>
这里需要说明的是,如果集合的元素是引用类型,那么只需要在对应的元素声明处使用ref节点指向另外声明的bean。
4. depends-on依赖
这里depends-on依赖指的是在某些bean进行实例化时,必须保证另外一个bean已经实例化完成,并且这两个bean不一定具有属性依赖关系。depends-on实际使用情况比如进行dao的bean实例化时,需要先将管理数据库连接池的bean进行初始化。如下是一个depends-on依赖的示例:
public class ServiceInstance {}
public class SystemConfigurationSetup {
static {
System.out.println("static initialization! ");
}
}
public class SystemConfigurationSetup2 {
static {
System.out.println("static initialization 2 ! ");
}
}
配置文件:
<bean id="scSetup1" class="com.market.SystemConfigurationSetup"/>
<bean id="scSetup2" class="com.market.SystemConfigurationSetup2"/>
<bean id="serviceInstance" class="com.market.ServiceInstance" depends-on="scSetup1,scSetup2"/>
可以看到,这里在ServiceInstance的bean标签中使用的depends-on,具有多个依赖的使用逗号隔开,IoC容器在进行该bean的初始化之前会保证scSetup1和scSetup2都初始化完毕。
5. autowire自动注入
autowire自动注入指的是在声明一个bean的时候不显示的为其声明构造函数或者是属性名的参数,而是使用autowire节点,让IoC容器通过构造函数和属性名自动识别当前bean所依赖的bean,从而注入进来。autowire有两个值可选byType和byName,分别表示根据构造函数参数和属性的类型进行自动注入,或者是根据属性名进行自动注入。如下所示为autowire注入的一个示例:
public class Foo {
private Bar emphasisAttribute;
public void setEmphasisAttribute(Bar emphasisAttribute) {
this.emphasisAttribute = emphasisAttribute;
}
}
public class Bar {}
<bean id="fooBean" class="com.market.Foo" autowire="byName"/>
<bean id="emphasisAttribute" class="com.market.Bar"/>
示例中,Foo实例依赖于Bar实例,在配置文件中创建Foo实例的处并没有指定其属性值,而是使用了autowire="byName",而Bar实例的名称则和Foo的setter方法后的名称一致。这里也可以使用byType类型的自动注入,此时Bar实例的名称则可以为任意名称:
<bean id="fooBean" class="com.market.Foo" autowire="byType"/>
<bean id="anyName" class="com.market.Bar"/>
6. 继承
- bean的类之间具有继承关系
对于具有继承关系的bean,由于父类的属性,子类也会有,因而如果直接配置,那么两个bean的配置将会有很大一部分趋于相似。这里可以使用parent属性用来将父类已经注入的bean继承给子类bean,子类bean可以只更改其中实现与父类有区别的bean。如下示例中,SpecialSuperMarket继承自SuperMarket类,而SpecialApple则继承自Apple。在实例化SpecialSuperMarket实例的时候其和SuperMarket实例有部分相同的属性,而另一部分是有区别的。如下是SpecialSuperMarket和SpecialApple的声明,其余的类与前面的类声明一致:
public class SpecialSuperMarket extends SuperMarket {}
public class SpecialApple extends Apple {}
<bean id="drink" class="com.market.Milk"/>
<bean id="fruit" class="com.market.Apple"/>
<bean id="superMarket" class="com.market.SuperMarket">
<property name="fruit" ref="fruit"/>
<property name="drink" ref="drink"/>
</bean>
<bean id="specFruit" class="com.market.SpecialApple"/>
<bean id="specSuperMarket" parent="superMarket" class="com.market.SpecialSuperMarket">
<property name="fruit" ref="specFruit"/>
</bean>
从配置文件可以看出来,父类bean只需要按照正常方式声明即可,子类的bean只需要使用parent节点指定其继承的父类bean,并且指明子类与父类有差异的属性bean。
- 提取公共bean并进行继承
对于两个或多个bean,如果其大部分属性bean都是相似的,只有少部分不一致,那么就可以将公共的bean提取出来作为父bean,然后每个bean继承自这个bean,子bean可以重写自己与父bean不一致的属性。这里需要注意的是,提取出来的父bean并不是一个真正的bean,其也没有对应的Java类对应。
如下例所示,假设另外有一个零售商店Outlet与SuperMarket一样,其DrinkProvider也为Milk,但其FruitProvider不一样,是Pear,这里就可以将Outlet示例与SuperMarket实例的声明中的相同部分Milk提取出来,而FruitProvider则各自自己提供(SuperMarket代码与前面一致,这里省略):
public class Outlet {
private DrinkProvider drink;
private FruitProvider fruit;
public void setDrink(DrinkProvider drink) {
this.drink = drink;
}
public void setFruit(FruitProvider fruit) {
this.fruit = fruit;
}
}
public class Pear implements FruitProvider {}
<bean id="drink" class="chapter2.eg1.Milk"/>
<bean id="superSales" abstract="true">
<property name="drink" ref="drink"/>
</bean>
<bean id="marketFruit" class="chapter2.eg1.Apple"/>
<bean id="superMarket" parent="superSales" class="chapter2.eg1.SuperMarket">
<property name="fruit" ref="marketFruit"/>
</bean>
<bean id="outletFruit" parent="superSales" class="chapter2.eg4.Pear"/>
<bean id="outlet" class="chapter2.eg4.Outlet">
<property name="fruit" ref="outletFruit"/>
</bean>
从配置文件中可以看出来,这里将SuperMarket和Outlet中drink属性的注入提取出来,从而形成一个父bean,即superSales,而SuperMarket和Outlet的bean只需要继承父bean,并且注入各自特有的bean即可。这里需要注意,由于父bean是没有对应的class与之对应的,因而其没有class节点,并且父bean需要设置为abstract类型的。
4. 结语
本文首先对IoC容器管理bean的方式进行了讲解,然后分别介绍了如何使用硬编码,元数据和配置文件的方式进行bean的配置,并且这里着重讲解了如何使用配置文件对bean进行配置。
Spring的bean创建详解的更多相关文章
- 面试阿里,字节,美团必看的Spring的Bean管理详解
IOC容器 工厂只负责创建对象,而Spring当然不仅仅是一个对象工厂,其核心是一个对象容器,其具备控制反转的能力,所以也称为IOC容器. 帮助我们存放对象,并且管理对象,包括:创建.销毁.装配,这样 ...
- Spring对Bean装配详解
1.Spring提供了三种装配bean的方式: 2.自动装配bean: 3.通过Java代码装配bean 4.通过XML装配bean 前言:创建对象的协作关系称为装配,也就是DI(依赖注入)的本质.而 ...
- Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等)
上文,我们看了IOC设计要点和设计结构:以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的:容器中存放的是Bean的定义即Be ...
- Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建
上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...
- Spring Boot 自定义日志详解
本节内容基于 Spring Boot 2.0. 你所需具备的基础 什么是 Spring Boot? Spring Boot 核心配置文件详解 Spring Boot 开启的 2 种方式 Spring ...
- spring框架 AOP核心详解
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...
- Spring各个jar包详解
Spring各jar包详解 spring.jar 是包含有完整发布模块的单个jar 包.但是不包括mock.jar,aspects.jar, spring-portlet.jar, and sprin ...
- Spring Batch(4): Job详解
Spring Batch(4): Job详解 2016-03-26 18:46 870人阅读 评论(1) 收藏 举报 分类: Spring(6) 版权声明:本文为博主原创文章,未经博主允许不得转载 ...
- Spring中的BeanPostProcessor详解
Spring中的BeanPostProcessor详解 概述 BeanPostProcessor也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初 ...
随机推荐
- (转)添加Template(模板)并基于模板部署应用
通过Template,可以定义一个或多个需要部署的镜像,定义依赖的对象,定义可供用户输入的配置参数项. 以cakephp-mysql.json为例. # oc create -f https://ra ...
- 404 Note Found 队-Beta2
目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...
- python为什么叫胶水语言?python为什么是系统脚本?
python为什么叫胶水语言?python为什么是系统脚本? 特点是什么? python现在最广为闻名的形容大概有这些: 他是很好的胶水语言.什么是胶水语言?反正当时的我不知道. 他是新一代的系统 ...
- OpenGL ES画板
一.概述 利用自定义顶点和片元着色器渲染,并且设置图片纹理颜色为画笔颜色 二.核心代码 - (void)renderLineFromPoint:(CGPoint)start toPoint:(CGPo ...
- Linux基础入门 第二章 Linux终端和shell
Linux终端 进入编辑IP地址命令:vi /etc/sysconfig/network-scripts/ifcfg-eth0 按键“i”:进行编辑 按键“ESC”:退出编辑 按键“:”:输入wq, ...
- Linux--find命令和 xargs命令组合
find 查找文件的命令,并可以做出相应的处理 命令格式: find filename [选项][-print -exec -ok ...] 选项参数: 1.-name :按照文件名称查找,可以提前c ...
- Scala的控制结构和函数
控制结构和函数 先看以下简单的一个条件表达式的demo object TestConditional { def main(args: Array[String]): Unit = { // scal ...
- 大数据入门第七天——MapReduce详解(一)入门与简单示例
一.概述 1.map-reduce是什么 Hadoop MapReduce is a software framework for easily writing applications which ...
- 13-[CSS]-postion位置:相relative,绝absolute,固fixed,static(默认),z-index
1.postion位置属性 <!DOCTYPE html> <html lang="en"> <head> <meta charset=& ...
- GBDT+LR算法解析及Python实现
1. GBDT + LR 是什么 本质上GBDT+LR是一种具有stacking思想的二分类器模型,所以可以用来解决二分类问题.这个方法出自于Facebook 2014年的论文 Practical L ...