《Spring 实战》笔记2:裝配 Bean

IoC 和 DI

http://sishuok.com/forum/blogPost/list/2427.html

IoC — Inversion of Control ,即“控制反转”,不是什么技术,而是一种设计思想。在 Java 开发中, Ioc 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。IoC 很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

  • 谁控制谁,控制什么:传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 Ioc 容器来控制对象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

DI — Dependency Injection ,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标

需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。理解 DI 的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是某个容器管理对象依赖于 IoC 容器;“被注入对象的对象”依赖于“依赖对象”;
  • 为什么需要依赖:容器管理对象需要 IoC 容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是 IoC 容器注入某个对象,也就是注入“依赖对象”;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

三种装配方式

Spring 容器负责创建应用中的 bean,并通过 DI 维护这些 bean 之间的协作关系。Spring 提供了三种装配方式:

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

建议使用:自动装配机制,显示配置越少越好。当你必须显示配置时,推荐使用类型安全并且比 XML 更加强大的 JavaConfig。最后只有当你想要使用便利的 XML 命名空间,并且在 JavaConfig 中没有同样的实现时,才应用使用 XML。

自动化装配 bean

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

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

创建可被发现的 bean

CD 为我们阐述了 DI 是如何运行提供了一个很好的样例,如果你不将 CD 插入(注入)到 CD 播放器中,那么 CD 播放器其实没有太大的用处。CD 播放器依赖于 CD 才能完成它的使命。

// 定义 CD 的接口
public interface CompactDisc {
    void play();
}
// 定义 CD 接口的一个实现
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";

    @Override
    public void play() {
        System.out.println("Playing " + title + " by " + artist);
    }
}

组件扫描默认是不启用的,需要显式配置下。

@Configuration
@ComponentScan  // 默认会扫描与配置类相同的包
public class CDPlayConfig {
}

使用 XMl 来启用组件扫描的话,可以使用 Spring context 命名空间的 context:component-scan 元素。

<?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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <context:component-scan base-package="springaction02.auto"/>
</beans>
// 测试组件扫描功能 创建一个简单的 JUnit 测试,它会创建 Spring 上下文,并判断 CompactDisc 是不是真的创建出来了。
import 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)  // 会自动创建 Spring 应用的上下文
// @ContextConfiguration(locations = "classpath:CDPlayConfig.xml")  // 使用 xml 加载配置
@ContextConfiguration(classes = CDPlayConfig.class)  // 使用 Java 加载配置
public class CDPalyConfigTest {

    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull() {
        Assert.assertNotNull(cd);
    }
}

为组件扫描的 bean 命名

Spring 应用上下文中所有的 bean 都会给定一个 ID,默认就是将类名的首字母变成小写。SgtPeppers ID 为 sgtPeppers。

如果要设置不同的 ID,则需要传递给 @Component 注解。

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

还有一种方式,使用 Java 依赖注入规范(Java Dependency Injection)中提供的 @Named 注解来为 bean 设置 ID

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

Spring 支持将 @Named 作为 @Component 注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,他们是可以互相替换的。

设置组件扫描的基础包

没有为 @ComponentScan 设置任何属性,这意味着,按照默认规则,它会以配置类所在的包作为基础包(base package) 来扫描组件。

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

如果你想更加清晰的表明你所设置的是基础包,那么你可以通过 basePackages 属性来进行设置。basePackages 属性使用的是复数形式,这可以设置多个基础包。

@Configuration
@Componentscan("springaction02")
// @Componentscan(basePackages="springaction02")
// @Componentscan(basePackages={"springaction02", "video"})
public class CDPlayerConfig{}

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

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

可以考虑在包中创建一个用来扫描的空标记接口。通过标记接口的方式,你依然能够保持对重构友好的接口的引用,但是可以避免引用任何实际的应用程序代码。

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

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

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

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

@Autowired 注解不仅可以用于构造器上,还能用在属性 Setter 方法上,甚至 @Autowired 可以用在类的任何方法上

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

