资讯专栏INFORMATION COLUMN

Spring核心 装配Bean

wanglu1209 / 2645人阅读

摘要:它的构造器上添加了注解,这表明当创建的时候,会通过这个构造器来进行实例化并且会传入一个可设置给类型的通过自动装配,将一个注入到之中注解不仅能够用在构造器上,还能用在属性的方法上。

Spring配置的可选方案

Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

在XML中进行显式配置

在Java中进行显式配置

隐式的bean发现机制和自动装配

尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来
维护的,而当你需要为这些代码配置bean的时候),推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML

自动化装配bean

Spring从两个角度来实现自动化装配:

组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean

自动装配(autowiring):Spring自动满足bean之间的依赖

组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将显式配置降低到最少

创建可被发现的bean CompactDisc接口在Java中定义了CD的概念
package soundsystem;

public interface CompactDisc
{
    void play();
}

CompactDisc的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度

带有@Component注解的CompactDisc实现类SgtPeppers
package soundsystem;
import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc
{
    private String title = "Sgt. Pepper"s Lonely Hearts Club Band";
    private String artist = "The Beatles";
    
    public void play()
    {
        System.out.println("Playing" + title + "by" + artist);
    }
}

SgtPeppers类上使用了@Component注解。这个简单的注解表明SgtPeppers类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppersbean,因为这个类使用了@Component注解,所以Spring会为你把事情处理妥当

不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean

@ComponentScan注解启用了组件扫描

以下程序的配置类展现了完成这项任务的最简洁配:

package soundsystem;
import org.springframework.context.annotation.componentScan;
import org.springframework.context.annotation.Con;
    
@Component
@ComponentScan
public class CDPlayerConfig{}

类CDPlayerConfig通过Java代码定义了Spring的装配规则。观察可知,CDPlayerConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean

通过XML启用组件扫描

如更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的元素。以下程序展示了启用组件扫描的最简洁XML配置:



        
    
    

测试组件扫描能够发现CompactDisc
package soundsystem;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest
{
    @Autowired
    private CompactDisc cd;
    
    @test_139772[test] 
    public void cdShouldNotBeNull()
    {
        assertNotNull(cd);
    }
}

CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan,因此最终的应用上下文中应该包含CompactDiscbean

在测试代码中有一个CompactDisc类型的属性,并且这个属性带有@Autowired注解,以便于将CompactDiscbean
注入到测试代码之中。最后,会有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中

为组件扫描的bean命名

Spring应用上下文中所有的bean都会给定一个ID。前面的例子中,尽管没有明确地为SgtPeppersbean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写

若想为这个bean设置不同的ID,则需将期望的ID作为值传递给@Component注解:

@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc
{
    ...
}

另外一种为bean命名的方式,使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID:

package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc
{
    ...
}
设置组件扫描的基础包

为了指定不同的基础包,所需要在@ComponentScan的value属性中指明包的名称:

@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig{}

若需更加清晰地表明你所设置的是基础包,通过basePackages属性进行配置:

@Configuration
@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig{}

如果需要可以设置多个基础包,只需要将basePackages属性设置为要扫描包的一个数组即可:

@Configuration
@ComponentScan(basePackages = {"soundsystem", “videos”})
public class CDPlayerConfig{}

上面例子中,所设置的基础包是以String类型表示的。这是可行的,但这种方法是类型不安全(not type-safe)的。如进行代码重构,那么所指定的基础包可能会出现错误

除了将包设置为简单的String类型之外,@ComponentScan提供的另外一种方法,将其指定为包中所包含的类或接口:

@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig{}
通过为bean添加注解实现自动装配

自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解

下述程序CDPlayer类。它的构造器上添加了@Autowired注解,这表明当Spring创建CDPlayerbean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean

通过自动装配,将一个CompactDisc注入到CDPlayer之中:

package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implement MediaPlayer
{
    private CompactDisc cd;
    
    @Autowired
    public CDPlayer(CompactDisc cd)
    {
        this.cd = cd;
    }
    
    public void play
    {
        cd.play();
    }
}

@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如说,如果CDPlayer有一个setCompactDisc()方法,那么可以采用如下的注解形式进行自动装配:

@Autowired
public void setCompactDisc(CompactDisc cd)

@Autowired注解可以用在类的任何方法上。假设CDPlayer类有一个insertDisc()方法,那么@Autowired能够像在setCompactDisc()上那样,发挥完全相同的作用:

@Autowired
public void insertDisc(CompactDisc cd)
{
    this.cd = cd;
}

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来

如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:

@Autowired
public CDPlayer(CompactDisc cd)
{
    this.cd = cd;
}

