读书笔记:Spring in action 第2章

2.1Spring配置的可选方案

  • 在XML中进行显示配置
  • 在Java中进行显示配置
  • 隐式的bean发现机制和自动装配

在很多场景下, 选择哪种方案很大程度上就是个人喜好的问题,你尽可以选择自己最喜欢的方式。

即便如此,我们还是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfg中没有同样的实现时,才应该使用XML。

2.2自动化装配bean

Spring从两个角度来实现自动化装配

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

组件扫描和自动装配组合到一起就能发挥出强大的威力,它们能够将你的显示配置降低到最少。

2.2.1创建可被发现的bean

书中以CD及CD播放器为例:
程序清单2.1 CompactDisc接口在Java中定义了CD的概念

package soundsystem;
public interface CompactDisc (
void play();

CompactDisc的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。

我们还需要一个CompactDisc的实现, 实际上,我们可以有CompactDisc接口的多个实现。在本例中,我们首先会创建其中的一个实现,也就是程序清单2.2所示的SgtPeppers类。

程序清单2.2带有@Component注解的CompactDisc实现类SgtPeppers
package soundsystem;
import org.springframework.stereotype .Component;

@Component
public class SgtPeppers implements CompactDisc {

private string title = "Sgt.Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";

public void play() (
	System.out.printin(“Playing” + title + "by" + artist);
	}
}

和CompactDisc接口一样,SgtPeppers的具体内容并不重要,我们只需要注意SgtPeppers类使用了@Component注解,这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
组件扫描默认是不启动的,我们需要显示配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean,程序清单2.3的配置类展现了完成这项任务的最简洁配置。

程序清单2.3 @ComponentScan注解 后用了组件扫描
package soundsystem;
import org.springframework.context.annotation.componentscan;
import org.springframework.context.annotation.Configuration;

@Configuration
@Componentscan
public class CDPlayerConfig () {
}

类CDPlayerConfig通过Java代码定义了Spring的装配规则。CDPlayerConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。

如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。

我们也可以使用XML来启用组件扫描,使用Spring context命名空间的< context :component-scan > 元素。程序清单2.4展示了启用组件扫描的最简洁XML配置。

程序清单2.4通过XML启用组件扫描
<?xcml versions = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://ww.springframework.org/schema/beang"
  xmns:xsi=http://ww.w3. org/2001/XMLSchena- 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 = "soundsystem"/>
</beans>

为了测试组件扫描的功能,我们创建一一个简单的JUnit测试,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来了。程序清单2.5中的CDPlayerTest就是用来完成这项任务的。

程序清单2.5测试组件扫描能够发现CompactDisc
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annocation.Aucowired;
import org.springframework.test.contex.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUni t4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContexConfigurationt(classes=CDPlayerConfig.class)
public class CDPlayerTest {
  @Autowired
  private CompactDisc cd;
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd) ;
  }
}

CDPlayerTest使用 了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration会告诉它需要

在CDP1ayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan,因此最终的应用上下文中应该包含CompactDiscbean。

为了证明这一点, 在测试代码中有一个CompactDisc类型的属性,并且这个属性带有@Autowired注解,以便于将CompactDiscbean注入到测试代码之中(稍后,我会讨论@Autowired)。最后,会有一个简单的测试方法断言cd属性不为null.如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。

现在,我们会更加深入地探讨@ComponentScan和@Component,看一下使用组件扫描还能做些什么。

2.2.2 为组件扫描的bean命名

如果想为这个bean设置不同的D,你所要做的就是将期望的ID作为值传递给@Component注解。比如说,如果想将这个bean标识为lonelyHeartsClub,那么你需要将SgtPeppers类的@Component注解配置为如下所示:

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

还有另外一种为bean命名的方式这种方式不使用@Component主解,而是使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID:

package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class Sgtpeppers implements CompactDisc {
	...
}

Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差异, 但是在大多数场景中,它们是可以互相替换的。

2.2.3设置组件扫描的基础包

到现在为止,我们没有为@ComponentScan设置任何属性。这意味着,按照默认规则,它会以配置类所在的包作为基础包(base package)来扫描组件。但是,如果你想扫描不同的包,那该怎么办呢?或者如果你想扫描多个基础包,那又该怎么办呢?

