欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring学习-装配Bean

程序员文章站 2022-07-12 12:57:16
...

转自:http://blog.csdn.net/y492235584/article/details/51672285


前言

今天开始系统学习Spring框架,使用的书籍是Spring in action第三版中文版。之前已有一定的Spring基础,有些部分可能不会记录的非常详细。

依赖注入(前言)

依赖注入将对象的使用和对象的管理(创建、赋值等)进行了解耦。 
Spring通过应用上下文(Application Context)装载Bean的定义并把它们组装起来,Spring应用上下文全权负责对象的创建和组装,Spring自带了几种应用上下文的实现,它们之间的主要区别仅仅是如何加载它们的配置(xml配置、注解配置等)

//加载myxml.xml配置文件
ApplicationContext context = new 
ClassPathXmlApplicationContext("myxml.xml");
//获得Bean
User user = (User)context.geatBean("User");
  • 1
  • 2
  • 3
  • 4
  • 5

*

Spring容器:

容器是Spring框架的核心,Spring容器使用依赖注入管理构成应用的组件,它会创建相互协作的组件之间的关系。 
Spring自带了两种不同的容器: 
1、BeanFactory,由org.Springframework.beans.factory.BeanFactory接口定义,他是最简单的容器,提供基本的DI支持 
2、应用上下文,由org.springframework.context.ApplicationContext接口定义,基于BeanFactory之上构建,并提供面向应用的服务,例如从属性文件解析文本信息的能力,以及发布应用事件给感兴趣的事件监听者的能力。 
*:大部分情况下我们使用第二种容器。 
Spring自带了几种类型的应用上下文。下面3钟是比较常用的: 
1、ClassPathXmlApplicationContext:加载位于应用系统classpath下的一个或多个XML文件。 
2、FileSystemXmlApplicationContext:读取文件系统下的XML配置文件并加载上下文定义。 
3、XmlWebApplicationContext:读取Wweb应用下的XML配置文件并装载上下文。

Bean的生命周期

1、Spring对Bean进行实例化。 
2、Spring将值和Bean的引用注入Bean对应的属性中。 
3、如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()接口方法 
4、如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()接口方法,将BeanFactory容器实例传入。 
5、如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext接口方法,将应用上下文的引用传入。 
6、如果Bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()接口方法。 
7、如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()接口方法。类似地,如果Bean使用init-method声明了初始化方法,该方法也会被调用。 
8、如果Bean实现了BeanPostProcessor接口,Spring将调用它们的post-PoressAfterInitialization()方法。 
9、此时此刻,Bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到应用上下文被销毁。 
10、如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法。同样,如果Bean使用destory-method声明了销毁方法,该方法也会被调用。

装配Bean

第一步: 
声明Bean,即创建对应的类

