Loading

Spring实战 二 Bean装配

先导

Spring实战 一 依赖注入和AOP概述

三种方式

Spring提供了三种方式来装配Bean,XML只是其中一种,还有通过JavaConfig,就是使用一个Java类来配置,这种配置的好处是配置就是程序,你可以使用Java的任何语法进行各种个性化的配置。还有一种是自动装配。

大部分时间自动装配就可以了,能用自动装配的时候就自动装配,再次的选择就是JavaConfig,实在不行再使用XML。

自动装配

自动装配比较简单,而且也是大部分时候使用的。

下面来看示例中的几个接口

// 代表一张CD光碟
public interface CompactDisc {
    void play();
}
// 代表一种播放器
public interface MediaPlayer {
    void play();
}

下面我们定义一个CD的实现类,@Component是Spring的一个注解,代表它是一个Spring的组件,Spring会自动扫描这些组件,Spring会为组件创建Bean。

@Component
public class SgtPeppers implements CompactDisc {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String author = "The Beatles";

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

下面我们来定义一个配置类,因为咱得告诉Spring扫描哪个包下的组件,要不然它怎么知道扫啥。

@Configuration
@ComponentScan
public class CDPlayerConfiguration { }

@Configuration代表了这个类是一个配置类,而@ComponentScan代表了它要进行组件扫描,如果你不指定任何参数,它就是扫描和配置类相同的包下的组件。可以通过basePackges指定要扫描的包,也可以通过basePackageClasses来通过类来指定类所在的包。

嘶,我们创建了一个配置类去指定Spring扫描哪个包,那么是不是还得指定Spring去哪扫描配置类????

创建一个测试类,@ContextConfiguration注解指定了注解类,这个参数名叫classes,复数形式,哈哈哈证明你可以以数组的形式传递多个配置类。@RunWith是Spring为JUnit测试框架提供的一个Runner,先忽略它就好。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfiguration.class)
public class CDPlayerTest {

    @Autowired private CompactDisc cd;

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

}

注意注意,这里我们在测试类里声明了一个CompactDisc类型的属性,因为Spring已经能够通过配置类扫描组件并且读取到我们的Bean了,所以我们可以直接让Spring将这个Bean注入,使用@Autowired注解让Spring自动去容器中匹配一个Bean来注入。

应该是能通过测试。。。。。。哈哈哈。。。

下面来编写CDPlayer类,来播放CompactDisc,同样使用@Component注解来声明,默认的组件名就是类名首字母小写,也就是cDPlayer,现在我们给它指定了一个新名字。

@Component("MyCDPlayer")
public class CDPlayer implements MediaPlayer {

    private CompactDisc cd;

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

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

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

}

@Autowired可以添加在构造器上也可以添加在一个普通方法上,会将参数中需要的类型给注入。一般情况下使用一个办法即可。如果指定的类型在容器中找不到可以装配的Bean,那么Spring就会抛出个异常,如果你有这种不必须注入的Bean的需求,使用require=false即可。

然后我们修改测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfiguration.class)
public class CDPlayerTest {

    @Autowired private MediaPlayer player;

    @Test
    public void testPlay() {
        player.play();
    }

}

可以使用@Named代替@Component,使用@Inject代替@Autowired,这两个是Java的依赖注入标准提供的。

现在我们的第一个使用自动装配的例子就写完了。

通过Java代码装配Bean

删除掉那些@Component@Autowired,并且删除掉配置类中的自动扫描注解@ComponentScan

@Configuration
public class CDPlayerConfiguration {

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

    @Bean
    public MediaPlayer setMediaPlayer() {
        CDPlayer player = new CDPlayer();
        player.setCompactDisc(setCompactDisc());
        return player;
    }
}

@Bean声明一个Bean,并且没有了@Component,你要自己把它们变成Bean让Spring给扫描到。创建出来的Bean的名字就是方法名。

对于setCompactDisc我们直接使用new来创建一个对象作为Bean。

而对于setMediaPlayer我们用了set方法,这没有一个规则,你可以用任何Java语法来返回这个对象作为Bean。

player.setCompactDisc时,我们调用了另一个Bean的方法,是不是每次都会重新创建一个Bean对象?默认情况下Spring中的所有Bean都是单例的,Spring会拦截所有的Bean方法的调用,只返回它最初返回的那个对象。

也就是说无论你构造多少个CDPlayer,它们持有的都是同一个CompactDisc。

如果这样你看着很费解,还有另一种写法,比较符合依赖注入的思想,我也比较喜欢这种写法。

@Bean
public MediaPlayer setMediaPlayer(CompactDisc cd) {
    CDPlayer player = new CDPlayer();
    player.setCompactDisc(cd);
    return player;
}

这就像依赖注入了,好像是我们在装配MediaPlayer Bean时,向Spring请求了一个CompactDisc类型的Bean,然后Spring去给我注入这个对象。

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfiguration.class)
public class CDPlayerJavaConfigTest {

    @Autowired private MediaPlayer player;

    @Test
    public void testPlay(){
        player.play();
    }
}

在测试中我们同样使用了@Autowired,这证明我们可以混合使用三种装配方式。

使用XML配置