为了指定不同的基础包,你所需要做的就是在@ComponentScan的value属性中指明包的名称:

@Configuration
@ComponentScan ("soundsysten")
public class CDPlayerConfig {}

如果你想更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:

@Configuration
0ComponentScan (bascPackages = "soundsystem" )
public class CDPlayerConfig {}

可以设置多个基础包,如果想要这么做的话,只需要将basePackages属性设置为要扫描包的一个数组即可:

@Configuration
@ComponentScan (basePackages={"soundsystem, "video" })
public class CDPlayerConfig {}

在上面的例子中,所设置的基础包是以String类型表示的。这种方法是类型不安全(not type-safe)的。如果你重构代码的话,那么所指定的基础包可能就会出现错误了。

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

@Configuration
@Componentscan (basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}

可以看到,basePackages属性被替换成了basePackageClasses.同时,我们不是再使用String类型的名称来指定包为basePackageClasses属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包。

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

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

简单来说,自动装配就是Spring自动满足bean依赖的一种方法在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配我们可以借助Spring的@Autowired注解。

比方说,考虑程序清单2.6中的CDPlayer类。它的构造器上添加了@Autowired注解,这表明当Spring创建CDPlayerbean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。

程序清单2.6通过自动装配,将一个CompactDisc注入到CDPlayer之中
package soundsystem;
import.org.springframework.beans.factory.annotation.Autowired;
import.org.springframework.stereotype.Component;

@Component
publlc 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;
}

在Spring初始化bean之后,它会尽可能得去满足bean的依赖,在本例中,依赖是通过带有@Autowired注解的方法进行声明的,也就是setCompactDisc ()。
实际上,Setter方法并没有什么特殊之处。@Autowi red注解可以用在类的任何方法上。假设CDPlayer类有一个insertDisc()方法,那么@Autowired能够像。在setCompactDisc()上那样,发挥完全相同的作用:

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

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:

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

将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是,把required属性设置为false时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException。

如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。在第3章中,我们会进一步讨论自动装配中的歧义性。

@Autowired是Spring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换为@Inject:

package soundsystem:
import.javax.inject.Inject;
import.javax.inject.Named;

@Named
public class CDPlayer {
  ...
  @Inject
  public CDPlayer (CompactDisc cd) {
    this.cd = cd;
  }
  ...
}

@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named注解。在自动装配中,Spring同时支持@Inject和@Autowired。尽管@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。

2.2.5 验证自动装配要

现在,我们已经在CDP1ayer的构造器中添加了@Autowired注解,Spring将把个可分配给CompactDisc类型的bean自动注入进来。为了验证这一点,让我们修改一下CDPlayerTest,使其能够借助CDPlayer bean播放CD:

package soundsystem;
import static org. junit.Assert.*;
import org.Junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardoutputsereamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframowork.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJunit4ClassRunner

@RunWith(SpringJUnit4classRunner.class)
@ContextConfiguratlon(classes = CDPlayerConfig.class)
public class CDPlayorTest {
  @Rule
  public final StandardOutputStreamLog 1og = new StandardOutputStreamLog();

  @Autowired
  private MediaPlayer player;

  @Autowired
  private CompactDisc cd;

  @Test
  public vold cdShouldNotBeNull() {
    assertNotNull(cd);
  }
  
  @Test
  public vold play() {
    player.play();
    assertEquals (
  		"Playing Sgt. Pepper's Lonely Hearts Club Band"+
		"by The Beatles\n",
    	log.getLog());
  }
}

现在,除了注入CompactDisc,我们还将CDPlayerbean注入到测试代码的player成员变量之中(它是更为通用的MediaPlayer类型)。在play()测试方法中我们可以调用CDPlayer的play()方法,并断言它的行为与你的预期一致。

在测试代码中使用System.out.println ()是稍微有点棘手的事情。因此,该样例中使用了StandardOutputStreamLog,这是来源于System Rules库 (htp://stefanbirkner.github.io/system-rules/index.html)的一个JUnit规则,该规则能够基于控制台的输出编写断言。在这里,我们断言SgtPeppers.play()方法的输出被发送到了控制台上。
现在,你已经了解了组件扫描和自动装配的基础知识,在第3章中,当我们介绍如何处理自动装配的歧义性时,还会继续研究组件扫描。

2.3通过Java代码装配bean

有时候自动化配置的方案行不通因此需要明确配置Spring。比如说你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。在这种情况下,你必须要采用显式装配的方式。在进行显式配置的时候,有两种可选方案Java和XML。

进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样 都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。

2.3.1 创建配置类

继续以CD做样例:

package soundsystem;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig() {
}

创建JavaConfig类的关键在于为其添加@Configuration注解,@Conficurati on注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。为了进行显示配置我们移除了@ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。

2.3.2 声明简单的bean

要在JavaConfg中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加Bean注解。比方说,下面的代码声明了CompactDisc bean:

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

@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。

默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers。如果想为其设置成一个不同的名字的话,那么可以重命名该方法或者通过name属性指定一个不同的名字:

@Bean(name = "lonelyHeartsClubBand")
	return new sgtPeppers();
}

不管你采用什么方法来为bean命名,bean声明都是非常简单的。方法本返回了一个新的SgtPeppers实例。这里是使用Java来进行描述的,因此我们可以发挥Java提供的所有功能只要最终生成一个CompactDisc实例即可。

请稍微发挥一下你的想象力,我们可能希望做一点稍微疯狂的事情,比如说,在一组CD中机选择一个CompactDisc来播放;

@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 {
		return new Revolver();
	}
}

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