package xie;
public class Juggler implements Performer {
    private int beanBags = 3;
    public Juggler() {
    }
    public Juggler(int beanBags) {
        this.beanBags = beanBags;
    }
    public void perform() throws PerformanceException {
    Sysotem.out.println("JUGGLING" + beanBags + "BEANBAGS");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

第二步: 
创建Spring配置,有两种方式 
xml方式: 
在XML文件中声明Bean时,所有的Bean配置在Beans根元素下。 
一个典型的SpringXML配置文件:

< beans xmlns="http://www.springframework.org/schema/beans"
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-3.0.xsd>
    < !-- Bean declarations go here -->
< /beans>
  • 1
  • 2
  • 3
  • 4
  • 5

在< beans>元素内,可以配置所有的Spring配置信息,包括< bean>声明。beans命名空间不是唯一的Spring命名空间,Spring的核心框架自带了10个命名空间的配置:Spring学习-装配Bean 
在Spring中配置声明的类: 
< bean id=”duke” class=”xie.Juggler” /> 
这样就创建了一个由Spring容器管理的Bean,id属性定义了Bean的名字,也作为该Bean在Spring容器中的引用。 
当Spring容器加载该Bean时,Spring将调用默认的构造器来实例化duke Bean,实际上,duke会使用如下代码来创建:

new xie.Juggler();
  • 1

可以使用如下代码加载Spring上下文

ApplicationContext ctx = new ClassPathXmlApplication("***.xml");
Performer performer = (Performer) ctx.getBean("duke");
performer.perform();
  • 1
  • 2
  • 3

如果不想使用默认的构造方法,则可以这样配置:

< bean id="duke" class="xie.Juggler">
    < constructor-arg value="15" />
< /bean>
  • 1
  • 2
  • 3

通过使用< constructor-arg>标签,可以告诉Spring额外的构造信息,此时对象的创建将调用带一个参数的那个构造方法。 
如果在构造时想传入一个对象作为参数:

< bean id="obj1" class="...">
< bean id="duke" class="xie.Juggler">
    < constructor-arg ref="obj1" />
< /bean>
  • 1
  • 2
  • 3
  • 4

这样,就可以吧名字为obj1的Bean作为duke构造器的参数了。(此处忽略了实现的可行性,只关注配置方法) 
*注:如果属性是简单类型,则可以使用value。如果是对象类型,则使用ref 
使用factory-method装配Bean 
可以使用factory-method属性指定创建对象的方法

< bean id="theSingle" 
    class="..." 
    factory-method="getInstance" />
  • 1
  • 2
  • 3

Bean的作用域 ##

所有Spring Bean默认都是单例,要改变Bean的作用范围,只需要配置Bean的scope属性,scope属性有以下取值 
Spring学习-装配Bean 
*注:Spring中有关单例的概念限于Spring上下文的范围内,不像真正的单例,在每个类加载器中保证只有一个实例。Spring的单例Bean只能保证在每个应用上下文中只有一个Bean实例。没人可以阻止你使用传统方式实例化用一个Bean,或者你甚至可以定义几个< bean>声明来实例化一个bean。

初始化和销毁Bean

为Bean定义初始化和销毁操作,需要使用init-method和destory-method参数来配置< bean>元素。init-method属性指定在初始化Bean时要调用的方法,destory-method属性指定了Bean从容器中移除之前要调用的方法。

< bean id="auditorium"
    class="..."
    init-method="turnOnLights"
    destory-method="turnOffLights" />
  • 1
  • 2
  • 3
  • 4

auditorium Bean实例化后会立即调用turnOnLights()方法,在该Bean从容器中移除和销毁前,会调用turnOffLights()方法。 
Spring学习-装配Bean 
在< beans>标签中可以使用default-init-method和default-destory-method方法为所有的bean设置默认的初始化和销毁方法。

注入Bean属性

在Spring中可以使用< property>元素配置Bean的属性。< property>和< constructor-arg>在很多方面都类似,只不过一个是通过构造器参数注入值,一个是通过setter方法注入值。

< bean id="kenny" class="...">
    < property name="song" value="my love"/>
< /bean>
  • 1
  • 2
  • 3

一旦Kenny被实例化,Spring就会调用< property>元素所指定属性的setter方法为该属性注入值。通过上述配置,Kenny实例的song属性将被设置为“my love”。 
*注:value属性可以指定String/int/float/double/boolean/long/char等简单类型的值。为它设置String类型和int等类型方式是一样的:使用“值”。指定引用对象使用ref属性 
注入内部Bean 
内部Bean:定义在其他Bean内部的Bean

< bean id="kenny" class="...">
    < property name="instrument" >
        < bean class="..." />
    < /property>
< /bean>
  • 1
  • 2
  • 3
  • 4
  • 5

内部Bean通过直接声明一个< bean>元素作为< property>元素的子节点而定义的。内部Bean不仅限于setter注入,我们还可以把内部Bean装配到构造方法的参数中。 
*注:内部Bean没有id属性,因为我们永远不会通过名字来引用内部Bean,这也突出了使用内部Bean的最大缺点:它不能被复用。内部Bean仅适用于一次注入,而且不能被其他Bean所引用。

Spring命名空间p装配属性

Spring的命名空间p提供了另一种Bean属性的装配方式。 
通过加入一段声明可以使用命名空间p

< beans xmlns="http://www.springframework.org/schema/beans"
< !--加入的声明-->
xmlns:p="http://www.springframework.org/schema/p"
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-3.0.xsd>
    < !-- Bean declarations go here -->
< /beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

现在,可以使用P:作为< bean>元素所有属性的前缀来装配Bean的属性。

< bean id="kenny" class="..."
    < !--使用my love装配song属性(通过setter方法) -->
    p:song = "my love"
    < !--使用id为saxophone的Bean装配instrument属性 -->
    p:instrument-ref = "saxophone" />
  • 1
  • 2
  • 3
  • 4
  • 5

装配集合

Spring提供了4中类型的集合配置元素: 
Spring学习-装配Bean 
使用< list>

< bean id="hank" class="...">
    < property name="instruments">
        < list>
            < ref bean="guiter" />
            < ref bean="cymbal" />
            < ref bean="harmonica" />
        < /list>
    < /property>
< /bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

无论是< list>还是< set>都可以用来装配类型为java.util.collection的任意实现或者数组的属性。也可以使用< set>装配List集合,虽然这样有点怪,但确实似乎可以的,但是必须确保list集合中的每个元素都是唯一的(< set>重复的属性不会注入)。 
装配map

< bean id="hank" class="...">
    < property name="instruments">
        < map>
            < entry key="GUITER" value-ref=“guiter”>
            ...
        < /map>
    < /property>
< /bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上述中map key为String类型,value为对象类型Spring学习-装配Bean 
装配Properties 
当要注入的类型为Properties时

< bean id="hank" class="...">
    < property name="instruments">
        < !--定义一个Properties集合-->
        < props>
            < !--定义成员-->
            < prop key="GUITAR">STRUM < /prop>
            < prop key="CYMBAL">CRASH < /prop>
            < prop key="HARMONICA">HUM < /prop>
        < /props>
    < /property>
< /bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

< property>的name属性声明了字段的名称,Properties的每个成员都有< prop>元素定义,其中“GUITAR”为Properties成员的键,它的值为“STRUM” 
装配空值 
使用< null/>元素即可。

< property name="xxx">
    < null/>
< /property>
  • 1
  • 2
  • 3

使用Spring表达式

使用#{ }界定符来书写Spring表达式 
简单的SpEL(Spring表达式)

< property name="count" value="#{5}"/>
  • 1

这个表达式将count字段的值设为5,SpEL表达式还可以和非SpEL表达式混用:

< property name="count" value="the value is #{5}"/>
  • 1

String类型的字面值可以使用单引号或者双引号作为字符串的界定符。

< property name="userName" value="#{"小明"}"/>
  • 1

布尔类型

< property name="enabled" value="#{false}"/>
  • 1

复杂的SqEL 
引用Bean:可以通过Bean的ID直接引用Bean

< property name="instrument" value="#{saxophone}"/>
  • 1

引用Bean的属性

< !--使用Bean ID为kenny的song属性值作为值-->
< property name="song" value="#{kenny.song}"/>
  • 1
  • 2

引用Bean的方法 
假设有一个songSelector Bean,该Bean有一个selectSong()方法

< property name="song" value="#{songSelector.selectSong()}"/>
  • 1

转换大小写

< !--将返回的字母转换为大写-->
< property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>
  • 1
  • 2

上述代码中如果selectSong()返回的是null,则会抛出一个NULLPointerException异常。使用null-safe存取器可以避免异常

< !--使用?判断-->
< property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>
  • 1
  • 2

减少XML的配置

自动装配

Spring提供了4中自动装配策略。 
1、byName:把Bean的属性具有相同名字(或ID)的其他Bean自动装配到Bean的对应属性中。

通过设置Bean的id与需要被自动装配的Bean对应的类的字段名称相同完成自动装配

< !--需要设置autowire属性-->
< bean id="kenny" class="MyClass" autowire="byName"/>
< !--如果Spring中还有一个Beanidname,并且MyClass类中有name属性,那么Bean kenny会自动为其属性name注入Bean name-->
  • 1
  • 2
  • 3

2、byType:把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。

byType使用与byName类似,只不过此处检测的是类型是否相同。如果匹配到多个Bean类型相同,Spring会抛出异常。 
经测试,byType会匹配子类型,同样如果匹配到多个子类型也会抛出异常。

3、constructor:把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。

< bean id="duke" class="..." autowire="constructor" />
  • 1

这种方式和byType有一样的局限性,如果匹配到多个Bean类型或多个构造器,Spring都会抛出异常。 
4、autodetect:首先尝试使用constructor进行自动装配,如果失败,再尝试byType进行自动装配。

< bean id="duke" class="..." autowire="autodetect" />
  • 1

设置首选Bean 
设置Bean的primary属性为true可以将Bean设置为首选Bean,但是Spring默认所有Bean的primary属性为true

< !--这个Bean是首选Bean-->
< bean id="saxophone" class="..." primary="false" />
  • 1
  • 2

排除某个Bean 
设置Bean的autowire-candidate属性为false可以排除这个Bean(自动装配时将不会考虑使用这个Bean)

< !--这个Bean将不会被自动装配检测-->
< bean id="saxophone" class="..." autowire-candidate="false" />
  • 1
  • 2

默认自动装配 
如果需要为Spring应用上下文的所有Bean均配置相同的autowire属性,只需要在根元素< beans>上添加default-autowire属性

<--设置beans下所有Bean默认使用byType装配-->
< beans xmlns="..."
            ......
            default-autowire="byType">
            ......
< /beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

默认情况下default-autowire属性的值是none即不使用自动装配。 
*注:使用bean标签中的autowire属性可以覆盖默认设置。

混合使用自动装配和显式装配 
使用自动装配的同时也可以自己显式的装配字段。被显示装配的字段将不会被自动装配

< bean id="kenny" class="..." autowire="byType">
    < property name="song" value="Jingle Bells">
< /bean>
  • 1
  • 2
  • 3

Bean kenny的song字段通过setName方法设置成”Jingle Bells”,而这个类的其他字段将被自动装配(byType)

使用注解装配

注解详细介绍 
要使用注解装配,首先要在Spring中启用它,在< beans>标签下添加如下代码

< context:annotation-config />
  • 1

使用注解可以实现更小颗粒度的自动装配(可以只装配某一个字段) 
Spring3支持3种不同的自动装配注解 
- Spring自带的@Autowired注解 
- JSR-330的@Inject注解 
- JSR-250 的@Resource注解

@Autowired 
@Autowired可以标注任何方法,字段和构造器。

@Autowired //即使user是私有字段,也不需要setter方法即可注入,因为Spring直接通过反射注入属性值。
private User user 
---------
@Autowired //自动注入user
public void show(User user);
-----------
@Autowired //构造器也一样可以注入
public Car(User user);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当使用@Autowired注解时,Spring会搜索对应的Bean并将Bean注入。但是,如果没有Bean被匹配或多个Bean被匹配,使用这个注解就会可能产生异常。

可选的自动装配 
通过设置@Autowired的required属性为false,当没有找到合适的Bean时,就不会产生异常,而是会赋值为NULL。

@Autowired(required=false) //如果没有找到合适的Bean,instrument的值设为NULL
private Instrument instrument
  • 1
  • 2

*注:required属性可以用于@Autowired注解使用的任何地方,但是当使用在构造器时,只有一个构造器可以将@Autowired的required属性设置为true。其他的构造器必须设置为false。此外,当使用@autowired标注多个构造器时,Spring会从所有满足装配条件的构造器中选择满足条件最多的那个构造器。 
@Value注解 
@Value可以注入简单类型的值:String、int、boolean等

@Value("xiaoming") /为name属性注入值xiaoming
private String name;
  • 1
  • 2

在@Value注解中也可以使用SpEL表达式

@Value(*#{systenProperties.myFavoriteSong}*)
private String song;
  • 1
  • 2

自动检测Bean

使用< context:component-scan>可以指定对应包自动检测Bean,如果要扫描多个包,使用逗号分隔开(使用这个标签指定的范围,除了完成了< context:annotation-config>的工作,还能自动检测Bean和定义Bean)

< beans .....>
    < context:component-scan base-package="com.springinaction.springidol">
    < /context:component-scan>
< /beans>
  • 1
  • 2
  • 3
  • 4

base-package属性标识了< context:component-scan>标签所扫描的包。下面介绍这个标签检测Bean的方法: 
使用注解标识Bean 
这个标签指定包下的类可以使用集中注解表明这个类为Spring Bean 
- @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。 
- @Service 通常作用在业务层,但是目前该功能与 @Component 相同。 
- @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。 
- @Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。

当使用@Component注解一个类时(在扫描范围内),这个类会被标识为一个Bean,Bean的ID默认为类的无限定名,例如Guiter类标识后Bean的id默认为guiter, 
这个注解也可以使用一个参数设置Bean的id,例如@Component(“myGuiter”)设置Bean的Id为myGuiter 
过滤扫描组件、 
在< context:component-scan>标签下可以使用< context:include-filter>或者< context:exclude-filter>。通过这两个标签的type和expression属性可以定义过滤策略。 
type属性指定过滤器类型: 
Spring学习-装配Bean

< context:component-scan 
    base-package="com.springinaction.springidol">
    < !--将指定包下所有派生于Instrument类的类自动标识为Bean-->
    < context:include-filter type="assignable"
        expression="com.springinaction.springidol.Instrument" />
        < !--除了被@SkipIt注解标识的类-->
    < context:exclude-filter type="annotation"
        expression="com.springinaction.springidol.SkipIt">
< /context:component-scan>