基于注解的自动配置

显式配置并不怎么方便。我们必须备好配置文件,把Bean的创建信息一个不差地填写进去之后交给Spring容器,Spring容器才能进行Bean的创建。若是需要创建的Bean不多,只有二三十个还好;若是需要创建的Bean很多,有成千上万个;这时,把Bean的创建信息一个不差地填进配置文件里就难免繁杂了。

那么,有没有什么办法能够解决这个问题,让事情优雅起来,简单起来呢?

当然有的。基于注解的自动配置就能有效地解决这个问题,让事情优雅起来,简单起来。至于基于注解的自动配置有多自动,从而能够简化配置;不妨让我们紧接前文实现的music-player项目,看看改用基于注解的自动配置能使配置简化多少;进而学习基于注解的自动配置的基础知识。为此,请打开music-player项目,修改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        xmlns:context="http://www.springframework.org/schema/context"
 5        xsi:schemaLocation="
 6        http://www.springframework.org/schema/beans
 7        http://www.springframework.org/schema/beans/spring-beans.xsd
 8        http://www.springframework.org/schema/context
 9        http://www.springframework.org/schema/context/spring-context.xsd">
10 
11     <context:component-scan base-package="com.dream" />
12 
13 </beans>

瞧,那些关于如何创建Bean的配置信息一个不剩,全没了!却多了以下两个神秘的配置信息:
1.XML模式文件http://www.springframework.org/schema/context/spring-context.xsd
2.XML元素<context:component-scan base-package="com.dream"/>

这是怎么回事呢?

原来,基于注解的自动配置对配置文件没那么多要求。只要启用组件扫描,让Spring容器扫描组件之后自动进行Bean的创建即可。这里出现的两个神秘的配置信息显然就是用来启用组件扫描的。

其中,spring-context.xsd定义了些XML元素用于支持Spring应用上下文的配置。<context:component-scan>则是spring-context.xsd定义的其中一个XML元素,用于启用组件扫描;具有base-package属性,用于指定一个或多个即将扫描的包。不同的包之间可用逗号,分号或空格进行分隔。我们添加的<context:component-scan base-package="com.dream"/>用于告诉Spring容器扫描com.dream包,找出包里所有组件之后自动进行Bean的创建。

可是,组件是什么?Spring容器扫描组件之后又是怎样进行Bean的自动创建的呢?回答这些问题之前,让我们先在Music类和Player类上添加@Component注解如下:

1 @Component(value = "music")
2 public class Music {
3     // 省略类的内容
4 }
5 
6 @Component(value = "player")
7 public class Player {
8     // 省略类的内容
9 }

这是怎么回事呢?

原来,Spring容器如果发现配置文件存在<context:component-scan>元素,就会扫描base-package属性指定的包,找出包里所有带有@Component注解的类。带有@Component注解的类就是组件。之后,Spring容器通过反射技术调用类的构造函数,自动完成Bean的创建。创建出来的Bean其id默认是类名的第一个字母变成小写之后的字符串。当然,我们也可通过@Component注解的value属性指定id

基于这些,我们在Music类上添加@Component(value="music")注解,告诉Spring容器创建一个类型为Music,id为music的Bean;在Player类上添加@Component(value="player")注解,告诉Spring容器创建一个类型为Player,id为player的Bean

需要注意的是,组件扫描只是完成了Bean的自动创建,没有完成Bean的自动装配。因此,现有的配置是明显不够的。我们还需提供一些配置信息,告诉Spring容器怎样完成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        xmlns:context="http://www.springframework.org/schema/context"
 5        xsi:schemaLocation="
 6        http://www.springframework.org/schema/beans
 7        http://www.springframework.org/schema/beans/spring-beans.xsd
 8        http://www.springframework.org/schema/context
 9        http://www.springframework.org/schema/context/spring-context.xsd">
10 
11     <context:annotation-config />
12     <context:component-scan base-package="com.dream" />
13 
14 </beans>

现在,配置文件新增了个<context:annotation-config>元素。<context:annotation-config>同样也是spring-context.xsd定义的一个XML元素,用于启用基于注解的自动装配,告诉Spring容器通过注解提供的信息完成Bean的自动装配。由此可知,我们还需要往类里添加更多注解,如下:

 1 @Component(value = "music")
 2 public class Music {
 3     @Value(value="执着")
 4     public void setMusicName(String musicName) {
 5         this.musicName = musicName;
 6     }
 7 
 8     // 省略类的内容
 9 }
10 
11 @Component(value = "player")
12 public class Player {
13     @Autowired(required = true)
14     public void setMusic(Music music) {
15         this.music = music;
16     }
17 
18     // 省略类的内容
19 }

