基于Java的显式配置

我们知道基于XML的显式配置就是采用XML描述Bean的创建信息,告诉Spring容器具体需要创建哪些Bean。自然而然的,基于Java的显式配置则是采用Java这种编程语言描述Bean的创建信息,告诉Spring容器具体需要创建哪些Bean。至于如何描述;让我们趁热打铁,紧接前文,看看同样的项目能用Java怎么配置;进而学习基于Java的显式配置的基础知识。为此,请打开music-player项目,新建AppConfig类如下:

1 package com.dream;
2 
3 import org.springframework.context.annotation.*;
4 
5 @Configuration
6 public class AppConfig {
7 }

乍眼一瞧,大家心里肯定得犯嘀咕:“咋写了个啥也没有的类咧!”可是,当我们静下心来仔细瞧上一瞧,就会惊人地发现这个荒芜的类上有个神秘的@Configuration注解。

这是怎么回事呢?

原来,@Configuration是Spring提供的一个注解,可以把某个类标为配置类,使之具有向Spring容器提供配置信息的能力。由此可见,AppConfig既不是一个啥也没有的类,也不是一个普通的类,而是一个具有@Configuration注解的配置类。只因我们尚未往该类里添加配置信息,所以空落落的。既然这样,就让我们敲些代码充实一下该类:

 1 package com.dream;
 2 
 3 import org.springframework.context.annotation.*;
 4 
 5 @Configuration
 6 public class AppConfig {
 7     @Bean(name = "music")
 8     public Music produceMusic() {
 9         var music = new Music();
10         music.setMusicName("执着");
11         return music;
12     }
13 
14     @Bean(name = "player")
15     public Player producePlayer() {
16         var music = this.produceMusic();
17         var player = new Player();
18         player.setMusic(music);
19         return player;
20     }
21 }

代码非常简单,就实现了两个方法:一个用于创建Music类型的对象;一个用于创建Player类型的对象。特别引人注目的是,这两个方法无一例外,都带有一个神秘的@Bean注解。

这是怎么回事呢?

原来,@Bean也是Spring提供的一个注解,可以把某个方法标为配置方法,使之能被Spring容器发现之后进行调用,从而达到创建Bean的目的。创建出来的Bean其Id默认是配置方法的方法名。当然,我们也能设置@Bean注解的name属性,指定我们想要的Id

因此,AppConfig配置类实现的produceMusic方法带有@Bean(name="music")注解,能被Spring容器发现之后进行调用,创建一个类型为Music,id为music的Bean;实现的producePlayer方法带有@Bean(name="player")注解,能被Spring容器发现之后进行调用,创建一个类型为Player,id为player的Bean

于是,AppConfig配置类完成了,可以把它交给Spring容器了。如下:

 1 package com.dream;
 2 
 3 import org.springframework.context.annotation.*;
 4 
 5 public class Main {
 6     public static void main(String[] args) {
 7         try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
 8             var player = context.getBean("player", Player.class);
 9             player.play();
10             player.pause();
11         }
12     }
13 }

这里用到AnnotationConfigApplicationContext。AnnotationConfigApplicationContext是Spring提供的一种Spring应用上下文实现,具有加载配置类,调用配置类里的配置方法创建Bean的能力。其构造函数签名如下:

public AnnotationConfigApplicationContext(Class<?>... componentClasses)

可以看到构造函数接受一个Class<?>...类型的参数,能够传入多个配置类。因此,我们创建Spring应用上下文实例的时候传入了AppConfig.class。这样,Spring应用上下文就能加载AppConfig配置类进行Bean的创建了。之后,我们使用Spring应用上下文里的Bean顺利实现了音乐播放器。

可是,完成实现之后我们却开始困惑了。

前文曾经提及,Spring容器创建的Bean默认是单例的。可是,Spring容器调用配置方法创建Bean时,不是调用一次方法就创建一个对象,调用两次方法就创建两个对象吗?如此,Spring容器创建的Bean怎么可能还是单例的呢?

为了解开这个迷题,让我们修改一下AppConfig配置类,添加producePlayer_2方法如下:

 1 package com.dream;
 2 
 3 import org.springframework.context.annotation.*;
 4 
 5 @Configuration
 6 public class AppConfig {
 7     @Bean(name = "music")
 8     public Music produceMusic() {
 9         var music = new Music();
10         music.setMusicName("执着");
11         return music;
12     }
13 
14     @Bean(name = "player")
15     public Player producePlayer() {
16         var music = this.produceMusic();
17         var player = new Player();
18         player.setMusic(music);
19         return player;
20     }
21 
22     @Bean(name="player_2")
23     public Player producePlayer_2() {
24         Music music = this.produceMusic();
25         Player player = new Player();
26         player.setMusic(music);
27         return player;
28     }
29 }

现在,producePlayer方法和producePlayer_2方法都能创建player对象,并且创建player对象时都会调用produceMusic方法创建music对象进行注入。于是问题来了,producePlayer方法调用produceMusic方法创建的music对象和producePlayer_2方法调用produceMusic方法创建的music对象是同一个吗? 

当然是的。实际上,Spring应用上下文瞧见@Configuration注解之后并不会直接加载配置类,而是基于配置类生成一个代理类,基于带有@Bean注解的配置方法生成代理方法。因此,每次调用produceMusic方法创建music对象时并不是直接调用produceMusic方法创建music对象,而是调用代理类里的代理方法进行创建。

代理方法创建Bean之前会先判断一下即将创建的Bean Spring应用上下文里是不是已经有了。如果已经有了,则直接返回Spring应用上下文里的Bean,不再创建;如果还没有,则调用配置方法进行创建。由是我们的困惑解开了,Spring应用上下文加载配置类之后创建的Bean默认还是单例的。

那么,producePlayer方法创建的player对象和producePlayer_2方法创建的player对象也是同一个吗?

当然不是。producePlayer方法和producePlayer_2方法是两个不同的方法,不同的方法运行不同的代码创建的Bean当然是不同的。至于代理,则是Java这门编程语言的一个高级特性,超出本书的讨论范围。如果大家对此不太了解又想深入学习的话,建议大家阅读一下关于代理的Java书籍或文章。这里不作介绍。

至此,基于Java的显式配置的基础知识介绍完了。和基于XML的显式配置相比,基于Java的显式配置至少具有两大优点。首先,基于Java的显式配置是类型安全的。这意味着编译代码即能暴露一些写错了的配置信息。比如某个类名写错了,编译一下就能知道。而XML只是文本文件,诸如这样的问题只在运行的时候才能发现,编译的时候是发现不了的。其次,基于Java的显式配置功能更加强大。只要我们愿意,我们可往配置类里写任何代码进行对象的创建。虽然并不建议。

更加令人惊喜的是,除了显式配置,Spring还提供了自动配置这样一种更加让人着迷的配置方式。至于自动配置有多迷人,我们将在下章揭开它的面纱。欢迎大家继续阅读,谢谢大家!

返回目录    下载代码

posted @ 2021-10-24 21:27  林雪波  阅读(262)  评论(0编辑  收藏  举报