@SpringBootApplication 漫谈,理解 Spring 注解驱动编程

简介: 前言几乎所有的 Spring Boot 应用都会在启动类上添加 @SpringBootApplication,可以说它是 Spring Boot 应用最核心的注解了。Spring Boot 基于 Spring Framework,@SpringBootApplication 也不例外,Spring 注解核心知识在《重学 Spring》专栏实际上已经写了多篇,这篇希望在理解 @SpringBootApplication 的基础上将前面的内容进行串联,以便达到融会贯通的效果。

前言


几乎所有的 Spring Boot 应用都会在启动类上添加 @SpringBootApplication,可以说它是 Spring Boot 应用最核心的注解了。Spring Boot 基于 Spring Framework,@SpringBootApplication 也不例外,Spring 注解核心知识在《重学 Spring》专栏实际上已经写了多篇,这篇希望在理解 @SpringBootApplication 的基础上将前面的内容进行串联,以便达到融会贯通的效果。


理解 @SpringBootApplication 语义


即便初学者不理解 @SpringBootApplication,通常来说也会把这个注解添加到启动类,然后将启动类放在根包下面。


@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}


可以说上面的代码,就是 Spring Boot 应用的一个模板,所有的 Spring Boot 应用都有类似的代码。


关于 @SpringBootApplication 的注解语义,Spring 官网在 “3.6 Using the @SpringBootApplication Annotation” 描述如下:


许多 Spring Boot 开发者喜欢在他们的应用中使用自动装配、组件扫描以及在配置类上定义额外的配置。单个 @SpringBootApplication 注解已经用来启动这三个特性,即:


EnableAutoConfiguration:启用 Spring Boot 自动装配机制。

@ComponentScan:启用应用中包下面的 @Component 扫描功能。

@Configuration:允许在上下文中注册 bean 或导入额外的配置类。

也就是说 @SpringBootApplication 相当于三个注解的组合,我们来看下这个注解的定义。


