《spring实战》学习笔记-第二章:装配bean

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《spring实战》学习笔记-第二章:装配bean相关的知识,希望对你有一定的参考价值。

2.1 Spring配置的可选方案
  当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
  (1)在XML中进行显式配置。
  (2)在Java中进行显式配置。
  (3)隐式的bean发现机制和自动装配。
  建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
2.2 自动化装配bean
  Spring从两个角度来实现自动化装配:
  组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
  自动装配(autowiring):Spring自动满足bean之间的依赖。
  组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。

2.2.1 创建可被发现的bean
  如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实是没有太大用处的。所以,可以这样说,CD播放器依赖于CD才能完成它的使命。为了在Spring中阐述这个例子,让我们首先在Java中建立CD的概念。
代码清单1:CompactDisc接口在Java中定义了CD的概念

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

  CompactDisc的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
代码清单2:带有@Component注解的CompactDisc实现类SgtPeppers

package spring.soundsystem;

import org.springframework.stereotype.Component;
@Component()
public class SgtPeppers implements CompactDisc {
    private String title="《天下》";
    private String artist="张杰";

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

  @Component注解表明该类会作为组件类,不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。程序清单3的配置类展现了完成这项任务的最简洁配置。
代码清单3:@ComponentScan注解启用了组件扫描

package spring.soundsystem;

import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class CDPlayerConfig { }

  @ComponentScan默认会扫描与配置类相同的包以及这个包下的所有子包,查找带有@Component注解的类,这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。
  如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的<context:component-scan>元素。代码清单4展示了启用组件扫描的最简洁XML配置。
代码清单4:通过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="spring.soundsystem"/>
</beans>

代码清单5:测试组件扫描能够发现CompactDisc

package spring.soundsystem.test;

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 spring.soundsystem.CDPlayerConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
//@ContextConfiguration(locations="classpath:applicationcontext.xml") 
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;
    @Test
    public void play(){
        cd.play();
    }
}

  CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。
2.2.2 为组件扫描的bean命名
  Spring应用上下文中所有的bean都会给定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。
  如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component注解。例如命名为lonelyHeartsClub:

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

2.2.3 设置组件扫描的基础包
  我们没有为@ComponentScan设置任何属性。这意味着,按照默认规则,它会以配置类所在的包作为基础包(base package)来扫描组件.。以下方式可指定不同的包:

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

  或者通过basePackages熟悉进行设置,这种方式可以设置多个基础包:

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

  除了将包设置为简单的String类型之外,@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:

@Configuration
@ComponentScan(basePackageClasses= {SgtPeppers.class})
public class CDPlayerConfig {   
}

  在你的应用程序中,如果所有的对象都是独立的,彼此之间没有任何依赖,就像SgtPeppersbean这样,那么你所需要的可能就是组件扫描而已。但是,很多对象会依赖其他的对象才能完成任务。这样的话,我们就需要有一种方法能够将组件扫描得到的bean和它们的依赖装配在一起。要完成这项任务,我们需要了解一下Spring自动化配置的另外一方面内容,那就是自动装配。

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

  为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
代码清单6:通过自动装配,将一个CompactDisc注入到CDPlayer之中

package spring.soundsystem;

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

@Component
public class CDPlayer implements MediaPlayer {
    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;
 }

  实际上,Setter方法并没有什么特殊之处。@Autowired注解可以用在类的任何方法上。假设CDPlayer类有一个insertDisc()方法,那么@Autowired能够像在setCompactDisc()上那样,发挥完全相同的作用:

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

  如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:

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

  将required属性设置为false时,如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是,如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException。
  如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
2.2.5 验证自动装配
  现在,我们已经在CDPlayer的构造器中添加了@Autowired注解,Spring将把一个可分配给CompactDisc类型的bean自动注入进来。为了验证这一点,让我们修改一下CDPlayerTest,使其能够借助CDPlayer bean播放CD:

package spring.soundsystem.test;

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 spring.soundsystem.CDPlayerConfig;
import spring.soundsystem.MediaPlayer;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
// @ContextConfiguration(locations="classpath:applicationcontext.xml")
public class CDPlayerTest {
    @Autowired
    private MediaPlayer player;

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

2.3 通过Java代码装配bean
  尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。
  在进行显式配置的时候,有两种可选方案:Java和XML。就像我之前所说的,在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。
2.3.1 创建配置类

package spring.soundsystem;

import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {
}

  创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。到此为止,我们都是依赖组件扫描来发现Spring应该创建的bean。尽管我们可以同时使用组件扫描和显式配置,但是在本节中,我们更加关注于显式配置,因此我将CDPlayerConfig的@ComponentScan注解移除掉了
  移除了@ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。
2.3.2 声明简单的bean
  要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解比方说,下面的代码声明了CompactDisc bean:

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

@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
请稍微发挥一下你的想象力,我们可能希望做一点稍微疯狂的事情,比如说,在一组CD中随机选择一个CompactDisc来播放:

技术分享

  现在,你可以自己想象一下,借助@Bean注解方法的形式,我们该如何发挥出Java的全部威力来产生bean。当你想完之后,我们要回过头来看一下在JavaConfig中,如何将CompactDisc注入到CDPlayer之中。

2.3.3 借助JavaConfig实现注入
  在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如,下面就是一种声明CDPlayer的可行方案:

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

cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所创建的bean ID为cdPlayer,与方法的名字相同。
  默认情况下,Spring中的bean都是单例的,因此,两个CDPlayer bean会得到相同的SgtPeppers实例。如下:

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

