文章目录
前言:上篇文章谈到了bean的简介,这篇文章将带你深入了解Spring中bean并通过实现Bean来更好的认识Spring中Bean的概念,同时掌握Spring中Bean的操作。毕竟IOC、DI是Spring的核心思想,而Bean就是这个思想的载体,Spring就是围绕着Bean的创建注入以及Bean的使用管理进行项目管理的。
ps1:本文是一个学习笔记,是根据我的理解汇总的博客,可能趣味性少一点,如果你想看趣味性强一点,那种娓娓道来的文章。可以看一下《Spring实战第四版》
创建学习Spring的项目
“工欲善其事必先利其器”,因此实践第一步 创建Spring学习项目,创建项目了才能够写Bean的代码。创建过程看我的另一篇文章: Spring学习项目的创建配置
创建完成,开始正式进入Bean的实践阶段
Bean的装配
在Spring学习之提纲(我暂时还没发出来。。。因为整个系列并没有写完)中,我们谈到了Spring,IOC(DI),Bean的概念。知道了Bean只是加入Spring管理的组件的统称,Spring会生成一个工厂类,这个工厂类管理这些Bean,我们只负责创建这些Bean,并在xml文件上写下清单(或者是用在Bean类上添加注解),工厂类会根据上面的清单创建各个Bean的实例,然后调用Bean里指定的方法,组装起来。举个例子吧
从前厨师svllen做菜都是一个铁锅打天下,自己买原材料(原材料就是Bean)自己做菜(做菜就是new的比喻),做菜的步骤都得记清楚,什么时候该放这个什么时候放 放哪个都有讲究。有一天店里引进了新设备。svllen发现他再也不用记这些步骤了,新设备都给你提供了
说白了Bean就是一些组件(各种类 接口),既然要创建注入,我们肯定得指定类路径吧 类名 还有你要注入的属性名 属性类型等信息。Spring就是通过这些信息管理Bean的。
因此Bean的实现分为两步 一步是实例化(这个实例化指得是在Spring容器(applicationContext或者BeanFactory)中实例化,提供给Bean工厂,而不是创建这个Bean类,Bean类是你自己创建的),另一步就是属性注入
在Spring中,提供了3种装配Bean的方案:
- 在xml中显式配置
- 在java代码中进行显式配置(其实就是创建个配置类)
- 隐式的bean发现机制和自动装配
其实我觉得只有两种,一种xml配置,一种注解开发,后面两种都算是注解开发
在这三种方案中,各有各的好处,但是我还是推荐尽可能地使用自动配置的机制,显示配置越少也好,当你必须使用显示配置时,优先使用javaConfig,最后只有当你想要使用便利的xml命名空间,并且在javaConfig没有同样的实现时,才应该使用xml。
由于写起来才知道Bean要讲的东西太多了,因此这篇文章先讲xml显式配置情况下的Bean。
Bean的实例化
简单来说只需要在配置文件中添加标签就可以了
格式:
<bean id="bean" class="cn.svllen.Spring.demo01.Bean1" >
< bean >标签:
- id:唯一标识符,不能包含特殊符号
- name:与id的作用差不多,但是可以包含特殊符号(基本不用,只是用来解决Struts1的遗留问题)
- class: Bean的全路径
- scope:Bean的作用范围(详情请参考上面提到的Bean的作用范围)
- init-method:Bean组件里指定的初始化方法
- destory-mthod:Bean组件里的指定的销毁方法
- autowire:表示bean的自动装配
- no:默认值,不进行装配
- byName:通过属性的名称自动装配(注入)。Spring会在容器中查找名称与bean属性名称一致的bean,并自动注入到bean属性中。当然bean的属性需要有setter方法。例如:bean A有个属性master,master的setter方法就是setMaster,A设置了autowire=“byName”,那么Spring就会在容器中查找名为master的bean通过setMaster方法注入到A中。
- byType:通过属性的类型查找JavaBean依赖的对象并为其注入。比如类Computer有个属性printer,类型为Printer,那么,指定其autowire属性为byType后,Spring IoC容器会查找Class属性为Printer的bean,使用Seter方法为其注入。如果Spring的容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。
- constructor:类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但是与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。
- autodetect :在byType和constructor之间自动的选择注入方式。
- default:采用父级标签(即beans的default-autowire属性)的配置。
- factory-method:
- factory-bean:
在普通的应用程序中,对象是通过反射进行创建的,在Bean工厂类(Beanfactory或者是applicationContext)会根据Bean的关联方式不同也有不同的实例化方法。主要分为三类,构造器实例化,静态工厂实例化,实例工厂实例化。构造器方式的实例化是最常用的,后面两种很少用。所以后面两种静态工厂方法我勉强写了,但是还是觉得不是很好,所以实例工程实例化的方式我就直接不写了,附上一篇博客链接(打哪里都行 不要打脸~~)
构造器实例化的方式
构造器实例化也分为有参和无参
- 无参构造器
创建Bean类:bean1
public class Bean1 {
private String msg;
public Bean1(){
super();
System.out.println("Bean1的无参构造方法执行了。。。。。。");
}
public Bean(String msg){
this.msg = msg;
System.out.println("Bean1的有参构造方法执行了");
}
}
配置文件中配置
```
<!--无参构造方法实现Bean -->
<bean id="bean" class="cn.svllen.Spring.demo01.Bean1" />
```
- 有参构造器
其实有参构造也简单,在< bean>标签中使用< constructor-arg/>子标签就好了。
java类看上面就好了。
xml文件
<bean id="bean" class="cn.svllen.Spring.demo01.Bean1" >
<constructor-arg index="0" value="Hello Java"/>
</bean>
静态工厂实例化的方式
在实际开发中,我们可能会遇到jar包的类,这时候我们不能确定其是否有默认的构造方法,也不能用注解。这时候就可以使用静态工厂的方式(但是其实这种情况也很少见的,所以一般静态工厂实例化的方式并不多见)
比如说我们要生成的bean对象的类为
public class Bean2 {
public void init() {
System.out.println("使用静态工厂方式实例化");
}
}
以前没用spring的话我们利用静态工厂方法生成是这样的,创建一个静态工厂方法创建,当然这个工厂方法并不是很正规(应该有参数,里面还应该有多个判断条件生成不一样的类的,但是这里从简)
public class BeanFactory {
public static Bean2 getInstance() {
return new Bean2();
}
}
使用spring的xml方式就是这样
<bean id="bean2" class="cn.svllen.BeanFactory" factory-method="getInstance"/>
其实就是在bean中使用了factory-method属性,里面的值就是方法名
实例工厂实例化的方式
博客链接:https://blog.csdn.net/u012702547/article/details/56021922#commentBox
Bean的属性注入
前面我们提到了如何把Bean实例化到Spring的applicationContext中,一般来说 我们的Bean或多或少都有属性,这时候我们就需要考虑属性注入的方式了。
spring中Bean有两种注入方式,一种是构造器注入,另一种是set方法注入。
这里要说明的是,我把配置文件分成好几个,然后在总配置文件applicationContext中导入这些子配置文件。这些子配置文件分为
- applicationContext01.xml(以构造器方式实现属性注入)
- applicationContext02.xml (以setter方式实现属性注入)
那如何导入呢,很简单,在总配置文件中使用< import > 标签就好了
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--导入以构造器实现属性注入的配置文件 -->
<import resource="applicationContext01.xml"/>
<!-- 导入以setter方法实现属性注入的配置文件-->
<import resource="applicationContext02.xml"/>
</beans>
华丽的分割线
构造器属性注入
在xml的构造器注入中,有两种方案可以选择
- < constructor-arg>标签(< bean>的字标签)
- c-命名空间(Spring3.0引入)
具体的作用请看下面的讲解
注入基本属性
光说不练假把式,下面拿一个例子举例。现在汽车有很多品牌,因此把汽车抽象出来,定义一个车的接口类Car,车有很多种类型,我这里拿SUV举例,因此创建Car接口的实现类SUV。现在的一般来说都得人控制(排除自动驾驶),因此还得创建一个Driver类,Driver类中肯定得与Car的实现类绑定。接下来看代码
Car接口
public interface Car {
//汽车都可以跑
void run();
}
SUV
public class SUVCar implements Car {
private String name; //车名
private String price; //车是烧油还是电动
public SUVCar(String name, String price) {
this.name = name;
this.price = price;
}
@Override
public void run() {
System.out.println("这辆价格为:" + price +"的" + name + "汽车在行驶" );
}
}
applicationContext01.xml配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用构造器方式的constructor-arg标签注入基本属性(包括int String float等)-->
<bean id="BMWX3" class="cn.svllen.Spring.demo03.SUVCar" >
<constructor-arg name="name" value="宝马X3"/>
<constructor-arg name="price" value="38万" />
</bean>
<!--使用构造器方式的c-命名空间注入基本属性(包括int String float等) -->
<bean id="BMWX3" class="cn.svllen.Spring.demo03.SUVCar" c:name="宝马x3" c:price="38万"/>
<!-- c-命名空间还有另一种用法,使用属性的下标作为标记 -->
<bean id="BMWX3" class="cn.svllen.Spring.demo03.SUVCar" c:_0="宝马x3" c:_1="38万"/>
</beans>
我们通过< constructor-arg>和c-命名空间完成对SUVCar中name和price两个属性的注入,对于< constructor-arg>标签来说,name属性 为要注入的属性名,value属性 为要注入的属性的值,没啥好说的
c-命名空间的的用法是 c:属性名=“属性值” 或者c:_属性的index(下标)=“属性值”,推荐使用前者,后者不能看出属性的名称。与< constructor-arg>相比,c-命名空间更加简洁,这里有一个点需要注意,要使用c-命名空间还得在xml顶部声明其模式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
其中name和price都是基本类型,如果我们要注入的是复杂类型呢,比如其他的Bean(对象),集合,这又该怎么办呢
注入复杂属性之Bean(对象)
先说如何注入Bean(对象)类型的属性吧,比如在Driver类的构造器方法中我们需要把suvCar这个属性给实例化了,通过属性注入的形式。
Driver类
public class Driver {
private SUVCar suvCar;
public Driver(SUVCar suvCar){
this.suvCar = suvCar;
}
...
}
applicationContext01.xml配置文件中
<!-- 使用构造器方式注入属性,只有基础属性的时候-->
<bean id="BMWX3" class="cn.svllen.Spring.demo03.SUVCar" >
<constructor-arg name="name" value="宝马X3"/>
<constructor-arg name="price" value="38万" />
</bean> -->
<!-- 使用构造器方式注入属性,属性中还包含其他类时-->
<bean id="driver" class="cn.svllen.Spring.demo03.Driver" >
<!--使用<constructor-arg>标签的 -ref模式 -->
<constructor-arg name="suvCar" ref="BMWX3"/>
</bean>
<!-- 使用构造器方式的c-命名空间注入复杂属性之Bean引用时-->
<bean id="driverC-" class="cn.svllen.Spring.demo03.Driver" c:suvCar-ref="BMWX3"/>
不管是< constructor-arg>标签还是c-命名空间,都是使用了ref这个关键字,值是要注入bean的id。
对于< constructor-arg>标签来说,它引用了ID为BMWX3的Bean(通过ref属性),并将其注入到属性名为suvCar中(即name属性)。
对于c-命名空间来说,构成如下图所示。
注入复杂属性之集合
在以构造器方式注入集合类型属性中,我们只能选择< constructor-arg>标签,目前c-命名空间并没有支持。
集合有list set和map,为了讲解如何注入,我们创建了一个没有实际意义的演示CollectionAssembly
CollectionAssembly类
public class CollectionAssembly {
private List<String> list;
private Map<String, String> map;
private Properties properties;
private Set<String> set;
private String[] array;
public CollectionAssembly(List<String> list, Map<String, String> map, Properties properties, Set<String> set, String[] array) {
this.list = list;
this.map = map;
this.properties = properties;
this.set = set;
this.array = array;
}
}
applicationContext01.xml配置文件
<bean id="collection" class="cn.svllen.Spring.demo03.CollectionAssembly">
<!-- 装配String[]类型的array -->
<constructor-arg name="array">
<array>
<value>数组值1</value>
<value>数组值2</value>
<value>数组值3</value>
</array>
</constructor-arg>
<!-- 装配List类型的list -->
<constructor-arg name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</constructor-arg>
<!-- 装配Set类型的set -->
<constructor-arg name="set">
<set>
<value>宝马</value>
<value>奔驰</value>
<value>奥迪</value>
</set>
</constructor-arg>
<!-- 装配Map类型的map -->
<constructor-arg name="map">
<map>
<entry key="BMW" value="宝马"/>
<entry key="BC" value="奔驰"/>
<entry key="AD" value="奥迪"/>
</map>
</constructor-arg>
<!-- 装配Properties类型的properties -->
<constructor-arg name="properties">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</constructor-arg>
</bean>
其实也挺简单,就是在< constructor-arg>标签中添加各种子标签
- < list >标签:设置list类型的集合,< value>标签里放置的是注入的值,除了 < value>之外,其实也可以添加 < ref >标签引入其他Bean对象
- < set >标签:设置set集合,上同
- < map >标签:设置map集合
- < prop >标签:设置Properties属性类,不知道Properties的可以参考这篇文章
setter方法属性注入
当我们初始化一个类时,除了构造器设置属性,一般也会用到setter方法进行设置。因此在spring中也支持setter方法。
对于xml方式的,spring提供了两种方案,也是一个标签一个命名空间
- < peoperty >标签(< bean >的子标签)
- p-命名空间(Spring 2.0引入)
< property >标签与之前的< constructor-arg>很相似,属性都一样,都有name,ref,value。name为注入属性的属性名,ref是引用的bean对象id,value是属性的值
p-命名空间的基本用法 p:属性名="属性值" 如果是引用Bean对象 p:属性名-ref="属性值",与c-命名空间相比,少了以参数下标为属性名的用法。同理,使用p-命名空间也得在xml顶部进行声明。
注入基本属性
同样以之前构造器方法属性注入的这几个类(Car SUVCar Driver)来演示,只不过得把其中的构造器方法换成setter方法。(代码我就不贴了,这自己改就好了),实在觉得需要的话我上传了个txt的文档。
applicationContext02.xml配置文件中
<!--使用setter方式的 < property>标签注入基本属性(int String float)等 -->
<bean id="ADa6" class="cn.svllen.Spring.demo03.SUVCar">
<property name="name" value="奥迪a6"/>
<property name="price" value="38万"/>
</bean>
<!-- 使用setter方式的p-命名空间注入基本属性(int float String)-->
<bean id="ADa6p" class="cn.svllen.Spring.demo03.SUVCar" p:name="奥迪A6" p:price="38万" />
跟之前构造器方式注入基本属性很相似对吧,因为这些本来就不难。
注入复杂属性之Bean(对象)引用
对于复杂类型Bean对象引用,setter方式又以怎样的方式实现呢。
applicationContext02.xml配置文件中
<!-- 使用setter方式的<property> 标签注入复杂属性之Bean(对象)引用-->
<bean id="driver" class="cn.svllen.Spring.demo03.Driver">
<property name="suvCar" ref="ADa6"/>
</bean>
<!--使用setter方式的p- 命名空间注入复杂属性之Bean(对象)引用 -->
<bean id="driverp" class="cn.svllen.Spring.demo03.Driver" p:suvCar-ref="ADa6"/>
< property>标签为属性的setter方法所提供的功能与< constructor-arg>标签为构造器所提供的功能是一样的。在上面的例子中,< peoperty>通过ref属性引用了ID为ADa6的bean,并将其注入到name=suvCar的属性中
除了< property>标签,我们也可以选择p-命名空间来注入Bean(对象)引用类型的属性。格式直接上图
注入复杂属性之集合
由于< property >标签与 < constructor-arg>标签存在高度的相似性,因此在注入集合类型属性时,我们应该可以想到 < property >标签也可以实现这个功能。
applicationContext02.xml配置文件中
<!--使用setter方式的p- 命名空间注入复杂属性之集合(list set map) -->
<bean id="collection" class="cn.svllen.Spring.demo03.CollectionAssembly">
<!-- 装配String[]类型的array -->
<property name="array">
<array>
<value>数组值1</value>
<value>数组值2</value>
<value>数组值3</value>
</array>
</property>
<!-- 装配List类型的list -->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<!-- 装配Set类型的set -->
<property name="set">
<set>
<value>宝马</value>
<value>奔驰</value>
<value>奥迪</value>
</set>
</property>
<!-- 装配Map类型的map -->
<property name="map">
<map>
<entry key="BMW" value="宝马"/>
<entry key="BC" value="奔驰"/>
<entry key="AD" value="奥迪"/>
</map>
</property>
<!-- 装配Properties类型的properties -->
<property name="properties">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
</bean>
对于p-命名空间来说,严格意义上我们是不能实现注入集合属性的,但是通过util-命名空间与p-命名空间的混合使用实现这一功能
使用util-命名空间也得在xml顶部声明:xmlns:util=“http://www.springframework.org/schema/util”
applicationContext02.xml配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用setter方式的p-命名空间与util-命名空间混合的方式注入集合类型的属性(int float String -->
<bean id="Collectionp-" class="cn.svllen.Spring.demo03.CollectionAssembly"
p:list-ref="list" p:set-ref="set" p:map-ref="map" p:properties-ref="properties"/>
<!-- 装配List类型的list -->
<util:list id="list">
<value>张三</value>
<value>李四</value>
<value>王五</value>
</util:list>
<!-- 装配Set类型的set -->
<util:set id="set">
<value>宝马</value>
<value>奔驰</value>
<value>奥迪</value>
</util:set>
<!-- 装配Map类型的map -->
<util:map id="map">
<entry key="BMW" value="宝马"/>
<entry key="BC" value="奔驰"/>
<entry key="AD" value="奥迪"/>
</util:map>
<!-- 装配Properties类型的properties -->
<util:properties id="properties">
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</util:properties>
</beans>
总结:当你看到这里时,是不是感觉xml配置怎么那么麻烦,虽然不再需要我自己在java代码中手动捆绑类与类之间的关系,但是你在xml都写了一遍啊。其实在实际开发中,xml是作为最后的实现方式的,现在大部分项目只有配置文件是需要xml实现,其他的bean注入都交给注解了(不过其实也可以使用注解来设置配置文件)。下一篇文章介绍的注解方式你就会看到更简单的方法。