Springboot 相比于 Spring 的强大之处在于用注解解决了很多 Spring 的设计中需要配置来解决的问题,极大地提高了开发体验。这篇文章主要解释 Springboot 是如何使用注解实现依赖注入的。
在讲解 Springboot 注解之前先来看看 Java 的注解,每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。
Java 注解又称 Java 标注,是 Java 语言 5.0 版本开始支持加入源代码的特殊语法元数据。注解本身并不承载功能,而是绑定在 Java 成员上的一些元数据,可以在特定的时候(编译检查、运行)时被获取和处理。
Java 最开始提供了 4 种元注解,直到 jdk1.8 增加到 7 种,专门负责新注解的创建工作。
@Target @Retention @Documented @Inherited @SafeVarargs @FunctionalInterface @Repeatable
我们来看一个最常见的注解 @Override
的源码:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Before { }
@interface
是 Java 注解申明的关键字,它的元注解翻译过来就是“可以用于 Java 方法,并保留至运行时,且不出现在用户文档中”。
更多使用元注解自定义注解可以参考我写的一个小 demo: https://github.com/ADU-21/java-annotation ,这里就不展开赘述了。
相比于 Java,Springboot 为我们提供更多的注解,用于依赖注入的有· @Autowired
、 @Resource
、 @Qualifier
、 @Service
、 @Controller
、 @Repository
、 @Component
。
上面有提到注解本身只是元数据而不实现功能。那 Springboot 从一个类被标记上注解到被注入到 Ioc 容器中都发生了什么?这就要从 Springboot 的启动流程说起。
springboot 启动流程大图,可以参考: https://www.processon.com/view/link/59812124e4b0de2518b32b6e
首先,很熟悉的是,SpringBoot为我们提供了一个注解,@SpringBootApplication, 打开注解的源码我们可以看到,主要由以下几个注解组成:
@SpringBootConfiguration
注解是由 @Configuration
来注解的,因此也就表示Application类本身就是一个 Bean。虽然 @SpringBootConfiguration
注释中说该注解一个应用中只能用一次,但是配置多个也不会报错,只是建议在一个应用中只用一次,并且该注解也可以与 @Configuration
互换。
@EnableAutoConfiguration
用于开启自动配置的。通过这个注解我们就能使用很多默认的配置来进行程序的开发,这个注解集成了 @EnableAutoConfiguration
注解,这个注解就是 auto-configuration 的核心,源码如下:
1. 其中最核心的就是 @Import(EnableAutoConfigurationImportSelector.class)
。 @Import
注解的作用就是导入其他的配置。
2. 在 Spring 容器启动的时候会利用 ConfigurationClassPostProcessor
进行解析,这个类实现了 BeanDefinitionRegistryPostProcessor
,在容器启动的时候会调用其方法对扫描路径下的 @Configuration
注解进行扫描和解析。
3. 在解析 @Configuration
的时候会去解析 @Import
注解,其中 EnableAutoConfigurationImportSelector
类是 AutoConfigurationImportSelector
的子类, AutoConfigurationImportSelector
中会进行当前根路径下的 jar 包下的 META-INF/spring.factories 文件的扫描和读取。并且将其中涉及到的 Bean 配合 @Conditional
注解添加到 Spring 容器中。
整体的调用链如下:
这是Springboot 注入的重点, @ComponentScan
注解的功能与 Spring 在 xml 文件配置的的功能是一样的,而上面也提到 Application 类放在源码的根目录下,其实就是与这个注解有关。 @ComponentScan
在没有指明basePackages 属性的时候,默认会扫描该注解所在的类的包及其子包下的所有 @Component
注解过的类,包括 @Controller
, @Service
, @Configuration
这些注解。这也就是为什么我们不用做任何配置,就可以将Springboot 应用中的类扫描为 Bean。
调用栈可以参考下图:
具体实现的方法为: ConfigurationClassParser.doProcessConfigurationClass
:
取到所有带 @Component
注解的类之后就是创建实例了,再来看调用栈:
对应前面大图里工厂模式一块:
事实上 Springboot 源码中在 context refresh 里有包含了绝大多数我们关心的和 bean 有关的处理: