Spring实战2:装配bean—依赖注入的本质

主要内容

  • Spring的配置方法概览
  • 自动装配bean
  • 基于Java配置文件装配bean
  • 控制bean的创建和销毁

任何一个成功的应用都是由多个为了实现某个业务目标而相互协作的组件构成的,这些组件必须相互了解、能够相互协作完成工作。例如,在一个在线购物系统中,订单管理组件需要与产品管理组件以及信用卡认证组件协作;这些组件还需要跟数据库组件协作从而进行数据库读写操作。

在Spring应用中,对象无需自己负责查找或者创建与其关联的其他对象,由容器负责将创建各个对象,并创建各个对象之间的依赖关系。例如,一个订单管理组件需要使用信用卡认证组件,它不需要自己创建信用卡认证组件,只需要定义它需要使用信用卡认证组件即可,容器会创建信用卡认证组件然后将该组件的引用注入给订单管理组件。

创建各个对象之间协作关系的行为通常被称为装配(wiring),这就是依赖注入(DI)的本质。

2.1 Spring的配置方法概览

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

  • 基于XML文件的显式装配
  • 基于Java文件的显式装配
  • 隐式bean发现机制和自动装配

绝大多数情况下,开发人员可以根据个人品味选择这三种装配方式中的一种。Spring也支持在同一个项目中混合使用不同的装配方式。

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

2.2 自动装配bean

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

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

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

2.2.1 创建可发现的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;

@Component
public 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 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/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="com.spring.sample.soundsystem" />
</beans>

在这个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中。

2.2.2 给被扫描的bean命名

在Spring上下文中,每个bean都有自己的ID。在上一个小节的例子中并没有提到这一点,但Spring在扫描到SgtPeppers这个组件并创建对应的bean时,默认给它设置的ID为sgtPeppers——是的,这个ID就是将类名称的首字母小写。

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

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

2.2.3 设置需要扫描的目标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各自所在的目录作为扫描机制的目标根目录。

如果应用中的对象是孤立的,并且互相之间没有依赖关系,例如SgtPeppersbean,那么这就够了。

2.2.4 自动装配bean

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

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

package com.spring.sample.soundsystem;

import org.springframework.beans.factory.annotation.Autowired;

@Component
public class CDPlayer implements MediaPlayer {
    private CompactDisc cd;

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

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

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

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

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

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

@Autowired
private CompactDisc cd;

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

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

2.2.5 验证自动配置

通过下列代码,可以验证: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();
    }
}

2.3 基于Java配置文件装配bean

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

2.3.1 创建配置类

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

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

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

2.3.2 定义bean

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

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

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

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

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

@Bean
public 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();
    }
}

2.3.3 JavaConfig中的属性注入

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

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

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

假如有下列代码:

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

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

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

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

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

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

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

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

2.4 基于XML配置文件装配bean

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

2.5 混合使用多种配置方法

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

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

2.5.1 在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 {
    }

2.5.2 在XML配置文件中应用JavaConfig

  • 通过<import>标签引入其他的XML配置文件;
  • 通过<bean>标签导入Java配置文件到XML配置文件,例如
    <bean class="soundsystem.CDConfig" />

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

2.6 总结

这一章中学习了Spring 装配bean的三种方式:自动装配、基于Java文件装配和基于XML文件装配。

由于自动装配几乎不需要手动定义bean,建议优先选择自动装配;如何必须使用显式配置,则优先选择基于Java文件装配这种方式,因为相比于XML文件,Java文件具备更多的能力、类型安全等特点;但是也有一种情况必须使用XML配置文件,即你需要使用某个名字空间(name space),该名字空间只在XML文件中可以使用。

参考资料

  1. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html
时间: 2024-11-08 19:05:56

Spring实战2:装配bean—依赖注入的本质的相关文章

【Spring实战】—— 5 设值注入

