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

Easter79
• 阅读 283

自建博客地址: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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Easter79 Easter79
2年前
Swift项目兼容Objective
!456.jpg(http://static.oschina.net/uploads/img/201509/13172704_1KcG.jpg"1433497731426906.jpg")本文是投稿文章,作者:一叶(博客(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2F00red
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Easter79 Easter79
2年前
Spring学习笔记之通过Java代码装配Bean
自建博客地址:https://bytelife.net(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fbytelife.net),欢迎访问!本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇本文作者:Jeffrey(https://www.oschina
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k