  假如对sgtPeppers()的调用就像其他的Java方法调用一样的话,那么每个CDPlayer实例都会有一个自己特有的SgtPeppers实例。如果我们讨论的是实际的CD播放器和CD光盘的话,这么做是有意义的。如果你有两台CD播放器,在物理上并没有办法将同一张CD光盘放到两个CD播放器中。
  但是,在软件领域中,我们完全可以将同一个SgtPeppers实例注入到任意数量的其他bean之中。默认情况下,Spring中的bean都是单例的,我们并没有必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()时所创建的
CompactDiscbean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。
  可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:

  技术分享

  在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
  通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayerbean。

  再次强调一遍,带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。

2.4 通过XML装配bean

2.4.1 创建XML配置规范
  借助Spring Tool Suite创建XML配置文件创建和管理Spring XML配置文件的一种简便方式是使用Spring Tool Suite。在Spring Tool Suite的菜单中,选择File>New>Spring Bean Configuration File,能够创建Spring XML配置文件,并且可以选择可用的配置命名空间。

2.4.2 声明一个简单的<bean>
  <bean>元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDiscbean:

技术分享

 

  在XML配置中,bean的创建显得更加被动,不过,它并没有JavaConfig那样强大,在JavaConfig配置方式中,你可以通过任何可以想象到的方法来创建bean实例。  

  另外一个需要注意到的事情就是,在这个简单的<bean>声明中,我们将bean的类型以字符串的形式设置在了class属性中。谁能保证设置给class属性的值是真正的类呢?Spring的XML配置并不能从编译期的类型检查中受益。即便它所引用的是实际的类型,如果你重命名了类,会发生什么呢?

  以上介绍的只是JavaConfig要优于XML配置的部分原因。我建议在为你的应用选择配置风格时,要记住XML配置的这些缺点。

2.4.3 借助构造器注入初始化bean
  在Spring XML配置中,只有一种声明bean的方式:使用<bean>元素并指定class属性。Spring会从这里获取必要的信息来创建bean。
  但是,在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:
  (1)<constructor-arg>元素
  (2)使用Spring 3.0所引入的c-命名空间
  两者的区别在很大程度就是是否冗长烦琐。可以看到,<constructor-arg>元素比使用c-命名空间会更加冗长,从而导致XML更加难以读懂。另外,有些事情<constructorarg>可以做到,但是使用c-命名空间却无法实现。
构造器注入bean引用
  现在已经声明了SgtPeppers bean,并且SgtPeppers类实现了CompactDisc接口,所以实际上我们已经有了一个可以注入到CDPlayerbean中的bean。我们所需要做的就是在XML中声明CDPlayer并通过ID引用SgtPeppers:

技术分享

  在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了,如下所示:

技术分享

  c-命名空间是在Spring 3.0中引入的,它是在XML中更为简洁地描述构造器参数的方式。要使用它的话,必须要在XML的顶部声明其模式。下图描述了这个属性名是如何组合而成的。

技术分享

  迄今为止,我们所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——而有时候,我们需要做的只是用一个字面量值来配置对象。为了阐述这一点,假设你要创建CompactDisc的一个新实现,如下所示:

技术分享

  现在,我们可以将已有的SgtPeppers替换为这个类:现在,我们可以将已有的SgtPeppers替换为这个类:

技术分享

  我们再次使用<constructor-arg>元素进行构造器参数的注入。但是这一次我们没有使用“ref”属性来引用其他的bean,而是使用了value属性。

装配集合
  到现在为止,我们假设CompactDisc在定义时只包含了唱片名称和艺术家的名字。如果现实世界中的CD也是这样的话,那么在技术上就不会任何的进展。CD之所以值得购买是因为它上面所承载的音乐。大多数的CD都会包含十多个磁道,每个磁道上包含一首歌。如果使用CompactDisc为真正的CD建模,那么它也应该有磁道列表的概念。请考虑下面这个新的BlankDisc:

技术分享

  我们可以有多个可选方案。首先,可以使用<list>元素将其声明为一个列表:我们可以有多个可选方案。首先,可以使用<list>元素将其声明为一个列表:

技术分享

  与之类似,我们也可以使用<ref>元素替代<value>,实现bean引用列表的装配。例如,假设你有一个Discography类,它的构造器如下所示:

技术分享

  那么,你可以采取如下的方式配置Discography bean:

技术分享

  当构造器参数的类型是java.util.List时,使用<list>元素是合情合理的。尽管如此,我们也可以按照同样的方式使用<set>元素:

技术分享

  <set>和<list>元素的区别不大,其中最重要的不同在于当Spring创建要装配的集合时,所创建的是java.util.Set还是java.util.List。如果是Set的话,所有重复的值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况下,<set>或<list>都可以用来装配List、Set甚至数组。

2.4.4 设置属性
  到目前为止,CDPlayer和BlankDisc类完全是通过构造器注入的,没有使用属性的Setter方法。接下来,我们就看一下如何使用Spring XML实现属性注入。假设属性注入的CDPlayer如下所示:

技术分享

  该选择构造器注入还是属性注入(set)呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。

技术分享

  <property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。在本例中,它引用了ID为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法)。

2.5 导入和混合配置
  可以使用@Import将两个配置类组合在一起。
2.6 小结
  Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
装配bean的三种主要方式:自动化配置、基于Java的显式配置以及基于XML的显式配置。不管你采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。
我同时建议尽可能使用自动化配置,以避免显式配置所带来的维护成本。但是,如果你确实需要显式配置Spring的话,应该优先选择基于Java的配置,它比基于XML的配置更加强大、类型安全并且易于重构。

  本文代码链接:https://github.com/Gugibv/spring

以上是关于《spring实战》学习笔记-第二章:装配bean的主要内容,如果未能解决你的问题,请参考以下文章

Spring实战读书笔记Spring装配Bean

Spring实战读书笔记Spring装配Bean

Spring实战笔记二

spring实战第二章小记-装配bean

Spring4实战学习笔记

Spring学习笔记——装配Bean