如果没有匹配的 bean,那么在应用上下文创建的时候,Spring 会抛出一个异常,为了避免异常的出现,你可以将 @Autowiredrequired 属性设置为 false。如果没有匹配的 bean 的话,代码中没有进行 null 检查的话,这个处于未装配的属性可能会出现 NullPointerException。

@Autowired(required=false)

如果有多个 bean 满足依赖关系时,Spring 将会抛出异常,表明没有明确指定要选择哪个 bean 进行装配。

@Autowired 是 Spring 特有的注解,也可以使用 @Inject,@Inject 注解来源于 Java 依赖注入规范,大多数情况下,它们是可以互相替换的。

验证自动装配

// import org.junit.contrib.java.lang.system.StandardOutputStreamLog
import org.junit.contrib.java.lang.system.SystemOutRule;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    @Rule
    public final SystemOutRule log = new SystemOutRule().enableLog();
    @Autowired
    private MediaPlayer player;
    @Autowired
    private CompactDisc cd;

    @Test
    public void  cdShouldNotBeNull() {
        assertNotNull(cd);
    }

    @Test
    public void play() {
        player.play();
        assertEquals(
                "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\n",
                log.getLog());
    }
}

书中使用了 StandardOutputStreamLog,这是来源于System Rules 库的一个 JUnit 规则,该规则能够基于控制台的输出编写断言。已过时,改用 SystemOutRule,需要导入 jar 包

<!-- https://mvnrepository.com/artifact/com.github.stefanbirkner/system-rules -->
<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-rules</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

通过 Java 代码装配 bean

JavaConfig 与应用程序中的业务逻辑和领域代码是不同的。JavaConfig 是配置代码。这意着它不应该包含任何业务逻辑,JavaConfig 也不应该侵入到业务逻辑代码中。通常会将 JavaConfig 放到单独的包中,使它与其他的应用程序逻辑分离开来。

创建配置类

import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {
}

和上文相比,只是将 @ComponentScan 注解移除了。

声明简单的 bean

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

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

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

默认情况下,bean 的 ID 与带有 @Bean 注解的方法名是一样的。这个例子中是 sgtPeppers,如果你想重命名该方面,也可以通过 name 属性指定一个不同的名字 @Bean(name="lonelyHeartsClub")

借助 JavaConfig 实现注入

现在需要声明 CDPlayer bean,它依赖与 CompactDisc。

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

@Bean  // 同 sgtPeppers 方法一样使用 @Bean 注解
public CDPlayer cdPlayer() {
    return new CDPlayer(sgtPeppers());
    // 调用了需要传入 CompactDisc 的构造器,看样子是调用 sgtPeppers() 方法得到的,
    // 因为 sgtPeppers() 方法上添加了 @Bean 注解,Spring 会拦截所有对它的调用,直接返回该方法的 bean,而不是每次都调用
}

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

Spring 中的 bean 都是单例的,Spring 会拦截对 sgtPeppers() 的调用并确保返回的是 Spring 创建的 bean,所以两个方法会得到相同的 bean。

通过调用方法来引用 bean 的方式有点令人困惑,其实还有一种理解起来更为简单的方式

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

当 Spring 调用 cdPlayer 时,它会自动装配一个 CompactDisc 到配置方法中,然后,方法体就可以按照合适的方式来使用它。借助于这种技术,cdPlayer 也可以将 CompactDisc 注入到 CDPlayer 的构造器中,而不用明确引用 CompactDisc 的 @Bean 方法。

通过这种方式引用其他的 bean 是最佳的选择。因为它不会要求将 CompactDisc 声明到同一个配置类中。实际上它可以通过组件扫描能够自动发现或者通过 XML 来进行配置。你可以将配置分散到多个配置类、XML 文件以及自动扫描和装配的 bean 中。

通过 XML 装配 bean

本节的内容只是用来帮助你维护已有的 XML 配置,在完成新的 Spring 工作时,希望你会使用自动化配置和 Java 配置。

声明一个简单的 bean

需要使用 spring-beans 模式中的另一个元素:<bean>,类似于 JavaConfig 中的 @Bean 注解。

<bean class="springaction02.SgtPeppers"/>

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

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

尽管自动化的 bean 命名方式非常方便,但需要引用它的话,借助于 id 属性。为每个 bean 设置一个你自己选择的名字

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

第一件需要注意的事情就是你不在需要直接负责创建 SgtPeppers 的实例,在基于 JavaConfig 的配置中,我们需要这样做。当 Spring 发现这个元素时,它会调用 SgtPeppers 的默认构造器来创建 bean。在 XML 配置中,bean 的创建显得更加被动,不过,它没有 javaConfig 那样强大,在 JavaConfig 中,你可以通过任何可以想象到的方法来创建 bean 实例。

另一个需要注意的是,在这个简单的声明中,我们将 bean 的类型以字符串的形式设置在了 class 属性中。谁能确保设置给 Class 属性的值是真正的类呢?Spring 的 XML 配置并不能从编译器的类型检查中受益,即便它所引用的是实际的类型,如果你重命名了会发生什么呢?

以上介绍的只是 JavaConfig 要优于 XML 配置的部分原因。在你的应用选择配置风格时,要记住 XMl 配置的这些缺点。

借助构造器注入初始化 bean

在 Spring XML 配置中,只有一种声明 bean 的方式,使用元素并制定 class 属性,Sprng 会从这里获取必要的信息来创建 bean。

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

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

<constructor-arg> 元素比使用 c- 命名空间会更加冗长。从而导致 XMl 更加难以读懂。另外有些事情 <constructor-arg> 可以做到,但是使用 c- 命名空间却无法实现。

构造器注入 bean 引用

同样还是 CDPlayer 的例子,已经声明了 SgtPeppers bean。

<bean id="cdPlayer" class="springaction02.CDPlayer">
    <!--  index="0" 可以表示位置 -->
    <constructor-arg ref="compactDisc"/>
</bean>

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

替代方案,可以使用 Spring 的 c- 命名空间。需在 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:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

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

通过 Spring 的 c- 命名空间将 bean 引用注入到构造器参数中

这里直接引用了构造器参数的名称,还可以使用参数在整个参数列表中的位置信息。

<!-- 0 也就是参数的索引 -->
<bean id="cdPlayer" class="springaction02.CDPlayer" c:_0-ref="compactDisc"/>

<!-- 因为只有一个构造器参数,所以可以不用写 0 -->
<bean id="cdPlayer" class="springaction02.CDPlayer" c:_-ref="compactDisc"/>

将字面量注入到构造器中

创建 CompactDisc 的一个新实现 。

public class BlankDisc implements CompactDisc {
    private String title;
    private String artist;

    public BlankDisc(String title, String artist) {
        this.title = title;
        this.artist = artist;
    }

    @Override
    public void play() {
        System.out.println("Playing " + title + " by " + artist);
    }
}

使用 constructor-arg

<bean id="compactDisc1" class="springaction02.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
    <constructor-arg value="The Beatles"/>

    <!-- <constructor-arg name="title" value="The Beatles"/> 根据参数名注入,不建议使用 -->
    <!-- <constructor-arg type="java.lang.String" value="The Beatles"/>  根据参数类型进行注入,不能有重复的 -->
</bean>

使用 c- 命名空间

<!-- 书中写的 c:_title 是错误的 -->
<bean id="compactDisc2" class="springaction02.BlankDisc"
          c:title="Sgt. Pepper's Lonely Hearts Club Band"
          c:artist="The Beatles" />

<bean id="compactDisc3" class="springaction02.BlankDisc"
        c:_0="Sgt. Pepper's Lonely Hearts Club Band"
        c:_1="The Beatles" />

<!-- 同样如果只有一个构造器参数,可以使用 c:_="" -->

使用静态工厂方式实例化 Bean

指定 factory-method 属性来指定实例化 Bean 的方法

