Spring学习笔记之自动化装配Bean

Easter79
• 阅读 156

自建博客地址:https://bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇

本文作者: Jeffrey
本文链接: https://bytelife.net/articles/33415.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!


在Spring中可以使用Java代码、XML和自动化装配三种方式来装配Bean。从便利性角度来说,最强大的还是Spring的自动化配置,如果Spring能够进行自动化装配的话,那何苦还要显式的将这些Bean装配在一起呢? Spring从两个角度来实现自动化装配:

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

为了阐述组件扫描和装配,我们需要创建几个Bean,它们代表了一个音响系统中的组件。

一、创建可被发现的bean

定义CD的一个接口:

package cn.javacodes.spring.beans.soundsystem;
public interface CompactDisc {
    void play();
}

CompactDisc接口定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。 下面创建一个CompactDisc的实现:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.stereotype.Component;
@Component
public class Transfer implements CompactDisc {
    private String title = "transfer";
    private String artist = "周传雄/小刚";
    public void play() {
        System.out.println("正在播放"+artist+"的专辑:" + title);
    }
}

这里需要注意的是该类使用了@Component注解,表明该类会作为组件类,并告知Spring要为这个组件创建bean。但是在这之前,由于默认组件扫描是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。 下面的这个类展现了完成这件事情的最简介配置方式:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于cn.javacodes.spring.beans.soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解标示的类,并在Spring中自动为其创建一个bean。 当然,如果你更加倾向于使用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:configurator="http://www.springframework.org/schema/c"
       xmlns:avalon="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="cn.javacodes.spring.beans.soundsystem"/>
</beans>

尽管我们可以使用XML的方案来启用组件扫描,但在后面的讨论中,更多的还是会使用基于Java的配置。 下面我们创建一个简单的JUnit测试,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来了。

package cn.javacodes.spring.beans.soundsystem;
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 static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;
    @Test
    public void cdShouldNotBeNull(){
        assertNotNull(cd);
    }
}

该类使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的上下文。注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan注解,因此最终的应用上下文中应该包含CompactDisc bean。 为了证明这一点,在测试代码中有一个CompactDisc属性,并且这个属性带有@Autowired注解,以便于将CompactDisc bean注入到测试代码中,有关与@Autowired注解的更多内容将在后面讲述。最后,有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到了测试代码中。 这个代码应该能够通过测试,并以测试成功的颜色显示。

二、为组件扫描的bean命名

Spring上下文中所有的bean都有一个id。在前面的例子中,即使我们并没有明确的给定Transfer bean一个id,但Spring会根据类名为其给定一个id。具体来讲,Spring会默认给定一个将类名首字母变为小写的id,例如上例中将给定的id为transfer。 如果想为这个bean给定不同的id,你需要做的就是将你所想要给定的id作为参数传递给@Component注解。例如:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.stereotype.Component;
@Component("transfer")
public class Transfer implements CompactDisc {
    private String title = "transfer";
    private String artist = "周传雄/小刚";
    public void play() {
        System.out.println("正在播放"+artist+"的专辑:" + title);
    }
}

还有另外一种为bean命名的方式,使用Java依赖注入规范中提供的@Named注解来为bean设置id:

package cn.javacodes.spring.beans.soundsystem;
import javax.inject.Named;
@Named("transfer")
public class Transfer implements CompactDisc {
    private String title = "transfer";
    private String artist = "周传雄/小刚";
    public void play() {
        System.out.println("正在播放"+artist+"的专辑:" + title);
    }
}

Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差别,不过大多数场景种它们使可以相互替换的。但是推荐使用@Component而不是@Named,因为@Component注解看起来更加能够知道它是干什么的。

三、设置组件扫描的基础包

现在我们已经知道,默认情况下@ComponentScan注解会扫描当前配置类所在的包及其子包,但我们可能更希望将配置类与其它类放在不同的包中,那么为了指定不同的基础包,可以将指定的包名作为参数传递给@ComponentScan注解即可:

@Configuration
@ComponentScan("cn.javacodes.spring.beans.soundsystem")
public class CDPlayerConfig {
}

当然也可以更加清晰的指明其是基础包,使用basePackages属性:

@Configuration
@ComponentScan(basePackages = "cn.javacodes.spring.beans.soundsystem")
public class CDPlayerConfig {
}

