Spring4 实战笔记(1):装配bean—依赖注入的本质
发布日期:2022-01-11 03:09:58 浏览次数:3 分类:技术文章

本文共 10943 字,大约阅读时间需要 36 分钟。

Spring的配置方法概览

正如在一文中提到的,Spring容器负责创建应用中的bean,并通过DI维护这些bean之间的协作关系。作为开发人员,你应该负责告诉Spring容器需要创建哪些bean以及如何将各个bean装配到一起。Spring提供三种装配bean的方式:

  • 基于XML文件的显式装配
  • 基于Java文件的显式装配
  • 隐式bean发现机制和自动装配
PS:尽可能使用自动装配,越少写显式的配置文件越好;当你必须使用显式配置时(例如,你要配置一个bean,但是该bean的源码不是由你维护),尽可能使用类型安全、功能更强大的基于Java文件的装配方式;最后,在某些情况下只有XML文件中才又你需要使用的名字空间时,再选择使用基于XML文件的装配方式。

自动装配bean

Spring通过两个特性实现自动装配:

  • Component scanning——Spring自动扫描和创建应用上下文中的beans;
  • Autowiring——Spring自动建立bean之间的依赖关系;

这里用一个例子来说明:假设你需要实现一个音响系统,该系统中包含CDPlayer和CompactDisc两个组件,Spring将自动发现这两个bean,并将CompactDisc的引用注入到CDPlayer中。

创建可发现的beans

首先创建CD的概念——CompactDisc接口,如下所示:

package com.spring.sample.soundsystem;public interface CompactDisc {    void play();}

 

CompactDisc接口的作用是将CDPlayer与具体的CD实现解耦合,即面向接口编程。这里还需定义一个具体的CD实现,如下所示:

package com.spring.sample.soundsystem;import org.springframework.stereotype.Component;@Componentpublic class SgtPeppers implements CompactDisc {    private String title = "Sgt. Perppers' Lonely Hearts Club Band";    private String artist = "The Beatles";    public void play() {        System.out.println("Playing " + title + " by " + artist);    }}

这里最重要的是@Component注解,它告诉Spring需要创建SgtPeppers bean。除此之外,还需要启动自动扫描机制,有两种方法:基于XML配置文件;基于Java配置文件,代码如下(二选一):

  • 创建soundsystem.xml配置文件

在这个XML配置文件中,使用<context:component-scan>标签启动Component扫描功能,并可设置base-package属性。

  • 创建Java配置文件
    package com.spring.sample.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration@ComponentScan(basePackages = "com.spring.sample.soundsystem")public class SoundSystemConfig {}

在这个Java配置文件中有两个注解值得注意:@Configuration表示这个.java文件是一个配置文件;@ComponentScan表示开启Component扫描,并且可以设置basePackages属性——Spring将会设置该目录以及子目录下所有被@Component注解修饰的类。

  • 自动配置的另一个关键注解是@Autowired,基于之前的两个类和一个Java配置文件,可以写个测试
  • package com.spring.sample.soundsystem;import com.spring.sample.config.SoundSystemConfig;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)@ContextConfiguration(classes = SoundSystemConfig.class)public class SoundSystemTest {    @Autowired    private CompactDisc cd;    @Test    public void cdShouldNotBeNull() {        Assert.assertNotNull(cd);    }}

运行测试,测试通过,说明@Autowired注解起作用了:自动将扫描机制创建的CompactDisc类型的bean注入到SoundSystemTest这个bean中。

给被扫描的bean命名

如果你需要给某个类对应的bean一个特别的名字,则可以给@Component注解传入指定的参数,例如:

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

pc: @Named 也是一样

设置需要扫描的目标basepackage

在之前的例子中,我们通过给@Component注解传入字符串形式的包路径,来设置需要扫描指定目录下的类并为之创建bean。

可以看出,basePackages是复数,意味着你可以设置多个目标目录,例如:

@Configuration@ComponentScan(basePackages = {"com.spring.sample.soundsystem", "com.spring.sample.video"})public class SoundSystemConfig {}

这种字符串形式的表示虽然可以,但是不具备“类型安全”,因此Spring也提供了更加类型安全的机制,即通过类或者接口来设置扫描机制的目标目录,例如:

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

通过如上设置,会将CDPlayer和DVDPlayer各自所在的目录作为扫描机制的目标根目录。

PC:可以做一个空标记(marker interface)作为扫描使用--这些类所在的包会作为组件扫描的基础包(以及其子包)

自动装配bean

简单得说,自动装配的意思是让Spring从应用上下文中找到对应的bean的引用,并将它们注入到指定的bean。通过@Autowired注解可以完成自动装配。