public class HelloApiStaticFactory {
    // 工厂方法
    public static HelloApi newInstance(String message) {
        // 返回需要的 Bean 实例
        return new HelloImpl2(message);
    }
}
<!-- 使用静态工厂方法 -->
<bean id="bean3" class="springaction02.HelloApiStaticFactory"
factory-method="newInstance">
    <constructor-arg index="0" value="Hello Spring!"/>
</bean>

使用实例工厂方法实例化 Bean

使用这种方式不能指定 class 属性,此时必须使用 factory-bean 属性来指定工厂 Bean,factory-method 属性指定实例化 Bean 的方法

// 实例工厂类代码
public class HelloApiInstanceFactory {
    public HelloApi newInstance(String message) {
        return new HelloImpl2(message);
    }
}
<!—1 、定义实例工厂 Bean -->
<bean id="beanInstanceFactory" class="springaction02.HelloApiInstanceFactory"/>
<!—2 、使用实例工厂 Bean 创建 Bean -->
<bean id="bean4" factory-bean="beanInstanceFactory" factory-method="newInstance">
    <constructor-arg index="0" value="Hello Spring!"></constructor-arg>
</bean>

装配集合,null 值

c- 命名空间无法实现。

假设 BlankDisc 有了一个新的属性

private List<String> tracks;

public BlankDisc(String title, String artist, List<String> tracks) {
    this.title = title;
    this.artist = artist;
    this.tracks = tracks;
}

最简单的方法,直接传递 null

<bean id="compactDisc4" class="springaction02.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
    <constructor-arg value="The Beatles"/>
    <constructor-arg><null/></constructor-arg>
</bean>

使用 <list><set><array>

<bean id="compactDisc5" class="springaction02.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
    <constructor-arg value="The Beatles"/>
    <constructor-arg>
        <list> <!-- 也可以使用 set array-->
            <value>11</value>
            <value>22</value>
            <value>33</value>
            <!-- 如果是引用,可以使用 <ref bean="getbean" /> -->
        </list>
    </constructor-arg>
</bean>

注入 Map 类型

<bean id="mapBean" class="springaction02.MapBean">
    <property name="values">
        <map key-type="java.lang.String" value-type="java.lang.String">
            <entry><key><value>1</value></key><value>11</value></entry>
            <entry key="2" value="22"/>
            <!-- <entry key-ref=" 键引用 " value-ref=" 值引用 "/> -->
        </map>
    </property>
</bean>

Properties 注入

public void setValues(Properties values) {
    this.values = values;
}
<bean id="propertiesBean" class="">
    <property name="values">
        <props value-type="int" merge="default">  <!-- value-type 不生效,类型固定为 String -->
            <prop key="1">less</prop>
            <prop key="2">2</prop>
        </props>
    </property>
</bean>

<!-- 另一种方式 -->
<bean id="propertiesBean" class="">
    <property name="values">
        <value>
            1=11
            2=22
            3=33
        </value>
    </property>
</bean>

设置属性

使用 Spring XML 配置实现属性注入(setter 注入)

public class CDPlayer {
    private CompactDisc compactDisc;

    @Autowired
    public void setCd(CompactDisc compactDisc){
        this.compactDisc = compactDisc;
    }

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

该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。

<!-- CDPlayer 没有任何的构造器(除了隐含的默认构造器),可以采用如下的方式将其声明为 Spring bean -->
<bean id="cdPlayer" class="springaction02.CDPlayer" />

Spring 在创建 bean 的时候不会有任何问题,但是 CDPlayTest 会因为出现 NullPointException 而导致测试失败,因为我们并没有注入 CDPlayer 的 compactDisc 属性。不过按照下面的方式修改 XML,就能解决该问题

<bean id="cdPlayer" class="springaction02.CDPlayer">
    <property name="compactDisc" ref="compactDisc"/>
    <!-- <property name="test" value="test"/> -->
    <!-- 如果是字面量,则使用 value -->
    <!-- 如果是 list,也同样和构造器注入类似
    <propert name="listTest">
      <list>
        <value>11</value>
        <value>22</value>
        <value>33</value>
      </list>
    </propert>
    -->
</bean>

替代 <property> 元素方案,p- 命名空间,需在 XML 文件中进行声明

xmlns:p="http://www.springframework.org/schema/p"

p 命名空间

使用 p- 命名空间,与 c- 命名空间类似

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

util- 命名空间

需在 XML 文件中进行声明

<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
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/beans/spring-util.xsd">
</beans>

util- 命名空间所提供的功能之一就是 <util:list> 元素它会创建一个列表的 bean,借助 <util:list>,将其声明到单独的 bean 之中。

<util:list id="trackList">
  <value>111</value>
  <value>222</value>
  <value>333</value>
</util:list>

现在,我们能够像使用其他的 bean 那样,将磁道列表 bean 注入到 BlankDisc bean 的 tracks 属性中:

<bean id="compactDisc6" class="springaction02.BlankDisc"
    p:title="Sgt. Pepper's Lonely Hearts Club Band"
    p:artist="The Beatles"
    p:tracks-ref="trackList"/>

Spring util- 命名空间中的元素