XML配置太繁琐了,能不用则不用。

Spring的XML配置文件要以beans为一个根标签,代表其中存储了很多Bean。

这其中有一些aop相关的xsd文件,我们用不到,但我也不像摘出来了。

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd">

</beans>

使用bean标签声明一个bean,以下是最简单的一个Bean声明

<bean class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers"/>

通过class指定一个Bean的完全限定名,默认情况下,Spring为该bean分配的Id为它的全限定类名#0,#0代表它是同类型的Bean中第一个声明的,如果再有一个这个类型的Bean那么它就是#1。

一般情况下我们会指定一个id,而不用自动生成的

<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers"/>

Spring发现这个bean配置的时候,会调用它的默认构造器创建一个实例。

如果需要完成依赖注入,可以通过给类添加其他构造器,并在XML文件中配置调用这个构造器。

<bean id="cdplayer" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer">
    <constructor-arg ref="sgtpeppers"/>
</bean>

constructor-arg就是构造函数的参数,Spring会自动匹配这些参数,调用对应的构造器。ref指定了一个其他bean的引用,Spring会将这个bean自动注入到构造函数中。

还有一种办法是使用Spring3.0及以上才会提供的c属性。首先在XML头中引入一个c命名空间。

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

注意这里使用了c:cd-ref来指定一个构造器参数,语法是c:构造器参数名-ref。虽然简洁了不少,但总感觉使用构造器参数名作为属性还是有点不对劲。

<bean id="cdplayer" 
    class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer"
    c:cd-ref="sgtpeppers">
</bean>

还可以用c:_0-ref,这里_数字是表示构造器中第几个参数。

我的想法就是这东西尽量别用,虽然比consructor-arg简单。

如果想注入字面量,可以使用value属性,先给SgtPeppers类添加一个构造器

public SgtPeppers(String title, String author) {
    this.title = title;
    this.author = author;
}
<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
    <constructor-arg value="The Beatles"/>
</bean>

使用c命名空间就是c:_title="xxx",去掉了后面的-ref

我们看看如何注入一个列表数据,加入歌曲音轨属性。

private List<String> tracks;

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

修改play方法

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

你可以在外面注入一个null,但这好像并没什么意义,因为本来就是null

<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
    <constructor-arg value="The Beatles"/>
    <constructor-arg><null/></constructor-arg>
</bean>

通过<list>可以传递一个列表数据。

<constructor-arg>
    <list>
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <value>Fixing a Hole</value>
    </list>
</constructor-arg>

除了使用value,当然也可以使用ref来进行传递任何引用类型的列表。

<set>标签和<list>标签都被允许,区别就是Spring创建对象的时候创建的是一个Set还是List

当然XML的方式也可以使用set方法来注入

<bean id="cdplayer" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer">
    <property name="compactDisc" ref="sgtpeppers"/>
</bean>

也有对应的简洁命名空间p

xmlns:p="http://www.springframework.org/schema/p"
<bean 
    id="cdplayer" 
    class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer"
    p:compactDisc-ref="sgtpeppers"/>

对于p就不过多介绍了,和c差不多。

p和c好像不能注入集合类型的数据,其实是可以的,使用util命名空间。好吧我承认我已经烦了,这可能就是为什么其它的配置方式被发明出来。

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

在这里使用util:list定义了一个列表,并通过c传递给构造器了,并且c命名空间可以和constructor-arg混合使用

<util:list id="trackList">
    <value>Sgt. Pepper's Lonely Hearts Club Band</value>
    <value>With a Little Help from My Friends</value>
    <value>Lucy in the Sky with Diamonds</value>
    <value>Getting Better</value>
    <value>Fixing a Hole</value>
</util:list>

<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers" c:tracks-ref="trackList">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
    <constructor-arg value="The Beatles"/>
</bean>

混合配置

混合配置多个JavaConfig

在一个大项目中,配置项可能有好多好多,不久一个Java文件就会显得笨重,难以容纳。

可以在一个JavaConfig中通过@Import标签导入另一个JavaConfig,不过更推荐的办法是使用一个总配置文件,其中什么也不写,只是整合所有的其他配置文件

@Configuration
@Import(value = {
        XXXConfig.class,
        YYYConfig.class
})
public class AppConfiguration {}

配置JavaConfig和XML

通过@ImportResource可以配置两种配置文件。

@Configuration
@Import(value = {
        XXXConfig.class,
        YYYConfig.class
})
@ImportResource(value = {
        "classpath:knight.xml",
        "classpath:cdplayer.xml"
})
public class AppConfiguration {}

XML拆分配置

如果只有一个XML文件,那么在大项目中,它将很快失控。

import标签可以导入其他xml。

<import resource="knights.xml"/>

bean标签可以将其他JavaConfig导入到XML中

<bean class="io.lilpig.springlearn.springlearn01.chapter02.javaconfig.CDPlayerConfiguration"></bean>

然后直接引用其中的Bean。

而且你也可以创建一个父级XML专门用于整合各种其他的XML和JavaConfig而不创建Bean。

posted @ 2021-09-01 10:32  yudoge  阅读(57)  评论(0编辑  收藏  举报