基于XML的显式配置
Spring提供了两种配置方式:一种是显式配置;一种是自动配置。显式配置又分为两种:一种是基于XML的显式配置;一种是基于Java的显式配置。自动配置只有一种,即基于注解的自动配置。本章即将介绍的是基于XML的显式配置,其它配置方式将在其它章节另行介绍。
基于XML的显式配置大家并不陌生,毕竟前文介绍Hello World的时候已经接触过了。现在,让我们一起瞧瞧能用基于XML的显式配置怎样实现一个简单的音乐播放器,使之能够播放音乐,暂停音乐;进而学习基于XML的显式配置的基础知识。为此,请打开IntelliJ IDEA,新建music-player项目;随后创建com.dream包,定义音乐Music类,音乐播放器Player类如下:
1 package com.dream; 2 3 public class Music { 4 private String musicName = null; 5 6 public String getMusicName() { 7 return this.musicName; 8 } 9 10 public void setMusicName(String musicName) { 11 this.musicName = musicName; 12 } 13 }
1 package com.dream; 2 3 public class Player { 4 private Music music = null; 5 6 public Music getMusic() { 7 return this.music; 8 } 9 10 public void setMusic(Music music) { 11 this.music = music; 12 } 13 14 public void play() { 15 var musicName = this.music.getMusicName(); 16 var musicMessage = String.format("开始播放音乐《%s》", musicName); 17 System.out.println(musicMessage); 18 } 19 20 public void pause() { 21 var musicName = this.music.getMusicName(); 22 var musicMessage = String.format("暂停播放音乐《%s》", musicName); 23 System.out.println(musicMessage); 24 } 25 }
Music类定义了musicName属性,用于存放音乐名。Player类定义了music属性,play方法,pause方法三个成员。music属性用于存放即将播放的音乐;play方法用于播放音乐;pause方法用于暂停播放。
于是,类的定义完成了。让我们写个配置文件,告诉Spring容器怎样创建我们需要的Bean。为此,请往resources目录添加配置文件app-config.xml如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd"> 7 8 <!-- 在这里填写配置信息 --> 9 10 </beans>
这是一个XML配置文件。里面空落落的,只有一个<beans>根元素。<beans>根元素列出一些XML模式文件(.xsd),定义了配置文件可以怎么配置。比如配置文件拥有哪些XML元素,XML元素拥有哪些XML属性,XML元素和XML属性各有什么作用,等等。spring-beans.xsd模式文件定义了Bean能够使用哪些XML元素,哪些XML属性进行配置。
那么,Bean具体应该怎么配置呢?是的,通过<bean>元素。比如,Music类和Player类就可使用<bean>元素配置如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd"> 7 8 <bean id="music" class="com.dream.Music" /> 9 <bean id="player" class="com.dream.Player" /> 10 11 </beans>
可以看到<bean>元素具有id和class两个XML属性。id用于唯一标识一个Bean,其值默认为全限定类名+索引。全限定类名指的是类的全名,也就是完整的包名+类名。比如,Music类的全限定类名就是com.dream.Music。索引是指存在于Spring应用上下文中的第几个某种类型的Bean。要知道XML配置文件是能同时配置多个相同类型的Bean的。比如,可以同时配置两个Music类型的Bean如下:
1 <bean class="com.dream.Music"/> 2 <bean class="com.dream.Music"/>
这里配置了两个相同类型的Bean。第一个Bean的id默认是com.dream.Music#0;第二个Bean的id默认是com.dream.Music#1。id末尾的#0,#1就是索引了。
<bean>元素的class属性用于表明Bean的类型,其值为全限定类名。
于是我们知道了,这里配置了两个Bean:一个Bean是Music类型的,id为music;一个Bean是Player类型的,id为player。这样,Spring容器加载这个XML配置文件之后就能知道应该创建哪些Bean了。需要了解的是,Spring容器创建的Bean默认是单例的。如果不想创建单例的Bean,可向配置文件提供一些关于Bean的作用域的信息,告诉Spring容器创建非单例的Bean。这些内容有些复杂,将在“细说Spring”的时候进行讨论。现在,我们只需知道Spring容器创建的Bean默认是单例的就行了。
或许大家已经察觉,这里配置的两个Bean并不完整。为什么呢?因为音乐和音乐播放器是有关系的,可是现有的配置并没有体现这种关系。这意味着音乐播放器压根不知道自己即将播放的是什么音乐,又怎么播放得了音乐呢?所以,我们还需往音乐里注入音乐名,再把有了音乐名的音乐注入到音乐播放器中,完成音乐和音乐播放器的装配。这样,音乐播放器才能播放音乐。如下所示:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd"> 7 8 <bean id="music" class="com.dream.Music"> 9 <property name="musicName" value="执着" /> 10 </bean> 11 <bean id="player" class="com.dream.Player"> 12 <property name="music" ref="music" /> 13 </bean> 14 15 </beans>
这里用到<property>元素。<property>元素是<bean>元素的子元素,用于配置通过属性进行依赖的注入。因此,<property>元素拥有name,value,ref三个常用的XML属性。name用于指定Bean的属性名;value用于指定字面量值(Literal Value)类型的属性值;ref用于指定引用类型的属性值。
于是我们知道了,这段配置信息用于告诉Spring容器创建music这个Bean之后,把“执着”这个字面量值注入到music的musicName属性中;创建player这个Bean之后,把music这个Bean注入到player的music属性中。这样,Spring容器就能正确装配Bean,让音乐播放器播放《执着》这首音乐了。同时这也意味着配置文件写好了,可以把它交给Spring容器了。为此,请新建Main类如下:
1 package com.dream; 2 3 import org.springframework.context.support.*; 4 5 public class Main { 6 public static void main(String[] args) { 7 var configFile = "resources\\app-config.xml"; 8 try (var context = new ClassPathXmlApplicationContext(configFile)) { 9 var player = context.getBean("player", Player.class); 10 player.play(); 11 player.pause(); 12 } 13 } 14 }
可以看到,我们把配置文件app-config.xml交给了ClassPathXmlApplicationContext。ClassPathXmlApplicationContext是Spring应用上下文的其中一种实现,能够基于类路径加载XML配置文件,解析XML配置文件,根据XML配置文件提供的信息创建和装配Bean。至于类路径,则是类所在的路径。就music-player这个项目而言,IntelliJ IDEA完成项目的编译之后,会把编译好的文件输到 <项目根目录>\out\production\music-player\ 目录。这个目录存有一个Java包,一个resources文件夹。Java包存有编译好的类文件,resources文件夹存有app-config.xml配置文件。类路径指的就是 <项目根目录>\out\production\music-player\ 这个存有编译好的Java包的目录,也就是存有编译好的类的目录。因此,我们传给Spring应用上下文的是 resources\app-config.xml
Spring应用上下文创建和装配Bean之后,Bean就存在Spring应用上下文里,由Spring应用上下文管理着。我们需要Bean的时候,可以调用Spring应用上下文的getBean方法进行获取。getBean方法签名如下:
public <T> T getBean(String name, Class<T> requiredType) throws BeansException
可以看到getBean方法接受两个参数。第一个参数是String类型的,用于指定Bean的id;第二个参数是Class<T>类型的,用于指定Bean的类型。调用getBean方法之后,Spring应用上下文就能找到id为某值的Bean,并把Bean转换为指定的类型返回给调用者。这里,我们调用getBean方法从Spring应用上下文那里获取id为player,类型为Player的Bean;随后调用Bean的play方法播放音乐,pause方法暂停播放;从而实现了一个简单的音乐播放器。运行之后结果如下:
当然,除了ClassPathXmlApplicationContext之外,我们也可使用FileSystemXmlApplicationContext这种Spring应用上下文实现。和ClassPathXmlApplicationContext相比,FileSystemXmlApplicationContext除了查找XML配置文件的方式不同之外,其它都是一样的。FileSystemXmlApplicationContext不是基于类路径,而是基于应用程序当前工作目录查找并加载XML配置文件的。因此,如果使用FileSystemXmlApplicationContext,Main类可以这样实现:
1 package com.dream; 2 3 import org.springframework.context.support.*; 4 5 public class Main { 6 public static void main(String[] args) { 7 var configFile = "out\\production\\music-player\\resources\\app-config.xml"; 8 try (var context = new FileSystemXmlApplicationContext(configFile)) { 9 var player = context.getBean("player", Player.class); 10 player.play(); 11 player.pause(); 12 } 13 } 14 }
至此,基于XML的显式配置的基础知识介绍完了。通过music-player这个项目可以发现,我们定义的Music类和Player类既没继承任何Spring相关的类,也没实现任何Spring相关的接口。Music类还是原来的Music类,Player类还是原来的Player类,没有因为Spring的引入产生任何变动,也没有因为Spring的引入与Spring产生任何耦合。这意味着Spring并没有对我们的代码产生任何侵入。这种无需继承任何其它框架提供的类,无需实现任何其它框架提供的接口,没被其它框架入侵的对象通常称为简单老式Java对象(Plain Old Java Object,POJO)
Spring出现之前的其它框架,比如EJB(Enterprise JavaBean),可不是这样的。那些框架的侵入性很强,总是要求我们的类继承或实现那些框架提供的某个类或接口才能享有那些框架提供的功能。从而导致我们的类与框架紧紧耦合在一起,非常重量级,非常复杂,非常难以维护。Spring另辟蹊径,通过配置文件将类和Spring进行联系,从而无需继承或实现任何Spring提供的类或接口;在保持简单,保持轻量级的同时也能享受Spring提供的强大功能。这不正是Spring充满魅力的其中一个重要原因吗?
还有,XML并不是Spring提供的唯一一种配置方式。我们还能选择Java这种编程语言进行Spring的显示配置。至于能用Java怎样显式配置Spring,则是下章我们即将讨论的内容。欢迎大家继续阅读,谢谢大家!