  • <util:constant>:引用某个类型的 public static 域,并将其暴露为 bean
  • <util:list>:创建一个 java.util.List 类型的 bean,其中包含值和引用
  • <util:map> :创建一个 java.util.Map 类型的 bean,其中包含值或引用。
  • <util:properties>:创建一个 java.util.properteis 类型的 bean。
  • <util:set>:创建一个 java.util.Set 类型的 bean,其中包含值或引用。

导入和混合配置

在典型的 Spring 应用中,我们可能同时使用自动化和显示配置。即便你更喜欢通过 JavaConfig 实现显示配置,但有时候 XML 确实最佳的方案。可以将 JavaConfig 的组件扫描和自动装配或 XMl 配置混合在一起。

在 JavaConfig 中引用 XML 配置

多个 JavaConfig 文件时,方法一,直接在其中一个文件使用 @Import 注解。

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

方法二,创建一个更高级别的 SoundSystemConfig,在这个类中使用 @Import 将两个配置组合在一起。

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

现在将一部分配置在了 XML 中,我们该如何让 Spring 同时加载它和其他基于 Java 的配置呢?

@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}

在 XML 配置中引用 JavaConfig

在 XML 中,我们可以使用 import 元素来拆分 XML 配置。

<import resource="cd-config.xml"/>
<bean id="cdPlayer"
      class="springaction02.CDPlayer"
      c:cd-ref="compactDisc" />

为了将 JavaConfig 类导入到 XML 配置中,我们可以这样声明 bean:

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

还可以声明一个更高级被的层次的配置文件,这个文件不声明任何 bean,只是负责将两个或多个配置文件组合在一起。

<bean class="springaction02.CDConfig"/>

<import resource="cdplayer-config.xml"/>

延迟初始化 Bean

延迟初始化的 Bean 通常会在第一次使用时被初始化;

<bean id="helloApi" class="" lazy-init="true"/>

小节

Spring 的核心是 Spring 容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证他们的依赖能够得到满足,这样的话,组件才能完成预定的任务。

在本章中,我们看到了 Spring 中装配 bean 的三种方式:自动化装配、基于 Java 的显示配置、以及基于 XML 的显示配置。不管采用什么方式,这些技术都描述了 Spring 应用中的组件以及这些组件之间的关系。

尽可能使用自动化配置,以避免显示配置所带来的维护成本。但是,如果确实需要显示的配置 Spring 的话,应该优先选择基于 Java 的配置,它比基于 XML 的配置更加强大、类型安全、并且易于重构。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Spring 实战》笔记2:裝配 Bean

文章字数:6.5k

本文作者:Bin

发布时间:2019-04-20, 21:14:22

最后更新:2019-12-08, 15:10:14

原始链接:http://coolview.github.io/2019/04/20/Spring/%E3%80%8ASpring%20%E5%AE%9E%E6%88%98%E3%80%8B%E7%AC%94%E8%AE%B02%EF%BC%9A%E8%A3%9D%E9%85%8D%20Bean/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录