我原以为就一个注解, 背后竟然还有 3 个 -- Java 面试必修
引言
前面两章我们先后认识了 SpringBoot 和它的极简配置, 为新手入门的学习降低了门槛, 会基本的使用后, 接下来我们将进一步认识 SpringBoot, 它为何能做到服务秒开, 就来跟随我一起分析 SpringBoot 运行启动的原理吧.
启动原理分 2 章讲解, 本章讲解 @SpringBootApplication 注解部分, 若需了解 SpringApplication.run https://www.itmsbx.com/article/13 方法部分请点击此处
运行启动
工具
SpringBoot 版本: 2.0.4
开发工具: IDEA 2018
- Maven:3.3 9
- JDK:1.8
首先我们看一段启动代码
- @SpringBootApplication
- public class DemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
- }
这不就是个启动类嘛? 从字面理解我都知道 Spring 启动的入口, 有啥好看的. 可别小瞧了这几行代码
开始推理
从上面代码来看,@SpringBootApplication 和 SpringApplication.run 长得很相似, 比较诡异, 所以我们从这两个开始分析, 首先先看注解
- @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 {
- ...
- }
不要被那么多元注解声明所吓到, 仔细查看可以看出其中有 3 个重要的注解
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
所以我觉得, 这 3 个才是真正的幕后主使,@SpringBootApplication 只是他们找来挡枪口的, 他们合体应该等价于 @SpringBootApplication, 如下代码正常运行
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
- public class DemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
- }
哈哈, 经过查询资料发现, Spring Boot 1.2 版之前, 真的是由这 3 个注解出面, 之后呢, 才隐居幕后, 由小喽喽在前面挡枪
@SpringBootConfiguration
首先我们来分析这个, 点开看到源码之后我慌了, 这特么什么都没有, 唯一的疑点在 @Configuration.
好吧, 还挺会伪装, 我们就分析 @Configuration 这个注解吧
用之前 spring 的思维, 我推断这个应该是解决当前 Class 的 xml 配置问题, 凡是经过该注解修饰的, 均被实例化到 Spring 应用程序上下文中, 由 spring 统一管理生命周期, 我说 IoC 大家应该熟悉了吧.
举个例子
传统的 xml 配置对象的写法
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
- default-lazy-init="true">
- <!--bean 定义 -->
- <bean id="userService" class="..UserServiceImpl">
- ...
- </bean>
- </beans>
使用 @Configuration 之后的写法
- @Configuration
- public class SpringConfiguration{
- @Bean
- public UserService userService(){
- return new UserServiceImpl();
- }
- }
任何一个标注了 @Configuration 的 Java 类定义都是一个 JavaConfig 配置类.
任何一个标注了 @Bean 的方法, 其返回值将作为一个 bean 定义注册到 Spring 的 IoC 容器, 方法名将默认成该 bean 定义的 id.
表达依赖注入关系层面
- <bean id="userService" class="..UserServiceImpl">
- <propery name ="dependencyService" ref="dependencyService" />
- </bean>
- <bean id="dependencyService" class="DependencyServiceImpl"></bean>
- @Configuration
- public class SpringConfiguration{
- @Bean
- public UserService userService(){
- return new UserServiceImpl(dependencyService());
- }
- @Bean
- public DependencyService dependencyService(){
- return new DependencyServiceImpl();
- }
- }
如果一个 bean 的定义依赖其他 bean, 则直接调用对应的 JavaConfig 类中依赖 bean 的创建方法即可, 如上方的 dependencyService().
@ComponentScan
这个注解在 Spring 中很重要, 它对应 xml 配置中的元素,@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件 (比如 @Component 和 @Repository 等) 或者 bean 定义, 最终将这些 bean 定义加载到 IoC 容器中.
我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围, 如果不指定, 则默认 Spring 框架实现会从声明 @ComponentScan 所在类的 package 进行扫描.
以前 xml 中是这么定义的, 如下
- <context:component-scan base-package="com.platform.fox.html.**.controller"/>
- <context:component-scan base-package="com.platform.fox.HTML.**.repository"/>
- <context:component-scan base-package="com.platform.fox.HTML.**.service"/>
- <context:component-scan base-package="com.platform.fox.HTML.**.wshandler"/>
注: 所以 SpringBoot 的启动类最好是放在 root package 下, 因为默认不指定 basePackages.
@EnableAutoConfiguration
Enable, 通常来说我们认为它一定是在开启或者支持什么功能, 比如:@EnableScheduling,@EnableCaching, 所以他们要做的事情应该都是相似的, 根据源码判断, 简单概括一下就是, 借助 @Import 的支持, 收集和注册特定场景相关的 bean 定义.
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- @AutoConfigurationPackage
- @Import({AutoConfigurationImportSelector.class})
- public @interface EnableAutoConfiguration {
- String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
- Class<?>[] exclude() default {};
- String[] excludeName() default {};
- }
我理解就是要开船了, EnableAutoConfigurationImportSelector 根据名单把水手, 舵手, 安检员都统一叫过来各就各位. 帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器. 就像一管理员一样
借助于 Spring 框架原有的一个工具类: SpringFactoriesLoader 的支持,@EnableAutoConfiguration 可以智能的自动配置功效才得以大功告成!
EnableAutoConfigurationImportSelector 中自动配置的关键方法如下
- protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
- // 获取符合条件的带有 Configuration 的注解示例类
- List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
- Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
- return configurations;
- }
- SpringFactoriesLoader
所以真正的工作者是 SpringFactoriesLoader,SpringFactoriesLoader 属于 Spring 框架私有的一种扩展方案, 其主要功能就是从指定的配置文件 META-INF/spring.factories 加载配置
- public abstract class SpringFactoriesLoader {
- //...
- public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
- ...
- }
- public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
- ....
- }
- }
配合 @EnableAutoConfiguration 使用的话, 它更多是提供一种配置查找的功能支持, 即根据 @EnableAutoConfiguration 的完整类名 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为查找的 Key, 获取对应的一组 @Configuration 类
spring.factories
上图就是从 SpringBoot 的 autoconfigure 依赖包中的 META-INF/spring.factories 配置文件中摘录的一段内容, 可以很好地说明问题. 我从中随机挑取一个类查看源代码
webMvcAutoConfiguration
所以, 其底层真正实现是从 classpath 中搜寻所有的 META-INF/spring.factories 配置文件, 并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射实例化为对应的标注了 @Configuration 的 JavaConfig 形式的 IoC 容器配置类, 然后汇总为一个并加载到 IoC 容器.
@SpringBootApplication 注解说完了, 接下来我们来分析 SpringApplication.run 方法
来源: https://www.cnblogs.com/itmsbx/p/9696598.html