/**
 * @since 1.4.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...省略属性
}


可以看到 @SpringBootApplication 被 @EnableAutoConfiguration、@ComponentScan 及 @SpringBootConfiguration 同时标注。


那么官网描述的 @Configuration 在哪呢?根据直觉我们看下 @SpringBootConfiguration。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    ... 省略属性
}


可以看到 @SpringBootConfiguration 被 @Configuration 标注,因此 Spring 官网提到 @Configuration 注解。


不过 @SpringBootApplication 中的 @ComponentScan 注解并没有使用默认的属性配置,而是指定了 excludeFilters 属性值用来过滤扫描的组件,TypeExcludeFilter 根据用户自定义的 TypeExcludeFilter bean 过滤组件,AutoConfigurationExcludeFilter 用于跳过自动装配的配置类。


事实上,@SpringBootApplication 这个注解也不是在最初的版本就有的,而是在 Spring Boot 1.4 版本添加到 Spring Boot 框架中的,在此之前只能使用组合 @SpringBootApplication 注解的三个注解,它们的语义是保持一致的,如下所示。


@Configuration
@ComponentScan
@EnableAutoConfiguration
//@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}


由于 @Configuration、@ComponentScan、@EnableAutoConfiguration 这三个注解使用较为频繁 Spring 官方才将其组合到一个注解中。


Spring 注解编程模型


注解作为元数据,只有被读取到才能发挥其作用,那么 Spring Boot 如何读取 @SpringBootApplication 注解的呢?


将 @SpringBootApplication 注解标注的 class 传入 SpringApplication.run 方法的参数后,Spring Boot 便会将这个 class 注册为 bean,之后 Spring 便会对这个 bean 进一步的处理,例如根据 @ComponentScan 进行组件扫描,根据 @EnableAutoConfiguration 进行自动装配。这也意味着不必一定要把 @SpringBootApplication 添加到启动类,假如我们想把 @SpringBootApplication 添加到其他配置类,我们可以如下更改。


@SpringBootApplication
public class Config {
}
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(Config.class, args);
    }
}


事实上,@SpringBootApplication 只是将多个注解标注到自身,Spring Boot 也没有对这个注解做额外的处理,我们甚至可以自定义一个注解替代 @SpringBootApplication,示例如下。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface CustomSpringBootApplication {
}
@CustomSpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}


上述我们自定义的 @CustomSpringBootApplication 注解可以达到类似 @SpringBootApplication 注解的效果,这得益于 Spring 的注解编程模型,其中一些概念如下。


1. 注解与元注解

标注在注解上的注解称为元注解,例如 @ComponentScan 注解标注在了 @SpringBootApplication 注解上,@ComponentScan 是 @SpringBootApplication 注解的元注解,同时称 @SpringBootApplication 被 @ComponentScan 注解元标注。


2. 组合注解

被一个或多个注解标注的注解被称为组合注解,如 @SpringBootApplication 注解,组合注解的目的在于组合多个注解具备的功能。例如 @SpringBootApplication 自动具备组件扫描、配置类、启动自动装配的功能。Spring 中的注解允许多层次 “继承”,例如 @Configuration 标注了 @SpringBootConfiguration,@SpringBootConfiguration 又标注了 @SpringBootApplication,因此 @SpringBootApplication 标注的类成为一个配置类。


3. 属性别名与属性重写

属性别名用于将一个属性指定为另一个属性的别名。使用 @AliasFor 注解进行标注。例如 Spring MVC 中的 @RequestMapping 注解。


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
  @AliasFor("path")
  String[] value() default {};
  @AliasFor("value")
  String[] path() default {};
}


path 和 value 属性上都标注了 @AliasFor 注解,它们互为对方的别名,使用时指定其中一个属性值即可,如果使用指定多个产生歧义则会报错。


由于注解不支持继承,用户无法直接指定元注解中的属性值,Spring 注解编程模型允许注解中的属性重写元注解中的属性。注解中的属性名称与元注解中的属性名称一致即可重写元注解属性,如果不一致还可以使用 @AliasFor 注解标注在注解属性上用来指定重写哪个元注解的哪个属性。例如对于 @SpringBootApplication 注解。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};
}


@SpringBootApplication 注解的 scanBasePackages 属性就重写了元注解 @ComponentScan 的 basePackages 属性。


注解编程模型的这个名词来自于 Spring 在 GitHub 上面的一篇文章,我对其做了翻译,更多内容可以参考《Spring 注解编程模型》。Spring 内部通过注解编程模型可以轻松获取给定注解或元注解的属性值,而不必关心这个这个注解直接标注还是元标注在类上。如果你想了解 @AliasFor 的更多实现细节,还可以参考 《Spring @AliasFor 实现源码分析》。


Spring 组件扫描


@SpringBootApplication 注解上标注了 @ComponentScan 注解,因此 @SpringBootApplication 可用于扫描组件,那么扫描组件的动作发生在什么时候呢?Spring 又是如何进行组件扫描的?


要理解 Spring 的组件扫描,还得看 Spring 的本质与历史。Spring 本质上只是一个管理 bean 的容器,并提供了依赖注入与依赖获取的能力,因此需要定义哪些 bean 交给 Spring 容器进行管理。


Spring 诞生初期,Java 注解还未诞生,Spring 采用业界广泛使用的 xml 作为配置文件。Spring 1.2 版本,第一个注解 @Transactional 诞生。到了 2.5 版本,Spring 才开始支持在 xml 配置 <context:component-scan> 对类路径下进行扫描,并将标注了 @Component 的类注册为 bean。到了 Spring 3.0 版本, @Configuration 注解诞生,此时只能配合 @Import、@ImporResource 或者 @Bean 注解有限的配置一些 bean。直到 Spring 3.1 版本,@ComponentScan 注解才诞生,并提供了基于注解的 ApplicationContext 实现 AnnotationConfigApplicationContext。


回到 Spring 对类路径下的组件扫描,流程大概如下,当使用 <context:component-scan> 或直接使用基于注解的 ApplicationContext 对类路径扫描后,Spring 会查找符合条件的组件类,并将满足条件的这些类注册为 Spring bean,包括满足 @Conditional 及 @ComponentScan#excludeFilter 条件。然后使用 ConfigurationClassPostProcessor 在应用上下文的生命周期 bean 实例化前的阶段对配置类进一步的处理,配置类包括标注或元标注了 @Component、@ComponentScan、@Import 或 @ImportResource 注解的组件类,或存在 @Bean 注解的方法的组件类。


Spring Boot 将 @SpringBootApplication 标注的类注册为 bean 之后,ConfigurationClassPostProcessor 就会对这个类进一步处理了,当发现存在注解或元注解 @ComponentScan 就会进一步处理,扫描并注册 bean。


关于组件扫描,我前面同样写了几篇文章具体介绍,包括 《Spring 类路径下 Bean 扫描实现分析》、《Spring 条件注解 @Conditional 使用及其底层实现》,感兴趣的小伙伴可自行翻阅。


Spring Boot 自动装配.


前文提到使用 @SpringBootApplication 注解后会自动开启自动装配功能,正是因为 @SpringBootApplication 被 @EnableAutoConfiguration 标注。


@EnableAutoConfiguration 使用了 Enable* 编程模型,主要利用 @Import 及 @Conditional 根据某些条件自动化装配一些 bean。关于 @Enable* 编程模型,可以先参考文章《Spring 框架中的 @Enable* 注解是怎样实现的?》 做更多了解。


自动装配可以理解为当某些条件满足后 Spring 会自动注册一些 bean,而不需要手动进行配置。例如,当我们添加 spring-boot-starter-web 依赖后,Spring Boot 发现类路径下存在 Tomcat,就会自动注入一些 Web 相关的 bean,从而简化用户的配置。


如果 Spring Boot 未提供自动装配功能,我们则只能通过 @Enable* 编程模型手动的将某些功能模块的 bean 进行注册,Spring Boot 将一些与业务无关的 bean 的装配工作简化到 @EnableAutoConfiguration 注解后可谓是大大降低了 Spring Boot 的上手门槛。


作为 Spring Boot 核心特性的自动装配,我们这里先简单理解,下面会对其单独介绍。


总结

这篇我们在介绍 @SpringBootApplication 的基础上,介绍了有关这个注解更底层的一些内容,包括注解编程模型、组件扫描、自动装配。如果你还对 @SpringBootApplication 还有疑问,也可以翻阅我前面的文章或给我留言。


目录
相关文章
|
26天前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
104 1
|
1月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
22天前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
44 0
|
7天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
1月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
1月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
1月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
1月前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
|
20天前
|
Java Spring XML
掌握面向切面编程的秘密武器:Spring AOP 让你的代码优雅转身,横切关注点再也不是难题!
【8月更文挑战第31天】面向切面编程(AOP)通过切面封装横切关注点,如日志记录、事务管理等,使业务逻辑更清晰。Spring AOP提供强大工具,无需在业务代码中硬编码这些功能。本文将深入探讨Spring AOP的概念、工作原理及实际应用,展示如何通过基于注解的配置创建切面,优化代码结构并提高可维护性。通过示例说明如何定义切面类、通知方法及其应用时机,实现方法调用前后的日志记录,展示AOP在分离关注点和添加新功能方面的优势。
30 0
|
20天前
|
Java Spring 容器
彻底改变你的编程人生!揭秘 Spring 框架依赖注入的神奇魔力,让你的代码瞬间焕然一新!
【8月更文挑战第31天】本文介绍 Spring 框架中的依赖注入(DI),一种降低代码耦合度的设计模式。通过 Spring 的 DI 容器,开发者可专注业务逻辑而非依赖管理。文中详细解释了 DI 的基本概念及其实现方式,如构造器注入、字段注入与 setter 方法注入,并提供示例说明如何在实际项目中应用这些技术。通过 Spring 的 @Configuration 和 @Bean 注解,可轻松定义与管理应用中的组件及其依赖关系,实现更简洁、易维护的代码结构。
25 0