将required属性设置为false时,Spring会尝试执行自动装配,若没有匹配的bean,Spring将会让这个bean处于未装配的状态。但把required属性设置为false时,需要谨慎对待。如果代码中没有进行null检查,处于未装配状态的属性可能出现NullPointerException

@Autowired是Spring特有的注解。如果不愿代码中到处使用
Spring的特定注解来完成自动装配任务,可考虑替换为@Inject:

package soundsystem;
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class CDPlayer
{
    ...
    @Inject
    public CDPlayer(CompactDisc cd)
    {
        this.cd = cd;
    }
    ...
}

@Inject注解来源于Java依赖注入规范,该规范同时还定义了@Named注解。在自动装配中,Spring同时支持@Inject和
@Autowired。尽管@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景下,是可以互相替换的

验证自动装配

除了注入CompactDisc,还将CDPlayerbean注入到测试代码的player成员变量之中(它是更为通用的MediaPlayer类型)。在play()测试方法中,我们可以调用CDPlayer的play()方法,并断言它的行为与预期一致

在测试代码中使用System.out.println()。因此,该样例中使用了StandardOutputStreamLog,是来源于System Rules库(http://stefanbirkner.github.i...)的一个JUnit规则,该规则能够基于控制台的输出编写断言。SgtPeppers.play()方法的输出将被发送到控制台上

通过Java代码装配bean

如想将第三方库中的组件装配到你的应用中,在这种情况下无法通过在它的类上添加@Component和@Autowired注解的方法实现自动化装配

JavaConfig与其他的Java代码存在区别,在概念上,它与应用程序中的业务逻辑和领域代码不同的。尽管都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到多带带的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了

创建配置类

创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节

package soundsystem;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig
{
    ...
}

到此为止,都是依赖组件扫描来发现Spring应该创建的bean。尽管可以同时使用组件扫描和显式配置,但在本节更加关注于显式配置,因此将CDPlayerConfig的@ComponentScan注解移除掉了。移除了@ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。如现在运行CDPlayerTest,测试失败,并出现BeanCreation-Exception异常。测试期望被注入CDPlayer和CompactDisc,但这些bean根本就没有创建,因为组件扫描不会发现它们。下一节,将展现如何使用JavaConfig装配CDPlayer和CompactDisc

声明简单的bean

要在JavaConfig中声明bean,需编写一个创建所需类型实例的方法,然后给这个方法添加@Bean注解。如下代码声明了CompactDisc bean:

@Bean
public CompactDisc sgtPeppers()
{
    return new SgtPeppers();
}

@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑

默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字:

@Bean(name= "lonelyHeartsClubBand")
public CompactDisc sgtPeppers()
{
    return new SgtPeppers();
}
借助JavaConfig实现注入

前面所声明的CompactDisc bean非常简单,自身没有其他依赖。现在,需要声明依赖于CompactDisc的CDPlayer bean。在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。如下就是一种声明CDPlayer的可行方案:

@Bean
public CDPlayer cdPlayer()
{
    return new CDPlayer(sgtPeppers());
}

这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所创建的bean ID为cdPlayer,与方法的名字相同。cdPlayer()的方法体与sgtPeppers()稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例。看起来,CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用

@Bean
public CDPlayer cdPlayer()
{
    return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer()
{
    return new CDPlayer(sgtPeppers());
}

默认情况下,Spring中的bean都是单例的,并没必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()时所创建的CompactDiscbean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc)
{
    return new CDPlayer(sgtPeppers());
}

在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法

通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置

可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean

通过XML装配bean 创建XML配置规范

在使用XML为Spring装配bean之前,你需要创建一个新的配置规范。在使用JavaConfig的时候,这意味着要创建一个带有@Configuration注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以元素为根



        
    
    

如上基本的XML配置已比同等功能的JavaConfig类复杂。在JavaConfig中只需要@Configuration,但在XML中,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素

借助Spring Tool Suite创建XML配置文件创建和管理Spring XML配置文件的一种简便方式是使用Spring Tool Suite(https://spring.io/tools/sts)。在Spring Tool Suite的菜单中,选择File>New>Spring Bean Configuration File,能够创建Spring XML配置文件,并且可以选择可用的配置命名空间

用来装配bean的最基本的XML元素包含在spring-beans模式之中,在上面这个XML文件中,它被定义为根命名空间。是该模式中的一个元素,它是所有Spring配置文件的根元素

声明一个简单的

要在基于XML的Spring配置中声明一个bean,我们要使用springbeans模式中的另外一个元素:元素类似于JavaConfig中的@Bean注解。可以如下的方式声明CompactDiscbean:


这里声明了一个简单的bean,创建这个bean的类通过class属性来指定的,并且要使用全限定的类名

因没明确给定ID,所以这个bean将会根据全限定类名来进行命名。在本例中,bean的ID将会是“soundsystem.SgtPeppers#0”。其中,“#0”是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个SgtPeppers,并且没有明确进行标识,那么它自动得到的ID将会是“soundsystem.SgtPeppers#1”

通常来讲,更好的办法是借助id属性,为每个bean设置一个你自己选择的名字:

    

减少繁琐为了减少XML中繁琐的配置,只对那些需要按名字引用的bean(比如,你需要将对它的引用注入到另外一个bean中)进行明确地命名

借助IDE检查XML的合法性使用能够感知Spring功能的IDE,如Spring Tool Suite,能够在很大程度上帮助你确保Spring XML配置的合法性

借助构造器注入初始化bean

在Spring XML配置中,只有一种声明bean的方式:使用元素并指定class属性。Spring会从这里获取必要的信息来创建bean。但是,在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:

元素

使用Spring 3.0所引入的c-命名空间

构造器注入bean引用

1.元素方案:
现已声明SgtPeppers bean,并且SgtPeppers类实现了CompactDisc接口,所以实际上已经一个可以注入到CDPlayerbean中的bean。所需做的是在XML中声明CDPlayer并通过ID引用SgtPeppers:


    

当Spring遇到这个元素时,会创建一个CDPlayer实例。元素会告知Spring要将一个ID
为compactDisc的bean引用传递到CDPlayer的构造器中

2.c-命名空间方案:
c-命名空间是在XML中更为简洁地描述构造器参数的方式。要使用它的话,必须要在XML的顶部声明其模式,如下所示:




    
    

    
    
        
        
        
    

    
    

    
    


在c-命名空间和模式声明之后,就可以使用它来声明构造器参数了,如下所示:


    

属性名以“c:”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref”,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量“compactDisc”

引用参数的名称有些怪异,因为需要编译代码时,将调试标志(debug symbol)保存在类代码中。如果优化构建过程,将调试标志移除掉,那么这种方式可能就无法正常执行。根本不用去标示参数的方案:



将字面量(literal value)注入到构造器中

迄今为止,所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——而有时需要的只是用一个字面量值来配置对象

1.元素方案:

package soundsystem;
public class BlankDisc implements CompactDisc
{
    private String title;
    private String artist;
    
    public BlankDisc(String title, String artist)
    {
        this.title = title;
        this.artist = artist;
    }
    
    public void play()
    {
        System.out.println("Playing" + title + "by" + artist);
    }
}

    
    

上述程序使用元素进行构造器参数的注入。没有使用“ref”属性来引用其他的bean,而使用了
value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中

2.c-命名空间方案:

引用构造器参数名:


通过参数索引装配相同的字面量值:

               
装配集合

含有磁道列表概念的BLankDisc

package soundsystem.collections;

import java.ytil.List;
import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc
{
    private String title;
    private String artist;
    private List tracks;
    
    public BlankDisc(String title, String artist, List tracks)
    {
        this.title = title;
        this.artist = artist;
        this.tracks = tracks;
    }
    
    public void play()
    {
        System.out.println("Playing" + title + "by" + artist);
        for(String track : tracks)
        {
            System.out.println("-Track: " + track);
        }
    }
}

这个变更会对Spring如何配置bean产生影响,在声明bean时,必须要提供一个磁道列表。较好的解决方法是提供一个磁道名称的列表。首先,可以使用元素将其声明为一个列表:


    
    
    
        
            Sgt. Pepper"s Lonely Hearts Club Band
            With a Little Help from My Friend
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            
    
        
        
    

    
    


使用p-命名空间装配compactDisc属性:

属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref”结尾,这会提示Spring要进行装配的是引用,而不是字面量

将字面量注入到属性中

以下BlankDisc完全通过属性注入进行配置,而不是构造器注入。新的BlankDisc类如下所示:

package soundsystem.collections;

import java.ytil.List;
import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc
{
    private String title;
    private String artist;
    private List tracks;
    
    public setTitle(String title)
    {
        this.title = title;
    }
    
    public setArtistString artist)
    {
        this.artist = artist;
    }
            
    public setTracks(List tracks)
    {
        this.tracks = tracks;
    }
    
    public void play()
    {
        System.out.println("Playing" + title + "by" + artist);
        for(String track : tracks)
        {
            System.out.println("-Track: " + track);
        }
    }
}

借助元素的value属性装配这些属性,内嵌元素设置tracks属性:


    
    
    
        
            Sgt. Pepper"s Lonely Hearts Club Band
            With a Little Help from My Friend
            Lucy in the Sky with Diamonds
            Getting Better
            Fixing a Hole
            
              
  
最新活动
阅读需要支付1元查看
<