例如,考虑下面代码中的CDPlayer类,它的构造函数被@Autowired修饰,表明当Spring创建CDPlayer的bean时,会给这个构造函数传入一个CompactDisc的bean对应的引用。

package com.spring.sample.soundsystem;import org.springframework.beans.factory.annotation.Autowired;@Componentpublic class CDPlayer implements MediaPlayer {    private CompactDisc cd;    @Autowired    public CDPlayer(CompactDisc cd) {        this.cd = cd;    }    public void play() {        cd.play();    }}

还有别的实现方法,例如将@Autowired注解作用在setCompactDisc()方法上:

@Autowiredpublic void setCd(CompactDisc cd) {    this.cd = cd;}

或者是其他名字的方法上,例如:

@Autowiredpublic void insertCD(CompactDisc cd) {    this.cd = cd;}

更简单的用法是,可以将@Autowired注解直接作用在成员变量之上,例如:

@Autowiredprivate CompactDisc cd;

只要对应类型的bean有且只有一个,则会自动装配到该属性上。如果没有找到对应的bean,应用会抛出对应的异常,如果想避免抛出这个异常,则需要设置@Autowired(required=false)。不过,在应用程序设计中,应该谨慎设置这个属性,因为这会使得你必须面对NullPointerException的问题。

如果存在多个同一类型的bean,则Spring会抛出异常,表示装配有歧义,解决办法有两个:(1)通过@Qualifier注解指定需要的bean的ID;(2)通过@Resource注解指定注入特定ID的bean;(3)@Primary

验证自动配置

通过下列代码,可以验证:CompactDisc的bean已经注入到CDPlayer的bean中,同时在测试用例中是将CDPlayer的bean注入到当前测试用例。

package com.spring.sample.soundsystem;import com.spring.sample.config.SoundSystemConfig;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SoundSystemConfig.class)public class CDPlayerTest {    public final Logger log = LoggerFactory.getLogger(CDPlayerTest.class);    @Autowired    private MediaPlayer player;    @Test    public void playTest() {        player.play();    }}

基于Java配置文件装配bean

Java配置文件不同于其他用于实现业务逻辑的Java代码,因此不能将Java配置文件业务逻辑代码混在一起。一般都会给Java配置文件新建一个单独的package。

创建配置类

实际上在之前的例子中我们已经实践过基于Java的配置文件,看如下代码:

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

@Configuration注解表示这个类是配置类,之前我们是通过
@ComponentScan注解实现bean的自动扫描和创建,这里我们重点是学习如何显式创建bean,因此首先将
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})这行代码去掉。

定义bean

通过@Bean注解创建一个Spring bean,该bean的默认ID和函数的方法名相同,即sgtPeppers。例如:

@Beanpublic CompactDisc sgtPeppers() {    return new SgtPeppers();}

同样,可以指定bean的ID,例如:

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

可以利用Java语言的表达能力,实现类似工厂模式的代码如下:

@Beanpublic CompactDisc randomBeatlesCD() {    int choice = (int)Math.floor(Math.random() * 4);    if (choice == 0) {        return new SgtPeppers();    } else if (choice == 1) {        return new WhiteAlbum();    } else if (choice == 2) {        return new HardDaysNight();    } else if (choice == 3) {        return new Revolover();    }}

JavaConfig中的属性注入

最简单的办法是将被引用的bean的生成函数传入到构造函数或者set函数中,例如:

@Beanpublic CDPlayer cdPlayer() {    return new CDPlayer(sgtPeppers());}

看起来是函数调用,实际上不是:由于sgtPeppers()方法被
@Bean注解修饰,所以Spring会拦截这个函数调用,并返回之前已经创建好的bean——确保该SgtPeppers bean为单例。

假如有下列代码:

@Beanpublic CDPlayer cdPlayer() {    return new CDPlayer(sgtPeppers());}@Beanpublic CDPlayer anotherCDPlayer() {    return new CDPlayer(sgtPeppers());}

如果把sgtPeppers()方法当作普通Java方法对待,则cdPlayerbean和anotherCDPlayerbean会持有不同的SgtPeppers实例——结合CDPlayer的业务场景看:就相当于将一片CD同时装入两个CD播放机中,显然这不可能。

默认情况下,Spring中所有的bean都是单例模式,因此cdPlayeranotherCDPlayer这俩bean持有相同的SgtPeppers实例。

当然,还有一种更清楚的写法:

@Beanpublic CDPlayer cdPlayer(CompactDisc compactDisc) {    return new CDPlayer(compactDisc);}@Beanpublic CDPlayer anotherCDPlayer() {    return new CDPlayer(sgtPeppers());}

这种情况下,cdPlayeranotherCDPlayer这俩bean持有相同的SgtPeppers实例,该实例的ID为lonelyHeartsClub。这种方法最值得使用,因为它不要求CompactDisc bean在同一个配置文件中定义——只要在应用上下文容器中即可(不管是基于自动扫描发现还是基于XML配置文件定义)。

