创建应用对象之间协作关系的行为通常称为装配(wiring),这也是DI的本质。
我们必须要告诉Spring要创建哪些bean并且如何将其装配在一起。
Spring提供了三种主要的装配机制:
Spring的配置风格是可以互相搭配的,但应尽可能地使用自动配置的机制,因其使用起来要方便的多,显式配置越少越好。显式配置越少,代码就越容易维护。而基于Java和XML相比,JavaConfig的安全性比XML要好,并且功能更强大。
所以我们先来看Spring的自动化配置。
Spring从两个角度来实现自动化装配:
组件扫描和自动装配组合在一起就能使我们的显式配置降低到最少。
为了阐述组件扫描和装配,我们来看这个CD和CD播放器的例子~
首先建立CD的概念,我们先定义CD的一个接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最低。
2.1 : CD接口
package soundsystem;
public interface CompactDisc {
void play();
}
现在,我们创建一个CD的实现,一盘霉霉的《TaylorSwift》CD。
2.2:实现一个《TaylorSwift》的CD
@Component
public class TaylorSwift implements CompactDisc {
private String title = "TaylorSwift";
private String artist = "MeiMei";
public void play() {
System.out.print("正在播放" + artist + "的" + title);
}
}
这个TaylorSwift使用了@Component注解,这个注解表明该类会作为组件类,并告知Spring要为这个TaylorSwift类创建Bean。
我们不必显式配置Bean,因为该类使用了@Component注解,Spring会将事情处理妥当。
但是组件扫描默认是不开启的,想要让Spring命令去寻找带有@Component注解的类,必须先对Spring进行显式配置。
同样的,显式配置有两种方式,XML和基于Java。
2.3:在Java代码中定义Spring的装配规则启用组件扫描
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
@ComponentScan注解能够在Spring中启用组件扫描,并且,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样就实现了组件扫描,就能发现TaylorSwift类,并且会在Spring中自动为其创建一个Bean。
2.4:使用XML配置启用组件扫描
<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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="soundsystem" />
beans>
在XML中,< context: component-scan>元素会有与@ComponentScan注解相对应的属性和子元素。
可是,真的扫描成功了吗?Bean到底创建成功了吗?接下来测试一下~
为了测试组件扫描的功能,创建一个简单的JUnit测试,它会创建Spring上下文,并判断TaylorSwift Bean是不是真的创建出来了。
2.5:测试组件扫描能够发现TaylorSwift
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath*:CDPlayer.xml") //使用XML启用组件扫描
@ContextConfiguration(classes = CDPlayerConfig.class) //使用基于Java配置启用组件扫描
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
//若程序运行没有出现异常, 说明断言正确
assertNotNull(cd);
}
}
CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration会告诉它需要在CDPlayerConfig或CDPlayer.xml中加载配置文件。因为配置文件中都包含了ComponentScan,所以最终的应用上下文中都包含TaylorSwift Bean。
下面我们会更加深入地探讨@ComponentScan和@Component,看看它们还有哪些作用。
Spring应用上下文中所有的Bean都会给定一个ID,Spring应用上下文自动创建的Bean的ID为将该类的名字的第一个字母小写,也就是说,TaylorSwift的Bean的ID为taylorSwift。
我们有两种方法为Bean设置不同的ID:
@Component("lovetaylor") //第一种
//@Named("lovetaylor") //第二种
public class TaylorSwift implements CompactDisc {
...
}
但是我们更倾向于使用@Component,@Component更能表明它是做什么的。
上面我们说过,@ComponentScan默认会扫描配置类所在的包,那如果我们想扫描不同的包,扫描多个包该怎么办呢?
我们可以在@ComponentScan的value属性中指明包的名称
@Configuration
//@ComponentScan("soundsystem")
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig{}
@Configuration
@ComponentScan(basePackages={"soundsystem","video"})
public class CDPlayerConfig{}
除了将包设置为简单的String类型之外,@ComponentScan还提供了另一种方法,那就是将其指定为包中所包含的类或接口,这些类所在的包将会作为组件扫描的基础包:
@Configuration
@ComponentScan(basePackageClasses=CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig{}
在我们的应哟呵那个程序中,很多对象会依赖其他的对象才能完成任务,所以就需要一种方法能够将组件扫描的Bean和它们的依赖装配在一起。
下面我们就来看看自动化装配的另一方面内容——自动装配。
自动装配就是让Spring自动满足Bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个Bean需求的其他Bean。
为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
通过程序来解释自动装配@Autowired是怎么回事。
现在有了CD,要将CD放入播放器里面播放啦~
同样的,先抽象出媒体播放器的接口,然后再实现CD播放器。
2.6:媒体播放器接口
public interface MediaPlayer {
public void play();
}
2.7:实现CD播放器
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
CDPlayer的构造方法处使用了@Autiwired注解,这表明Spring创建CDPlayer Bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的Bean。
换种说法,就是Spring在初始化CDPlayer这个Bean的时候,发现其构造方法需要依赖CompactDisc Bean,所以Spring就会去满足这种依赖关系,也就是将CompactDisc这个Bean自动装配进来。
不管是构造器、set方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。
如果没有匹配的Bean,那么应用上下文创建的时候,Spring就会抛出一个异常。可以将@Autowired的required属性设置为false,但是需谨慎,因为这样如果没有匹配的Bean, Spring就会让当前Bean处于未装配的状态。
如果有多个Bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个Bean进行自动装配。
像@Component和@Named一样,@Inject(来源于Java依赖注入规范)也可以用来替换@Autowired(Spring特有)。
现在,我们已经将自动化装配的两件事组件扫描和自动装配做好了,那接下来就来验证一下装配有没有成功。我们对之前的CDPlayerTest进行修改~
2.8:验证自动装配
@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("正在播放MeiMei的TaylorSwift", log.getLog());
}
}
代码中使用了StandardOutputStreamLog(已过时),这是来源于System Rules库的一个JUnit规则,这个规则能够基于控制台的输出编写断言。在这里,我们断言TaylorSwift.play()方法的输出被发送到了控制台上 。
现在我们已经了解了自动化装配,再来看看Spring中如何显式地装配Bean~
通过Java代码装配Bean,就是通过编写JavaConfig进行配置,更简单地来讲,就是在JavaConfig中声明Bean。具有强大、类型安全并且对重构友好的特点。
我们说JavaConfig就是带有@Configuration注解的Java代码,但是它和其他Java代码又有什么区别呢?
在概念上,JavaConfig与应用程序中的业务逻辑和领域代码是不同的,JavaConfig是配置代码,不包含任何业务逻辑,也不侵入到业务逻辑代码之中。所以通常将其放到单独的包中,使它与其他的应用程序逻辑分离开。
接下来我们看一下如何通过JavaConfig显式配置Spring~
前面的例子中我们已经认识了JavaConfig
2.9:在CD播放器例子中出现过的JavaConfig
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
这个JavaConfig是启用组件扫描的一个配置文件。@ComponentScan注解就是在Spring中启用组件扫描。
那么问题来了,我们说自动化装配是隐式配置,JavaConfig和XML是显式配置,那为什么JavaConfig还可以用到自动化装配里呢?
其实,自动装配Bean机制是由组件扫描和自动装配两部分组成, 组件扫描仍需要显式配置之后才能发挥它的作用。
现在理清了这些关系,那么我们来看一下通过Java代码来装配Bean的JavaConfig应该怎样写。
2.10:创建一个普通的JavaConfig
@Configuration
public class CDPlayerConfig {
}
创建好了,我们看一下如何使用JavaConfig装配CDPlayer和CompactDisc。
要在JavaConfig中声明Bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。
2.11: 声明一个TaylorSwift类的Bean
@Bean
public CompactDisc TaylorSwift() {
return new TaylorSwift();
}
@Bean注解会告诉Spring,这个方法会返回一个对象,该对象要注册为Spring应用上下文中的Bean。
Bean就是包装后的Object,那也就是说,Spring最终会将new TaylorSwift()产生的实例包裹起来,形成Bean。
也可以通过Bean的name属性为Bean命名。
2.12:给Bean重命名
@Bean(name="lovetaylor")
public CompactDisc TaylorSwift() {
return new TaylorSwift();
}
我们发现Bean的声明是很简单的,但是我们可能希望充分使用Java的功能来发挥其功能。
比如,我们希望随机选择一个CD来播放~
2.13: 随机播放一盘CD
@Bean
public CompactDisc randomCD(){
int choice = (int)Math.floor(Math.random() * 4);
if(choice == 0){
return new TaylorSwift();
} else if(choice ==1) {
return new bala1();
} else if(choice ==2) {
return new bala2();
} else{
return new bala();
}
}
在自动装配中,Spring帮我们来完成了Bean的注入,那么现在我们要通过Java代码来手动完成装配。
既然我们已经有了CD的Bean,现在要做的是播放CD,也就是要将CD注入到CDPlayer中。
最简单的方式就是引用创建Bean的方法:
2.14: 创建CDPlayer Bean时直接调用CD Bean
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(TaylorSwift());
}
这个cdPlayer()和TaylorSwift()方法一样,同样使用了@Bean注解,表明这个方法会创建一个Bean实例并将其注册到Spring应用上下文中。但是在cdPlayer()方法体里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例。
所以看起来就像是在创建CDPlayer Bean的时候会调用创建TaylorSwift Bean的方法,但实际上并非如此,cdPlayer()方法调用TaylorSwift()这个方法的时候,直接返回TaylorSwift Bean, 而非每次调用都重新new一个TaylorSwift对象再交给Spring注册成一个Bean。
这也就是说,TaylorSwift Bean在Spring中只存在一份,如果在这个JavaConfig中还有其它Bean需要用到TaylorSwift Bean的时候,将与CDPlayer Bean共用一份TaylorSwift Bean实例,因为默认情况下,Spring中的Bean都是单例的。
可以看到,这种通过调用的方法来引用Bean的方式有点令人困惑。
那么还有一种理解起来更为简单的方式:
2.15: 自动匹配所需的Bean进行装配
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayer Bean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。这样,cdPlayer()方法也能将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
这就是关于使用Java代码来装配Bean的方式。
由于XML装配Bean并不作为我们的首选方案,所以这部分内容只是用来帮助我们维护已有的XML配置。
在使用XML为Spring装配Bean之前,我们需要创建一个新的配置规范,即创建一个XML文件,并且以< beans>元素为根。
我们还要在XML文件顶部声明多个XML模式文件(.xsd),这些文件定义了配置Spring的XML元素。
2.16:没有声明任何Bean的最简单的XML文件
<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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
beans>
这就是一个合法的SpringXML配置。不过,它还没有声明任何Bean。现在,我们要给予它生命力,这次我们用XML配置重新创建CD。
我们要使用spring-beans模式中的另外一个元素:< bean>,它类似于JavaConfig中的@Bean注解。
我们可以这样声明CompactDisc Bean:
class="soundsystemXML.TaylorSwift" />
这个Bean的类通过class属性来指定,并且要使用全限定的类名。因为没有明确的指出ID,所以该Bean的ID将会是”soundsystemXML.TaylorSwift#0”。这个#0表示计数,如果我们也声明了不指定ID的另外一个TaylorSwift,那么它的ID将会是”soungsystemXML.TaylorSwift#1”。
但通常我们都会为它们指定ID,因为我们在装配Bean的时候会用到这个具体的名字,否则自动产生的名字也没有多大用处。
id="taylor" class="soundsystemXML.TaylorSwift" />
那么当Spring发现这个< bean>元素时,它将会调用TaylorSwift的默认构造器来创建Bean。
与JavaConfig的方式比较,我们不再需要直接创建TaylorSwift实例,这样也显得更加被动,但是它也没有JavaConfig强大,不能通过任何我们可以想象到的方法来创建Bean实例,而且,如果突然改变了类名,那又将造成很多麻烦。
我们可以回想一下,在JavaConfig中有两种注入Bean方式。那么在XML配置文件中也有两种注入Bean的方式:
什么叫通过构造器注入Bean呢? 前面讲过了,在XML中声明Bean时,使用全限定的类名来指定一个类,然后Spring调用这个类的默认构造方法new出一个对象,然后将其包裹成Bean。那么,如果这个类我们给其自定义了构造方法,我们就可以在XML中对其进行注入,完成Bean的初始化工作。
前面2.7的CD播放器的程序中,CDPlayer类的构造方法需要一个CompactDisc对象,如果我们要初始化这个Bean,就要完成CompactDisc Bean的注入。
在XML中,通过构造器注入Bean有两种配置方案:
2.17: < constructor-arg>元素配置方式注入初始化CDPlayer Bean
<bean id="cdPlayer" class="com.soundsystemXML.CDPlayer">
<constructor-arg ref="taylor" />
bean>
< constructor-arg>元素会告知Spring要将一个ID为taylor的Bean引用传递到CDPlayer的构造器中。
同样的,还有c-命名空间配置注入Bean。
2.18: c-命名配置方式注入Bean
id="cdPlayer1" class="com.soundsystemXML.CDPlayer"
c:cd-ref="taylor" />
c-命名空间这种配置方式看起来有点怪异,我们来分析一下:
关于c-命名空间,还有一些别的使用规则。比如,当构造器中需要多个Bean的时候,我们可以使用参数索引来表示参数列表里所需要的多个Bean。比如这样:
id="cdPlayer2" class="com.soundsystemXML.CDPlayer"
c:_0-ref="taylor" />
上面介绍的是通过构造器注入Bean,但是有的时候,构造器的参数并不是一个类变量,而是一个常量。那么就来看一下如何通过构造器注入常量~
假设现在有一个Taylor的CD如下:
2.19:TaylorDisc CD
public class TaylorDisc implements CompactDisc {
private String title;
private String artist;
public TaylorDisc(String title, String artist){
this.title = title;
this.artist = artist;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
就这个类的构造器而言,它的两个参数都是常量,所以要通过XML将常量注入。和前面一样,将常量注入到构造器中,也有两种方式:
2.20:将常量注入到构造器中
<bean id="taylorDisc1" class="com.soundsystemXML.TaylorDisc" >
<constructor-arg value="TSCD" />
<constructor-arg value="MeiMei" />
bean>
每种注入方式都是有多种书写格式的, 比如还可以像下面这样:
<bean id="taylorDisc1" class="com.soundsystemXML.TaylorDisc" >
<constructor-arg index="0" value="TSCD" />
<constructor-arg index="1" value="MeiMei" />
bean>
2.21:使用c-命名空间注入常量
id="taylorDisc2" class="com.soundsystemXML.TaylorDisc"
c:title="TSCD" c:artist="MeiMei" />
这种注入方式是通过构造器的参数列表的参数名称来指定,也可以像前面注入Bean那样,使用索引来指定要注入的常量。比如下面这样:
<bean id="bean id="taylorDisc2" class="com.soundsystemXML.TaylorDisc"
c:_0="TSCD" c:_1="MeiMei" />
需要特别说明的是,在装配Bean的时候,如果我们要注入的是一个数组/列表/集合呢?那么我们就需要用到< list>元素。
假设现在有一个五月天的新专辑类,我们要对这个CD进行一些更细致的处理,比如显示出播放这个CD里面的每一首歌:
2.22:五月天的新专辑
public class MayDayDisc implements CompactDisc {
private String title;
private String artist;
private List tracks;
// private Set tracks;
// 一般来说, List和Set的区别不是很大, 但是当Spring要装配的是集合的时候, 使用Set集合可以保证集合中的元素不会重复.
public MayDayDisc(String title, String artist, List tracks){
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for(String track : tracks){
System.out.println("-Track: " + track);
}
}
}
那么这个时候,XML文件里应该这样配置:
<bean id="mayDay1" class="com.soundsystemXML.MayDayDisc">
<constructor-arg value="五月天的新专辑" />
<constructor-arg value="五月天" />
<constructor-arg>
<list>
<value>歌曲1value>
<value>歌曲2value>
<value>歌曲3value>
list>
constructor-arg>
bean>
在上面的代码中提到了,在类里除了使用List,也可以使用Set,不过就是将相对应的XML配置文件中的< list>元素改为< set>就可以了。
同理,如果现在有一个电台,要播放若干CD,我们也可以将若干CD类使用< list>元素,注入到电台类中。
2.23:电台
public class Discography {
private String artist;
private List cds;
public Discography(String artist, List cds){
this.artist = artist;
this.cds = cds;
}
}
而其相对应的XML应该是这样:
<bean id="discpgraphy" class="com.soundsystemXML.Discography">
<constructor-arg index="0" value="今日流行"/>
<constructor-arg index="1">
<list>
<ref bean="jay" />
<ref bean="jayZhou" />
<ref bean="mayDay" />
list>
constructor-arg>
bean>
以上就是关于通过构造器装配(注入)Bean的内容,下面我们来看一下怎样通过设置属性来装配(注入)Bean~
在Java中,除了通过构造方法给数据成员赋值以外,我们还可以通过setter方法给数据成员赋值。
与通过构造器注入Bean一样,通过设置属性注入Bean也有两种装配方式:
我们现在对CDPlyer类进行修改,去掉它的自定义构造方法,加上所有数据成员的setter方法:
2.24:CDPlayer
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public void setCd(CompactDisc cd){
this.cd = cd;
}
public void play() {
cd.play();
}
}
如果现在要在XML中重新声明这个类,我们可能会这样声明:
id="cdPlayer" class = "com.soundsystemXML.CDPlayer" />
这样看起来是没有问题的,毕竟现在这个类没有自定义的构造方法了嘛,默认的构造方法也是不含参数的。可是这样真的对吗? 如果我们测试这个类,会发现它会抛出NullPoiterException异常,因为我们没有注入任何CompactDisc Bean,所以运行play()方法当然就出错了。
要通过设置属性注入Bean,这时候我们就要用到< property>元素了。元素为设置属性的setter方法所提供的功能和< constructor-arg>元素为构造器提供的功能是一样的。比如现在可以在XML中这样写:
<bean id="cdPlayer3" class = "com.soundsystemXML.CDPlayer">
<property name="cd" ref="taylor"/>
bean>
2.25:通过p-命名空间来装配Bean
id="cdPlayer4" class="com.soundsystemXML.CDPlayer"
p:cd-ref="taylor">
同样的,p-命名空间的参数也比较难理解。现在我们也来分析一下p:cd-ref=”taylor”里的各项参数:
有了前面通过构造器注入常量的例子,这里的通过设置属性注入常量应该也不难理解。
与前面一样,通过设置属性注入常量也有两种装配方式:
我们先对五月天的新专辑类进行一些修改,去掉它的构造方法,加上所有数据成员的setter方法。
2.26:五月天的新专辑类
public class MayDayDisc implements CompactDisc {
private String title;
private String artist;
private List tracks;
public void setTitle(String title){
this.title = title;
}
public void setArtist(String artist){
this.artist = artist;
}
public void setTracks(List tracks){
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for(String track : tracks){
System.out.println("-Track: " + track);
}
}
}
在XML中应该这样配置:
<bean id="mayDay3" class="com.soundsystemXML.MayDayDisc">
<property name="title" value="五月天的新专辑" />
<property name="artist" value="五月天" />
<property name="tracks">
<list>
<value>歌曲1value>
<value>歌曲2value>
<value>歌曲3value>
<value>歌曲4value>
list>
property>
bean>
使用p-命名空间装配的时候还有一丢丢小麻烦,不过我们先把装配的代码贴出来,再对它进行解释:
<bean id="mayDay4" class="com.soundsystemXML.MayDayDisc"
p:title="五月天的新专辑"
p:artist="五月天">
<property name="tracks">
<list>
<value>歌曲1value>
<value>歌曲2value>
<value>歌曲3value>
<value>歌曲4value>
list>
property>
bean>
可以看出,再注入列表常量的时候,没有使用p-命名空间,而是使用了< property>元素。事实上,p-命名空间是不能装配集合的。
但是,我们仍然有简化书写的办法,就是使用util-命名空间。
我们可以使用util-命名空间的< util:list>元素来创建一个Bean,然后再通过p-命名空间的ref来引用这个Bean。具体代码如下:
<util:list id="trackList">
<value>歌曲1value>
<value>歌曲2value>
<value>歌曲3value>
<value>歌曲4value>
util:list>
<bean id="mayDay5" class="com.soundsystemXML.MayDayDisc"
p:title="五月天的新专辑"
p:artist="五月天"
p:tracks-ref="trackList">
bean>
< util:list>元素只是util-命名空间所提供的功能之一,它还有许多其它的元素,比如:
: 引用某个类型的public static域, 并将其暴露为Bean;
: 创建一个java.util.List类型的Bean, 其中包含值或引用;
: 创建一个java.util.Map类型的Bean, 其中包含值或引用;
: 创建一个java.util.Properties类型的Bean;
: 引用一个Bean的属性(或内嵌属性), 并将其暴露为Bean;
set> : 创建一个java.util.Set类型的Bean, 其中包含值或引用.