Spring Boot如何实现基于注解配置

前言

我们在使用Spring注册Bean时,有很多方式,其中两种常用的配置方式如下

基于注解的配置:使用注解来定义 bean,如 @Component@Autowired@Configuration 等。这种方式相比于基于 XML 的配置更加简洁、易于阅读和维护,也是目前比较流行的一种方式。

基于 Java 配置类的配置:使用 Java 类来定义 bean 和它们之间的依赖关系,如 @Bean@Configuration@Import 等。这种方式也是基于注解的配置方式的一种扩展形式,它允许我们使用 Java 代码来定义 bean,从而更加灵活地控制应用程序的配置。

不难发现,上述两种配置方式的核心都是基于注解实现的,只是应用场景不同。

而我在使用基于Java配置类的配置时,发现在Spring中需要直接或间接实现AnnotationConfigRegistry接口时才能应用基于注解的配置方式;于是我便开始思考:Spring Boot是怎么实现的呢?通过查阅资料以及源码后,以下是我的拙见。

Spring使用注解(Java配置类)配置Bean

在Spring中,我们若想简单使用基于注解的配置方式,需要完成以下几步:

  1. 创建SpringConfig配置类作为传递参数

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("org.heng.dao")
    @Import(JdbcConfig.class)
    public class SpringConfig {
    }

    Spring创建容器时或根据该配置类上的注解信息来配置和创建引用程序上下文中的Bean。这样做可以省去XML配置的繁琐过程。

    JdbcConfig

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class JdbcConfig {

    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306//spring_db")
    private String url;
    @Value("root")
    private String userName;
    @Value("123456")
    private String password;

    @Bean
    public DataSource dataSource(BookDao BookDao){
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(password);
    return ds;
    }
    }
  2. 在Application启动类上使用AnnotationConfigApplicationContext类来创建容器

    1
    2
    3
    4
    5
    6
    7
    public class Application {
    public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    DataSource dataSource = context.getBean(DataSource.class);
    System.out.println(dataSource);
    }
    }

    上述代码中我们将SpringConfig配置类作为参数传入到AnnotationConfigApplicationContext类中,该类会根据接收到的参数来创建容器

  3. 通过上述代码,我们如果有其他Java配置类,只需要在配置类中写好配置信息后在SpringConfig类中的@Import注解上导入即可,不需要在XML中配置了。

Spring Boot为什么可以使用基于注解的配置方式?

在完成上述的Spring使用基于注解的配置方式完成配置之后,我在想SpringBoot是如何实现的呢,于是乎我去查看了一下SpringBoot的源码

在启动类中,我进入到SpringBootApplication类中,发现它没有继承任何类,于是我找到run方法。

1
2
3
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}

我本来以为un()方法中返回的ConfigurableApplicationContext会直接或者间接实现AnnotationConfigRegistry接口的,从而实现Spring Boot基于注解的配置的。于是我查看ConfigurableApplicationContext的类图,发现其并没有直接或间接实现上述的接口。

image-20230520162648142

然后我继续查阅源码,找到了创建创建Spring应用上下文的方法createApplicationContext

1
2
3
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}

这段代码看不出来什么端倪,于是乎我看到方法上面的注释

image-20230520163608571

这段注释的大概内容为

这个方法使用了一个策略模式,它会根据应用程序上下文工厂(ApplicationContextFactory)来创建应用程序上下文。如果应用程序上下文工厂被设置了,它会使用这个工厂来创建应用程序上下文;否则,它会使用适当的默认实现来创建应用程序上下文。

然后注释底部链接跳转了一个方法setApplicationContextFactory,我们继续追踪源码,查看该方法

1
2
3
4
public void setApplicationContextFactory(ApplicationContextFactory applicationContextFactory) {
this.applicationContextFactory = (applicationContextFactory != null) ? applicationContextFactory
: ApplicationContextFactory.DEFAULT;
}

不难发现setApplicationContextFactory方法是用来设置ApplicationContextFactory的,查看该方法的注释

image-20230520164231835

我们可以发现我们在创建Spring应用程序上下文时,如果设置了ApplicationContextFactory,则根据设置的Factory来创建Spring应用程序上下文。如果没有设置,根据应用程序的类型自动选择创建相应的ApplicationContext实现类,其中包括

  • 对于基于Servlet的Web应用程序

    创建AnnotationConfigServletWebServerApplicationContext实例。

  • 对于基于Reactive的Web应用程序

    创建AnnotationConfigReactiveWebServerApplicationContext实例。

  • 对于非Web应用程序

    创建AnnotationConfigApplicationContext实例。

而上述三个实现类,都实现了AnnotationConfigRegistry接口!所以,Spring Boot可以使用基于注解的配置方式的原因就在这里!

在开发中怎么设置ApplicationContextFactory

在Spring Boot应用程序中,可以通过继承SpringApplication类并重写其createApplicationContextFactory方法,来自定义应用程序上下文的创建过程,例如:

1
2
3
4
5
6
7
8
9
public class MyApplication extends SpringApplication {

@Override
protected ApplicationContextFactory createApplicationContextFactory() {
return () -> new MyWebApplicationContext();
}

// ...
}

在这个例子中,MyApplication类继承了SpringApplication类,并重写了其createApplicationContextFactory方法,返回一个lambda表达式,该表达式返回一个自定义的ApplicationContext实现类MyWebApplicationContext的实例。通过这种方式,可以在Spring Boot应用程序中自定义应用程序上下文的创建过程,满足不同的应用场景需求。

AnnotationConfigRegistry接口的作用和意义(补充)

AnnotationConfigRegistry 接口是 Spring Framework 中的一个接口,它继承了 BeanDefinitionRegistry 接口,并增加了一些注册的方法。主要作用是用于注册基于注解的配置类(如 @Configuration 注解的类)。

该接口的意义在于为开发者提供了一种便捷的方式,用于将基于注解的配置类注册到 Spring 容器中。在使用基于注解的配置时,开发者需要将其所在的类注册到 Spring 容器中,通常是通过 AnnotationConfigApplicationContext 或者其他类似的上下文来实现。而这些上下文都实现了 AnnotationConfigRegistry 接口,因此可以调用该接口中的方法来实现注册。

AnnotationConfigRegistry 接口中的方法有:

  • register(Class<?>... annotatedClasses) :注册一个或多个基于注解的配置类。

  • scan(String... basePackages) :扫描指定的包(及其子包)下的所有 Bean 定义,并将其加入到 Spring 容器中。

通过使用 AnnotationConfigRegistry 接口,开发者可以更加灵活地管理基于注解的配置类,并将其注册为 Spring Bean,从而实现更加方便、简单、快捷的配置方式。

总结

在Spring Boot启动时,它会通过SpringApplication类的createApplicationContext方法来创建Spring应用程序上下文。如果设置了ApplicationContextFactory,它将使用该工厂来创建Spring应用程序上下文,否则将根据应用程序的类型自动选择创建相应的ApplicationContext实现类。

Spring Boot基于注解的配置方式的实现原理是基于ApplicationContextFactory接口和AnnotationConfigRegistry接口结合实现的。ApplicationContextFactory接口用于创建Spring应用程序上下文的工厂,而AnnotationConfigRegistry接口则是用于注册基于注解的配置类的接口。这两者的结合使得Spring Boot能够使用基于注解的配置方式,从而简化了应用程序的配置过程。