这里做了两个改动:
1.在Music类的setMusicName方法上添加@Value("执着")注解。
2.在Player类的setMusic方法上添加@Autowired(required=true)注解。

这是怎么回事呢?

原来,Bean的装配分为两种:一种是字面量值装配;一种是对象装配。Spring容器扫描组件之后,如果发现组件的方法带有@Value注解,就会进行字面量值的自动装配。如果发现组件的方法带有@Autowired注解,就会进行对象的自动装配,看看带有@Autowired注解的方法需要什么类型的Bean,再从Spring容器里找到这种类型的Bean自动装配上去。

因此,@Value注解有个字符串类型的value属性,用于指定字面量值。这样,Spring容器读到@Value注解的value属性之后就能进行字面量值的自动装配了;@Autowired注解有个布尔类型的required属性,用于指定Bean的装配是不是必须的。当required属性的值等于TRUE时,Bean的装配是必须的。如果Spring容器找不到相应的Bean进行装配,就会抛出NoSuchBeanDefinitionException类型的异常。当required属性的值等于FALSE时,Bean的装配不是必须的。如果Spring容器找不到相应的Bean进行装配,则会忽略,不进行Bean的装配。required属性的值默认等于TRUE

于是我们知道了,Music类和Player类之所以带有@Component注解,是为了能被组件扫描发现。Music类的setMusicName方法之所以带有@Value("执着")注解,是为了把“执着”这个字面量值自动装配上去。Player类的setMusic方法之所以带有@Autowired(required=true)注解,是为了把类型为Music的Bean自动装配上去。

非常明显,基于注解的自动配置分为两步:第一步是组件扫描(Component Scanning);第二步是自动装配(Auto Wiring)。Spring容器如果发现配置文件带有<context:component-scan>,就会启用组件扫描,从base-package属性指定的包里找到所有组件之后进行Bean的创建。Spring容器如果发现配置文件带有<context:annotation-config>,就会启用基于注解的自动装配,通过带有@Value注解的方法进行字面量值的自动装配;通过带有@Autowired注解的方法进行对象的自动装配。

另外,<context:annotation-config>并不是必须的。因为<context:component-scan>除了具有启用组件扫描的功能之外,也隐含着<context:annotation-config>具有的功能。因此,启用基于注解的自动配置通常只需添加<context:component-scan>这个配置信息。

当然,除了XML配置文件,我们也能使用配置类启用组件扫描和自动装配。如下:

1 @Configuration
2 @ComponentScan(basePackages="com.dream")
3 public class AppConfig {
4 }

我们在AppConfig配置类上添加了@ComponentScan注解。@ComponentScan注解是和<context:component-scan>相对应的,同样具有启用组件扫描和自动装配的功能。扫描的包既可通过basePackages属性指定,也可通过basePackageClasses属性指定。basePackages属性是字符串类型的,用于指定一个或多个包名,包名之间可用逗号,分号或空格进行分隔;basePackageClasses是Class<?>[]类型的,用于指定一个或多个类,告诉Spring容器扫描这些类所在的包,如下:

1 @Configuration
2 @ComponentScan(basePackageClasses = Music.class)
3 public class AppConfig {
4 }

还有,Spring并没有提供与<context:annotation-config>相对应的注解。因为组件扫描和自动装配在实际的应用中总是同时启用的。既然@ComponentScan注解同时具有组件扫描与自动装配的功能,也就无需另外提供一个注解专门用于启用自动装配了。

至此,我们已经知道Spring提供了两种配置方式:一种是显式配置;一种是自动配置。显式配置通过XML或Java显式描述Bean的创建信息,再由Spring容器根据具体的配置信息进行Bean的创建和装配。因此,显式配置分为两种:一种是基于XML的显式配置,通常也叫通过XML进行配置;一种是基于Java的显式配置,通常也叫通过Java进行配置。自动配置由组件扫描和自动装配组成。我们需用Java配置类或XML配置文件启用组件扫描,再往类里添加注解,告诉Spring容器哪些类是组件以及如何进行Bean的自动装配。这样,Spring容器扫描组件之后就能进行Bean的自动创建和装配了。因此,自动配置是一种基于注解的自动配置,通常也叫通过注解进行配置。

于是,关于配置的基础知识介绍完了。想必大家还有很多困惑。比如关于自动装配,当Spring容器里存在多个相同类型的Bean时,Spring容器怎么知道应该选用哪个Bean进行装配呢?讲清诸如这样的问题需要花些篇幅,将在“细说Spring”的时候另行讨论。现在,让我们开启新的征程,先来看看Spring是怎样简化JDBC的。

返回目录    下载代码

posted @ 2021-10-24 22:31  林雪波  阅读(266)  评论(0编辑  收藏  举报