装配Bean

在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。
创建应用对象之间协作关系的行为通常成为装配(wiring),这也是依赖注入(DI)的本质。

1、Spring配置的可选方案

当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

  • 在XML中进行显示配置
  • 在Java中进行显示配置
  • 隐式的bean发现机制和自动装配

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

2、自动化装配bean

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

  • 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean
  • 自动装配(autowiring):Spring自动满足bean之间的依赖

2.1创建可被发现的bean

CompactDisc接口在Java中定义了CD的概念

package soundsystem;
public interface CompactDisc {
    void play();
}

带有@Component注解的CompactDisc实现类

package soundsystem;
import org.springframework.stereotype.Component;
/**
 * @Component注解,表明该类会作为组件类,并告知Spring要为这个类创建bean
 **/
@Component
public class SgtPeppers implements CompactDisc {
    private String title = "stg,peppers";
    private String artist = "the beatles";
    
    public void play() {
        System.out.println("playing " + title + " by " + artist);
    }
}

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

@ComponentScan注解启用了组件扫描

package soundsystem;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @Configuration用于定义配置类
 **/
@Configuration
@ComponentScan
public class CDPlayerConfig {
}

CDPlayerConfig类并没有显示地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包(扫描这个包以及这个包下的所有子包,查找带有@Component注解的类,然后在Spring中自动为其创建一个bean)。
若使用XML来启用组件扫描的话,可以使用Spring context命名空间的<context:component-scan>元素。

通过XML启用组件扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

        <context:component-scan base-package="soundsystem"/>

</beans>

测试组件扫描能够发现CompactDisc

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;
import soundsystem.CDPlayerConfig;
import soundsystem.CompactDisc;

import static junit.framework.TestCase.assertNotNull;

/**
 * @ContextConfiguration:告诉它需要在CDPlayerConfig中加载配置
 **/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= CDPlayerConfig.class)
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;

    @Test
    public void test(){
        assertNotNull(cd);//判断cd属性是否存在
    }
}

如果cd属性不为null,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码中。

2.2为组件扫描的bean命名

Spring应用上下文中所有的bean都会给定一个ID。默认会根据类名为其指定一个ID。

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

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

或者可以使用Java依赖注入规范中所提供的@Named注解来为bean设置ID

package soundsystem;
import javax.inject.Named;
/**
 * @Component注解,表明该类会作为组件类,并告知Spring要为这个类创建bean
 **/
@Named("lonelyHeartClub")
public class SgtPeppers implements CompactDisc {
    ...
}

2.3设置组件扫描的基础包

如果需要明确地设置基础包,需要在@ComponentScan的value属性中指明包的名称:

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

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

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

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

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

这里所设置的基础包是以String类型表示的,是类型不安全的。如果重构代码的话,那么所指定的基础包可能就会出现错误。
@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:

@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig {
}

为basePackageClasses属性所设置的数组中包含了类,这些类所在的包将会作为组件扫描的基础包。

2.4通过为bean添加注解实现自动装配

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

package soundsystem;

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

@Component
public class CDPlayer implements MediaPlayer {
    private CompactDisc cd;

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

    public void play() {
        cd.play();
    }
}

构造器上添加了@Autowired注解,表明当Spring创建CDPlayer bean时,会通过这个构造器进行实例化并会传入一个CompactDisc类型的bean

@Autowired注解可以用在类的任何方法上。
假如有且只有一个bean匹配需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用的上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,可以将@Autowired的required属性设置为false。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定哪个bean进行自动装配。

Java依赖注入规范中的@Inject可以替代@Autowired。

2.5验证自动装配

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= CDPlayerConfig.class)
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;

    @Autowired
    private MediaPlayer player;

    @Test
    public void test(){
        assertNotNull(cd);//判断cd属性是否存在
    }

    @Test
    public void play(){
        player.play();
    }
}//output:playing stg,peppers by the beatles

3、通过Java代码装配bean

如果要将第三方库中的组件装配到你的应用中,在这种情况下是没有办法在它的类上添加@Component和 @Autowired注解的,因此就不能使用自动化装配的方案了。
这种情况下,必须要采用显示装配的方案。在进行显示装配的时候,有两种可选方案:Java和XML。
在进行显示装配的时候,JavaConfig是更好的方案,因为他更为强大、类型安全并且重构友好。
JavaConfig是配置代码,意味着不应该包含任何业务逻辑,也不应该侵入到业务逻辑代码中。通常会将JavaConfig放到单独的包中,使它与其他的应用程序裸机分离开来。

3.1创建配置类

@Configuration
public class CDPlayerConfig {
}

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

3.2声明简单的bean

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

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

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

可以通过name属性重命名该方法:

@Bean(name = "lonelyHeartsClubBand")
    public CompactDisc sgtPeppers(){
        return new SgtPeppers();
    }

3.3借助JavaConfig实现注入

在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法。

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

CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调解,并确保返回该方法所创建的bean,而不是每次都对其进行实际的调用。

还有一种理解起来更为简单的方式:

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

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

也可以通过Setter方法注入CompactDisc

@Component
public class CDPlayer implements MediaPlayer {
    private CompactDisc cd;

    public void setCompactDisc(CompactDisc cd) {
        this.cd = cd;
    }

    public void play() {
        cd.play();
    }
}//这里的CDPlayer类作了修改

@Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc){
        CDPlayer cdPlayer = new CDPlayer();
        cdPlayer.setCompactDisc(compactDisc);
        return cdPlayer;
    }

带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到了Java语言的限制。

4、通过XML装配bean

4.1创建XML配置规范

最简单的Spring XML配置:

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
	<!-- configuration details go here -->
</beans>

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

在IDEA中配置和创建Spring XML文件:
导入maven包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.1.RELEASE</version>
</dependency>

便可以直接创建

在这里插入图片描述

4.2声明一个简单的<bean>

要在基于XMl的Spring配置中声明一个bean,需要使用spring-beans模式中的另外一个元素:<bean>

<bean class="soundsystem.SgtPeppers"/>

创建这个bean通过class属性来指定,并且要使用全限定的类名。

可以借助id属性,为每个bean设置一个自己选择的名字

<bean id="compactDisc" class="soundsystem.SgtPeppers"/>

4.3借助构造器注入初始化bean

在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:

1.<constructor-arg>元素
2.使用Spring3.0所引入的c-命名空间

4.3.1构造器注入bean引用

在XML中声明CDPlayer并通过ID引用SgtPeppers:

<bean id="cdPlayer" class="soundsystem.CDPlayer">
        <constructor-arg ref="compactDisc"/>
    </bean>

作为替代方案,可以使用Spring的c-命名空间。

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>

这个属性名如何组合而来
也可以使用参数的索引来识别构造器参数:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc"/>

4.3.2将字面量注入到构造器中

创建一个新的CompactDisc实现:

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);
    }
}

将已有的SgtPeppers替换为这个类:

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg value="Sgt. Peppers"/>
        <constructor-arg value="The Beatles"/>
    </bean>

这里是使用value属性,通过该属性表明给定的值要以字面量的形式注入到构造器中。
要使用c-命名空间的话,第一种方案是引用构造器参数的名字:

<bean id="compactDisc" class="soundsystem.BlankDisc"
          c:title="Sgt. Peppers"
          c:artist="The Beatles"/>

可以看出,装配字面量和装配引用的区别在于属性名中去掉了"-ref"后缀。
也可以通过参数索引装配相同的字面量值:

<bean id="compactDisc" class="soundsystem.BlankDisc"
          c:_0="Sgt. Peppers"
          c:_1="The Beatles"/>

4.3.3装配集合

为BlankDisc增加一个磁道列表:

public class BlankDisc implements CompactDisc {
    private String title;
    private String artist;
    private Set<String> tracks;

    public BlankDisc(String title, String artist, Set<String> 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);
        }
    }
}

在声明bean的时候,我们必须要提供一个磁道列表。
可以使用<list>元素将其声明为一个列表:

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg value="Sgt. Peppers"/>
        <constructor-arg value="The Beatles"/>
        <constructor-arg>
            <list>
                <value>track1</value>
                <value>track2</value>
                <value>track3</value>
                <value>track4</value>
                <value>track5</value>
            <list/>
        </constructor-arg>
    </bean>

<list>元素是<constructor-arg>的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>元素用来指定列表中的每个元素。
可以使用<ref>元素替代<value>,实现bean引用列表的装配。
假设有一个Discography类,它的构造器如下:

public Discography(String artist, List<CompactDisc> cds){...}

可以采取如下配置Discography bean:

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg value="The Beatles"/>
        <constructor-arg>
            <list>
                <ref bean="sgtPeppers"/>
                <ref bean="whiteAlbum"/>
                <ref bean="revolver"/>
            <list/>
        </constructor-arg>
    </bean>

当构造器参数类行为java.util.Set时,可以使用<set>

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg value="Sgt. Peppers"/>
        <constructor-arg value="The Beatles"/>
        <constructor-arg>
            <set>
                <value>track1</value>
                <value>track2</value>
                <value>track3</value>
                <value>track4</value>
                <value>track5</value>
            </set>
        </constructor-arg>
    </bean>

4.3设置属性

public class CDPlayer implements MediaPlayer {
    private CompactDisc compactDisc;
    
    public void setCompactDisc(CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }

    public void play() {
        compactDisc.play();
    }
}

对于强依赖使用构造器注入,对可选性依赖使用属性注入。

现在,CDPlayer没有任何的构造器(除了隐含的默认构造器),它也没有任何的强依赖。因此可以采用如下的方式将其声明为Spring bean:

<bean id="cdPlayer" class="soundsystem.CDPlayer">
    </bean>

