-
基于注解的自动配置
经过前文的学习,我们已经知道怎样基于XML和Java进行显式配置。与此同时,我们也惊人地发现显式配置格外麻烦。我们必须写好配置文件,把需要创建哪些Bean的信息一个不差地填进配置文件里。这样,Spring应用上下文才能读取配置文件创建Bean。若是需要创建的Bean不多,只有十个,二十个还好;若是需要创建的Bean很多,有一千个,两千个,甚至更多。这时,把这些Bean的创建信息一个不差地填进配置文件里就难免繁杂了。
那么,有没有什么办法能够解决这个问题,让事情优雅起来,简单起来呢?
当然有的。比如,基于注解的自动配置就能优雅地解决这个问题,让事情变得简单。至于基于注解的自动配置有多自动,从而能够简化配置;不妨让我们紧接前文实现的music-player,看看改用基于注解的自动配置能使配置简化多少,进而学习基于注解的自动配置的基础知识。为此,请打开music-player项目,修改AppConfig配置类如下:
1 package com.dream; 2 3 import org.springframework.context.annotation.*; 4 5 @Configuration 6 @ComponentScan(basePackages="com.dream") 7 public class AppConfig { 8 }
瞧,那些关于如何创建Bean的配置方法一个不剩,全没了!却见类上除了@Configuration注解之外,还带有一个神秘的@ComponentScan注解。
这是怎么回事呢?
原来,基于注解的自动配置对配置类没那么多要求,只要加上@ComponentScan注解就够了。至于那些关于如何创建Bean的配置方法,基于注解的自动配置是一个也不需要的。于是问题来了,@ComponentScan到底是什么样的注解?为什么它的功能如此强大,简简单单一句注解就能搞定配置?
其实,@ComponentScan注解只是启用了组件扫描,别的事情一件也没做。因此,@ComponentScan注解有个常用的basePackages属性,用于指定需要扫描的包。需要扫描的包可以同时指定多个,不同的包之间用逗号隔开就行。Spring应用上下文加载配置类之后如果发现配置类带有@ComponentScan注解,就会扫描组件。扫描组件之前会先看一下basePackages属性的值。如果basePackages属性无值就扫描配置类所在的包;如果basePackages属性有值就扫描该值指定的包。
于是我们知道了,AppConfig配置类上的@ComponentScan(basePackages="com.dream")注解只是启用了组件扫描,告诉Spring应用上下文扫描com.dream包,找出包里的所有组件。重点在于,找出所有组件之后呢?Spring应用上下文能否自动创建和装配Bean?如果Spring应用上下文真能自动创建和装配Bean,又是依据什么进行的呢?还有,组件是什么?带着这些问题,让我们修改一下Music类和Player类,如下:
1 package com.dream; 2 3 import org.springframework.beans.factory.annotation.*; 4 import org.springframework.stereotype.*; 5 6 @Component(value = "music") 7 public class Music { 8 private String musicName = null; 9 10 public String getMusicName() { 11 return this.musicName; 12 } 13 14 @Value(value="执着") 15 public void setMusicName(String musicName) { 16 this.musicName = musicName; 17 } 18 }
1 package com.dream; 2 3 import org.springframework.beans.factory.annotation.*; 4 import org.springframework.stereotype.*; 5 6 @Component(value = "player") 7 public class Player { 8 private Music music = null; 9 10 public Music getMusic() { 11 return this.music; 12 } 13 14 @Autowired(required = true) 15 public void setMusic(Music music) { 16 this.music = music; 17 } 18 19 public void play() { 20 var musicName = this.music.getMusicName(); 21 var musicMessage = String.format("开始播放音乐《%s》", musicName); 22 System.out.println(musicMessage); 23 } 24 25 public void pause() { 26 var musicName = this.music.getMusicName(); 27 var musicMessage = String.format("暂停播放音乐《%s》", musicName); 28 System.out.println(musicMessage); 29 } 30 }
可以看到,Music类和Player类带有这些注解:
1.Music类带有@Component注解。
2.Music类的setMusicName方法带有@Value("执着")注解。
3.Player类带有@Component注解。
4.Player类的setMusic方法带有@Autowired(required = true)注解。
这是怎么回事呢?
原来,Spring应用上下文加载配置类之后如果发现类上带有@ComponentScan注解,就会扫描指定的包,找出包里所有带有@Component注解的类。这些带有@Component注解的类就是组件。之后,Spring应用上下文通过反射技术调用类的默认构造函数创建Bean。这些Bean的id默认是类名的第一个字母变成小写之后的字符串。当然,我们也可设置@Component注解的value属性指定Bean的id。
于是我们知道了,组件扫描的目的是发现组件,创建Bean。那么,Bean的装配又是怎么进行的呢?
这就涉及@Value注解和@Autowired注解了。众所周知,Bean的装配分为两种:一种是字面量值装配;一种是对象装配。Spring应用上下文扫描组件之后,如果发现组件的方法带有@Value注解,就会进行字面量值的自动装配。如果发现组件的方法带有@Autowired注解,就会进行对象的自动装配,看看带有@Autowired注解的方法需要什么类型的Bean,再从Spring应用上下文里找到这种类型的Bean自动装配上去。
因此,@Value注解有个常用的value属性。这个属性是字符串类型的,用于指定字面量值。这样,Spring应用上下文读到@Value注解的value属性之后,就能进行字面量的自动装配了。@Autowired注解有个常用的required属性。这个属性是布尔类型的,用于指定对象的装配是不是必须的。当required属性的值等于TRUE时,对象的装配是必须的。如果Spring应用上下文找不到相应的Bean进行装配,就会抛出org.springframework.beans.factory.NoSuchBeanDefinitionException类型的异常;当required属性的值等于FALSE时,对象的装配不是必须的。如果Spring应用上下文找不到相应的Bean进行装配,则不进行装配。required属性的默认值是TRUE。
于是我们知道了,Music类和Player类之所以带有@Component注解,是为了能被组件扫描发现。Music类的setMusicName方法之所以带有@Value("执着")注解,是为了把“执着”这个字面量值装配进去。Player类的setMusic方法之所以带有@Autowired注解,是为了让Spring应用上下文找到类型为Music的对象之后装配进去。
非常明显,基于注解的自动配置分为两步:第一步是组件扫描(Component Scanning),第二步是自动装配(Auto Wiring)。Spring应用上下文加载配置类之后,发现配置类带有@ComponentScan注解,于是启用组件扫描从basePackages属性指定的包里找到所有组件,应用反射技术调用类的默认构造函数创建Bean。之后,Spring应用上下文开始自动装配。通过带有@Value注解的方法进行字面量值的自动装配;通过带有@Autowired注解的方法进行对象的自动装配。于是,基于注解的自动配置完成了。
同时我们也应看到,基于注解的自动配置是通过配置文件启用组件扫描的。这个配置文件可以是Java配置类,自然也可以是XML配置文件。比如,就music-player这个项目而言,如果采用XML启用组件扫描,则可修改app-config.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.dream" /> </beans>
正如大家所料。那些关于如何创建Bean的配置信息一个不剩,全没了!却多了以下两样东西:
1.XML模式文件http://www.springframework.org/schema/context/spring-context.xsd
2.XML元素<context:component-scan base-package="com.dream" />
spring-context.xsd模式文件定义了哪些XML元素可以用来配置Spring应用上下文。<context:component-scan base-package="com.dream" />正是spring-context.xsd模式文件定义的一个元素,用于启用组件扫描。其用法与@ComponentScan注解是一样的,这里不再赘述。
于是我们知道了,Spring提供了两种配置方式:一种是显式配置;一种是自动配置。显式配置通过XML或Java显式描述Bean的创建信息,再由Spring应用上下文根据具体的配置信息进行Bean的创建和装配。因此,显式配置分为两种:一种是基于XML的显式配置,通常也叫通过XML进行配置;一种是基于Java的显式配置,通常也叫通过Java进行配置。自动配置由组件扫描和自动装配两部分组成。我们需用Java配置类或XML配置文件启用组件扫描,再在类里添加注解,告诉Spring应用上下文哪些类是组件以及Bean应该怎么装配。这样,Spring应用上下文扫描组件之后就能进行Bean的创建和装配了。因此,自动配置是一种基于注解的自动配置,通常也叫通过注解进行配置。
至此,关于配置的基础知识介绍完了。想必大家还有很多困惑。比如关于自动装配,当Spring应用上下文里存在多个相同类型的对象时,Spring应用上下文怎么知道应该选用哪个对象进行装配呢?讲清诸如这样的问题需要花些篇幅,将在“细说Spring”的时候再行讨论。现在,让我们开启新的征程,先来看看Spring是怎样简化JDBC的。
下载代码 https://github.com/Evan-I/Open-Spring