2.3.3 借助JavaConfig实现注入

我们前面所声明的CompactDisc bean是非常简单的,它自身没有其他的依赖。但现在,我需要声明CDPlayerbean, 它依赖于CompactDisc。在JavaConfig中,要如何将它们装配在起呢?

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

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

cdPlayer ()方法像sgtPeppers()方法一 样,同样使用 了Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所创建的bean ID为cdPlayer,与方法的名字相同。

cdPlayer ()的方法体与sgtPeppers()稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例。

看起来CompactDisc是通过调用sgtPeppers ()得到的,但情况并非完全如此。因
为sgtPeppers ()方法上添加了Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。

比如说,假设你引入了一个其他的CDplayerbean,它和之前的那个bean完全一样:

@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。因此两个CDPlayerbean会得到相同的SgtPeppers实例。

可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:

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

在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer ()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。

通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDi sc必须要在JavaConig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传人到配置方法中并用来创建CDpIayer bean。

另外,需要提醒的是,我们在这里使用CDPlayer的构造器实现了功能 但是我们完全可以采用其他风格的DI配置。比如说如果你想通过Setter方法注入CompactD isc的话,那么代码看起来应该是这样的

@Bean
public CDPlayer cdPlayer (CompactDisc compactDisc) {
	CDPlayer cdPlayer = new CDPlayer (compactDisc);
	cdplayer.setCompactDisc (compactDisc);
	return cdPlayer;
}

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

2.4通过XML装配bean

2.4.1 创建XML配置规范

在XML配置中,要创建一个XML文件,并且要以元素为根。

最为简单的Spring XMI配置如下所示

<?xml version="1.0" encoding="UTE-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.W3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context">

<!-- configuration details go here -->

</beans>

很容易就能看出来,这个基本的XMI配置已经比同等功能的JavaConfig类复杂得多了。作为起步,在JavaConfig中所需要的只是@Configuration,但在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素,

2.4.2 声明一个简单的

要在基于XMI的Spring配置中声明一个bean, 我们要使用spr ing-beans模式中的另外一个元素:。元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDiscbean:

<bean class='soundsystem.SgtPeppers" />

因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。在本例中,bean的ID将会是“soundsystem SgtPeppers#0”。如果你声明了另外一个SgtPeppers,并且没有明确进行标识,那么它自动得到的ID将会是“soundsystem. SgtPeppers#1”。
更好的办法是借助id属性,为每个bean设置一个你自己选择的名字:

<bean id="compactDisc" class= "soundsystem.SgtPeppers" />

稍后将这个bean装配到CDPlayer bean之中的时候,你会用到这个具体的名字。

2.4.3 借助构造器注入初始化bean

未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值