Spring在创建bean的时候不会有任何问题,但是CDPlayerTest会因为出现NullPointerException而导致测试失败,因为并没有注入CDPlaye的CompactDisc属性,应改成如下的方式:

<bean id="cdPlayer" class="soundsystem.CDPlayer">
        <property name="compactDisc" ref="compactDisc"/>
    </bean>

<property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。

Spring还提供了更为简洁的p-命名空间,作为<property>的替代方案。

<bean id="cdPlayer" class="soundsystem.CDPlayer"
          p:compactDisc-ref="compactDisc"/>

4.3.1将字面量注入到属性中

属性也可以注入字面量。
创建新的类,完全通过属性注入进行配置:

public class BlankDisc implements CompactDisc {
    private String title;
    private String artist;
    private List<String> tracks;

    public void setTitle(String title) {
        this.title = title;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public void setTracks(List<String> tracks) {
        this.tracks = tracks;
    }

    public void play() {
        System.out.println("Playing " + title + " by " + artist);
        for (String track : tracks){
            System.out.println("-Track: " + track);
        }
    }
}

可以借助<property>元素中的value属性实现该功能:

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <property name="title" value="sgt.pepper's lonely hearts"/>
        <property name="artist" value="the beatles"/>
        <property name="tracks">
            <list>
                <value>with a little help</value>
                <value>lucy in the sky</value>
                <value>getting better</value>
            </list>
        </property>
    </bean>

另一种可选方案就是使用p-命名空间的属性来完成该功能:

<bean id="compactDisc" 
          class="soundsystem.BlankDisc"
            p:title="sgt.pepper's lonely hearts"
            p:artist="the beatles">
        <property name="tracks">
            <list>
                <value>with a little help</value>
                <value>lucy in the sky</value>
                <value>getting better</value>
            </list>
        </property>
    </bean>

与c-命名空间一样,装配bean引用和装配字面量的唯一区别就在于是否带有"-ref"后缀。如果没有"-ref"后缀的话,所装配的就是字面量。
为了解决p-命名空间不能装配集合的问题,可以使用Spring util-命名空间来简化BlankDisc bean。
util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表的bean。借助<util:list>,可以将磁道列表转移到BlankDisc bean之外,并将其声明到单独的bean中:

<util:list id="trackList">
        <value>with a little help</value>
        <value>lucy in the sky</value>
        <value>getting better</value>
    </util:list>

然后将磁道列表bean注入到BlankDisc bean的tracks属性中:

<util:list id="trackList">
        <value>with a little help</value>
        <value>lucy in the sky</value>
        <value>getting better</value>
    </util:list>

5、导入和混合配置

自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。

5.1在JavaConfig中引用XML配置

如果将BlankDisc bean从CDPlayerConfig拆分出来,定义到它自己的CDConfig类中:

@Configuration
public class CDConfig {
    @Bean
    public CompactDisc compactDisc(){
        return new SgtPeppers();
    }
    
}

compactDisc()方法已经从CDPlayerConfig中移除掉了,需要有一种方式将这两个类组合在一起。
一种方式就是在CDPlayerConfig中使用@Import注解导入CDConfig:

@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc){
        return new CDPlayer(compactDisc);
    }
}

另一种更好的方式,就是不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundsystemConfig,在这个类中使用@Inport将两个配置类组合在一起:

@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
public class SoundsystemConfig {
}

现在,我们假设基于某种原因希望通过XML配置BlankDisc,如下:

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <property name="title" value="sgt.pepper's lonely hearts"/>
        <property name="artist" value="the beatles"/>
        <property name="tracks">
            <list>
                <value>with a little help</value>
                <value>lucy in the sky</value>
                <value>getting better</value>
            </list>
        </property>
    </bean>

则需要通过@ImportResource注解让Spring同时加载它和其他基于Java的配置:

@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:Spring.xml")
public class SoundsystemConfig {
}

5.2在XML配置中引用JavaConfig

在XML中,我们可以使用<import>元素来拆分XML配置。
假设将BlankDisc bean拆分到自己的配置文件中,该文件命名为cd-config.xml。可以在XML配置文件中使用<import>元素来引用该文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
    <import resource="cd-config.xml"/>
    <bean id="cdPlayer" class="soundsystem.CDPlayer"
          p:compactDisc-ref="compactDisc"/>
</beans>

再假设,不再将BlankDisc 配置在XML中,而是将其配置在JavaConfig中,CDPlayer则将继续配置在XML中。基于XML的配置引用一个JavaConfig类,需要用到<bean>元素:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
       
    <bean class="soundsystem.CDConfig"/>
    <bean id="cdPlayer" class="soundsystem.CDPlayer"
          p:compactDisc-ref="compactDisc"/>
</beans>

同样,可以创建一个更高层次的配置文件,这个文件不声明任何的bean,只是负责将两个或更多的配置组合起来:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
  
    <bean class="soundsystem.CDConfig"/>
    <import resource="cdplayer-config.xml"/>
</beans>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值