本篇主要讲解了Spring的最常用的功能--依赖注入. 注入的方式,是使用Getter Setter注入,平时大多的编程也都是使用这种方法. 举个简单的例子,还是表演者. 表演者有自己的属性,年龄或者表演的歌曲等等.还需要一些复杂的属性,比如乐器,每一种乐器会发出不同的声音. 下面看一下表演者Performer package com.spring.test.action1; public interface Performer { void perform() throws Performan

Spring中管理Bean依赖注入之后和Bean销毁之前的行为

    对于Singleton作用域的Bean,Spring容器将会跟踪它们的生命周期,容器知道何时实例化结束.何时销毁.Spring可以管理Bean在实例化结束之后和Bean销毁之前的行为. Bean依赖关系注入之后的行为:     Spring提供了两种方式在Bean全部属性设置成功后执行特定的行为: 在Spring配置文件中使用init-method属性:这个属性指定某个方法在Bean全部依赖关系设置结束后自动执行.这个方法写在Bean里面.使用这种方法不需要将代码与Spring耦合在一起

【SSH系列】深入浅出spring IOC中三种依赖注入方式

spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则来消减计算机程序的耦合问题,控制反转一般分为两种类型,依赖注入和依赖查找,依赖什么?为什么需要依赖?注入什么?控制什么?依赖注入和控制反转是一样的概念吗?接触新的知识,小编的脑袋中全是大大的问号,不过没有关系,今天这篇博文,小编主要来简单的介绍一下在spring IOC中依赖注入的方法. 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性.我们可以从以下几个方面理解: a.参与者都

解决Spring中singleton的Bean依赖于prototype的Bean的问题

    当Spring容器中作用域不同的Bean相互依赖时,可能出现一些问题,例如:一个作用域为Singleton的Bean(设为A)依赖于一个作用域为prototype的Bean(设为B).由于A是单例的,只有一次初始化的机会,它的依赖关系也只在初始化阶段被设置,但它所依赖的B每次都会创建一个全新的实例,这将使A中的B不能及时得到更新.这样将导致如果客户端多次请求A,并调用A中B的某个方法(或获取A中B的某个属性),服务端总是返回同一个B,但客户端直接请求B却能获得最新的对象,这就产生了对象不

Spring 使用注解装配Bean

Spring可以使用xml配置文件来装配bean,也可以使用注解来装配Bean 1.在上一篇文章的基础上在com.springtest包中新建Tire类,源码为: package com.springtest; public class Tire { privatedouble price; privateString brand; publicString getBrand() { returnbrand; } publicvoid setBrand(String brand) { this.

基于.NET平台的分层架构实战(六)—依赖注入机制及IoC的设计

我们设计的分层架构,层与层之间应该是松散耦合的.因为是单向单一调用, 所以,这里的"松散耦合"实际是指上层类不能具体依赖于下层类, 而应该依赖于下层提供的一个接口.这样,上层类不能直接实例化下层中的类, 而只持有接口,至于接口所指变量最终究竟是哪一个类,则由依赖注入机制决定 . 之所以这样做,是为了实现层与层之间的"可替换"式设计 ,例如,现在需要换一种方式实现数据访问层,只要这个实现遵循了前面定义的 数据访问层接口,业务逻辑层和表示层不需要做任何改动,只需要改一下

Spring的控制反转和依赖注入

Spring的官网:https://spring.io/  Struts与Hibernate可以做什么事? Struts, Mvc中控制层解决方案 可以进行请求数据自动封装.类型转换.文件上传.效验- Hibernate, 持久层的解决方案: 可以做到, 把对象保存到数据库, 从数据库中取出的是对象. 传统的开发模式 基于mvc模式进行项目开发: 基于mvc的项目框架结构: Entity / dao / service / action 为什么引入Spring:  思考:     1. 对象创建

Spring实战3:装配bean的进阶知识

主要内容: Environments and profiles Conditional bean declaration 处理自动装配的歧义 bean的作用域 The Spring Expression Language 在装配bean-依赖注入的本质一文中,我们探讨了Spring的三种管理bean的方式:自动装配.基于JavaConfig.基于XML文件.这篇文字将探讨一些Spring中关于bean的管理的高级知识,这些技能你可能不会每天都用,但是非常重要. 3.1 Environments

深入解析Java的Spring框架中bean的依赖注入_java

每一个基于java的应用程序都有一个共同工作来展示给用户看到的内容作为工作的应用几个对象.当编写一个复杂的Java应用程序,应用程序类应该尽可能独立其他Java类来增加重复使用这些类,并独立于其他类别的测试它们,而这样做单元测试的可能性.依赖注入(或有时称为布线)有助于粘合这些类在一起,同时保持他们的独立. 考虑有其中有一个文本编辑器组件的应用程序,要提供拼写检查.标准的代码将看起来像这样:   public class TextEditor { private SpellChecker spe