基于XML配置文件装配bean

这种是Spring中最原始的定义方式,在此不再详述

混合使用多种配置方法

通常,可能在一个Spring项目中同时使用自动配置和显式配置,而且,即使你更喜欢JavaConfig,也有很多场景下更适合使用XML配置。幸运的是,这些配置方法可以混合使用。

首先明确一点:对于自动配置,它从整个容器上下文中查找合适的bean,无论这个bean是来自JavaConfig还是XML配置。

在JavaConfig中解析XML配置

  • 通过@Import注解导入其他的JavaConfig,并且支持同时导入多个配置文件;
  • @Configuration@Import({CDPlayerConfig.class, CDConfig.class})public class SoundSystemConfig {}
  • 通过@ImportResource注解导入XML配置文件;
  • @Configuration@Import(CDPlayerConfig.class)@ImportResource("classpath: cd-config.xml")public class SoundSystemConfig {}

在XML配置文件中应用JavaConfig

  • 通过<import>标签引入其他的XML配置文件;
  • 通过<bean>标签导入Java配置文件到XML配置文件,例如

通常的做法是:无论使用JavaConfig或者XML装配,都要创建一个root configuration,即模块化配置定义;并且在这个配置文件中开启自动扫描机制:<context:component-scan>或者@ComponentScan

工程所需的包Maven:

4.0.0
com.qiqi.test
springmvc4_test
0.0.1-SNAPSHOT
spring新版学习
1.7
javax.servlet
javax.servlet-api
3.1.0
javax.servlet.jsp
jsp-api
2.1
junit
junit
4.12
test
org.springframework
spring-aop
4.2.5.RELEASE
org.springframework
spring-beans
4.2.5.RELEASE
org.springframework
spring-context
4.2.5.RELEASE
org.springframework
spring-test
4.2.5.RELEASE
org.springframework
spring-core
4.2.5.RELEASE
org.springframework
spring-expression
4.2.5.RELEASE
org.springframework
spring-jdbc
4.2.5.RELEASE
org.springframework
spring-tx
4.2.5.RELEASE
org.springframework
spring-web
4.2.5.RELEASE
org.springframework
spring-webmvc
4.2.5.RELEASE
org.springframework
spring-aspects
4.2.5.RELEASE
org.apache.maven.plugins
maven-compiler-plugin
${jdk.version}
${jdk.version}
utf-8

转载地址:https://yuki-ho.blog.csdn.net/article/details/53577089 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring4 实战笔记(2):装配bean的进阶知识
下一篇:Spring 服务启动 自动执行(ApplicationListener)

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年03月05日 00时43分14秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

matlab dwt2(),MATLAB小波变换指令及其功能介绍(超级有用) 2019-04-21
php sequelize,egg.js整合数据库ORM框架Sequelize 2019-04-21
php同时打开2个数据库,thinkphp3.2同时连接两个数据库的简单方法 2019-04-21
centos 开发php扩展,centos 安装php扩展redis 2019-04-21
php+跑buth,php 中函数获取可变参数的方法, 这个语法有点像 golang 语言中的 2019-04-21
cms 单点登录 php,Yii2 中实现单点登录的方法 2019-04-21
oracle自己运行,创建Oracle自动执行Job 2019-04-21
oracle报错00020,oracle启动 ORA-00020: maximum number of processes (%s) exceeded错误 2019-04-21
chmod 赋权所有_chmod 权限 命令详细用法 2019-04-21
html代码翻译_[译]您知道 HTML 的键盘标签吗? 2019-04-21
html抽奖代码_JavaScript高手之路:封装抽奖效果 2019-04-21
hadoop 3.3 一直停留在running wordcount_蛋价持续下跌,今日跌破3.3元大关!深秋季节价格还能反弹吗?... 2019-04-21
的流程图做完后如何保存_2019超火的半永久眉是哪款?做完后我们如何护理?... 2019-04-21
去除logo 高德地图api_深圳品牌logo升级如何保持原型的同时更具创新? 2019-04-21
二重积分转换成极坐标_二重积分转换极坐标r的范围如何确定? 2019-04-21
python中倒背如流_八字基础知识--倒背如流篇 2019-04-21
以太坊地址和公钥_以太坊地址是什么 2019-04-21
linux查看wifi信号命令_linux – 获取WIFI信号强度 – 寻求最佳方式(IOCTL,iwlist(iw)等)... 2019-04-21
npm 不重启 全局安装后_解决修复npm安装全局模块权限的问题 2019-04-21
vs格式化json 不生效_vs code 格式化 json 配置 2019-04-21