这里我们发现basePackages属性是复数形式,我们猜测它是否可以指定多个基础包呢?答案是正确的,如果想要指定多个包,那么只需要将要扫描的包放到一个数组中即可:

@Configuration
@ComponentScan(basePackages = {"cn.javacodes.spring.beans.soundsystem", "cn.javacodes.spring.beans.video"})
public class CDPlayerConfig {
}

上面的方式中,包名以简单的字符串进行表示,当然这是可以的。但是如果我们日后对代码进行重构,很有可能就会出现问题,所以这种通过简单的字符串来配置基础包的方式是不安全的。为了解决这个问题,我们可以将其指定为包中所包含的类或接口:

package cn.javacodes.spring.configuration;
import cn.javacodes.spring.beans.soundsystem.CDPlayer;
import cn.javacodes.spring.beans.video.DVDPlayer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}

注意:这里不再使用basePackages属性,取而代之的是basePackageClasses属性。我们不再使用String类型的包名来指定包,而是为basePackageClasses属性设置的数组中包含了类。这些类所在的包会作为组件扫描的基础包。 当然,使用组件类直接给basePackageClasses属性并不是很好的方式,我们可以考虑在包中创建一个用来进行扫描的空标记接口。通过标记接口的方式,你依然能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码。

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

简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其它bean。为了声明要进行自动装配,我们可以考虑使用Spring的@Autowired注解。 比如下面的CDPlayer类,它的构造器使用了@Autowired注解,表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并会传入一个可以设置给CompactDisc类型的bean:

package cn.javacodes.spring.beans.soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer {
    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){
        this.cd = cd;
    }

在Spring完成初始化bean之后,它会尽可能的去满足bean的依赖。实际上,Setter方法并没有什么特殊之处,@Autowired可以出现在任何方法上。 假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被封装起来。 如果没有匹配的bean,那么在应用上下文创建的时候,Spring将会抛出一个异常。为了避免异常,可以将@Autowired的required属性设置为false,Spring会尝试执行自动匹配,但是如果没有匹配的bean的话,Spring会让这个bean处于未装配的状态:

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

但是,把required属性设置为false的时候你需要注意,如果你的代码中没有null检查的话,这个处于未装配状态的属性有可能会出现空指针异常(NullPointerException)。 如果有多个bean都能满足依赖关系的话,Spring会抛出一个异常,表明没有明确指定要选择哪个bean进行装配,有关于Spring自动化装配的歧义性的问题,我会在后续的文章中进行说明。 @Autowired是Spring特有的注解,如果你不希望在代码中到处使用Spring特有的注解的话,那么可以考虑使用@Inject注解对其进行替换,例如:

package cn.javacodes.spring.beans.soundsystem;
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class CDPlayer {
    private CompactDisc cd;
    @Inject
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }
    public void play(){
        cd.play();
    }
}

@Inject注解来源于Java依赖注入规范,同@Named注解一样,@Inject注解与@Autowired注解存在一些细微的差别,但大多数情况下它们可以进行相互替换。

五、验证自动装配

我们修改一下测试类CDPlayerTest,使其能够借助CDPlayer bean播放CD:

package cn.javacodes.spring.beans.soundsystem;
import cn.javacodes.spring.beans.MediaPlayer;
import cn.javacodes.spring.configuration.CDPlayerConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    @Rule
    public final StandardOutputStreamLog log = new StandardOutputStreamLog();
    @Autowired
    private MediaPlayer player;
    @Autowired
    private CompactDisc cd;
    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }
    @Test
    public void play() {
        player.play();
        assertEquals("正在播放周传雄/小刚的专辑:transfern", log.getLog());
    }
}

该类中,除了注入CompactDisc,还将CDPlayer bean注入到了测试代码中(更为通用的MediaPlayer类型)。在play()方法中,我们可以调用CDPlayer的play()方法并断言它的行为与你的预期是否一致。 自动化装配Bean还有更多的细节,我会在后续的文章中进行阐述。

--Posted from Rpc

点赞
收藏
评论区
推荐文章
技术小男生 技术小男生
4个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
4个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
小森森 小森森
4个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Easter79 Easter79
1年前
Spring学习笔记之通过Java代码装配Bean
自建博客地址:https://bytelife.net(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fbytelife.net),欢迎访问!本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇本文作者:Jeffrey(https://www.oschina
helloworld_34035044 helloworld_34035044
7个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue