`

SpringBoot自动装配原理解析

阅读更多

本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等

我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序:

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

和我们之前使用普通Spring时繁琐的配置相比简直不要太方便,那么你知道SpringBoot实现这些的原理么

首先我们看到类上方包含了一个@SpringBootApplication注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

这个注解上边包含的东西还是比较多的,咱们先看一下两个简单的热热身

@ComponentScan 注解

@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

这个注解咱们都是比较熟悉的,无非就是自动扫描并加载符合条件的Bean到容器中,这个注解会默认扫描声明类所在的包开始扫描,例如:
cn.shiyujun.Demo类上标注了@ComponentScan 注解,则cn.shiyujun.controllercn.shiyujun.service等等包下的类都可以被扫描到

这个注解一共包含以下几个属性:

basePackages:指定多个包名进行扫描
basePackageClasses:对指定的类和接口所属的包进行扫
excludeFilters:指定不扫描的过滤器
includeFilters:指定扫描的过滤器
lazyInit:是否对注册扫描的bean设置为懒加载
nameGenerator:为扫描到的bean自动命名
resourcePattern:控制可用于扫描的类文件
scopedProxy:指定代理是否应该被扫描
scopeResolver:指定扫描bean的范围
useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行检测

@SpringBootConfiguration注解

这个注解更简单了,它只是对Configuration注解的一个封装而已

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

EnableAutoConfiguration注解

这个注解可是重头戏了,SpringBoot号称的约定大于配置,也就是本文的重点自动装配的原理就在这里了

@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

简单概括一下,这个注解存在的意义就是:利用@Import注解,将所有符合自动装配条件的bean注入到IOC容器中,关于@Import注解原理这里就不再阐述,感兴趣的同学可以参考此篇文章:Spring @Import注解源码解析

进入类AutoConfigurationImportSelector,观察其selectImports方法,这个方法执行完毕后,Spring会把这个方法返回的类的全限定名数组里的所有的类都注入到IOC容器中

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

观察上方代码:

  1. 第一行if时会首先判断当前系统是否禁用了自动装配的功能,判断的代码如下:
protected boolean isEnabled(AnnotationMetadata metadata) {
       return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
   }
  1. 如果当前系统禁用了自动装配的功能则会返回如下这个空的数组,后续也就无法注入bean了
private static final String[] NO_IMPORTS = new String[0];
  1. 此时如果没有禁用自动装配则进入else分枝,第一步操作首先会去加载所有Spring预先定义的配置条件信息,这些配置信息在org.springframework.boot.autoconfigure包下的META-INF/spring-autoconfigure-metadata.properties文件中

  2. 这些配置条件主要含义大致是这样的:如果你要自动装配某个类的话,你觉得先存在哪些类或者哪些配置文件等等条件,这些条件的判断主要是利用了@ConditionalXXX注解,关于@ConditionalXXX系列注解可以参考这篇文章:SpringBoot条件注解@Conditional

  3. 这个文件里的内容格式是这样的:

org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,io.micrometer.core.instrument.MeterRegistry
org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
  1. 具体的加载代码就不列出了,无法就是个读取配置文件
  2. 这里放个加载之后的结果图:
    1
  3. 获取@EnableAutoConfiguration注解上的exclude、excludeName属性,这两个属性的作用都是排除一些类的
  4. 这里又是关键的一步,可以看到刚才图片中spring-autoconfigure-metadata.properties文件的上方存在一个文件spring.factories,这个文件可就不止存在于org.springframework.boot.autoconfigure包里了,所有的包里都有可能存在这个文件,所以这一步是加载整个项目所有的spring.factories文件。这个文件的格式是这样的
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration
  1. 这里存在一个知识点,SpringBoot中的star就是依靠这个文件完成的,假如我们需要自定义一个SpringBoot的Star,就可以在我们的项目的META-INF文件夹下新建一个spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.shiyujun.TestAutoConfiguration

这样当别的项目依赖我们的项目时就会自动把我们的TestAutoConfiguration类注入到Spring容器中

  1. 删除重复的自动配置类
  2. 下面三行就是去除我们指定排除的配置类
  3. 接着这一行的逻辑稍微复杂一些,主要就是根据加载的配置条件信息来判断各个配置类上的@ConditionalXXX系列注解是否满足需求
  4. 最后就是发布自动装配完成事件,然后返回所有能够自动装配的类的全限定名

到了这里我们已经把SpringBoot自动装配的原理搞清楚了,但是总感觉差点什么,那我们从这些自动装配的类里面挑一个我们比较熟悉的关于Servlet的类来看看咋回事吧:

@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
public class ServletEndpointManagementContextConfiguration {
    public ServletEndpointManagementContextConfiguration() {
    }

    @Bean
    public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(WebEndpointProperties properties) {
        Exposure exposure = properties.getExposure();
        return new ExposeExcludePropertyEndpointFilter(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude(), new String[0]);
    }

    @Configuration
    @ConditionalOnClass({ResourceConfig.class})
    @ConditionalOnMissingClass({"org.springframework.web.servlet.DispatcherServlet"})
    public class JerseyServletEndpointManagementContextConfiguration {
        public JerseyServletEndpointManagementContextConfiguration() {
        }

        @Bean
        public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
            return new ServletEndpointRegistrar(properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
        }
    }

    @Configuration
    @ConditionalOnClass({DispatcherServlet.class})
    public class WebMvcServletEndpointManagementContextConfiguration {
        private final ApplicationContext context;

        public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) {
            this.context = context;
        }

        @Bean
        public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
            DispatcherServletPathProvider servletPathProvider = (DispatcherServletPathProvider)this.context.getBean(DispatcherServletPathProvider.class);
            String servletPath = servletPathProvider.getServletPath();
            if (servletPath.equals("/")) {
                servletPath = "";
            }

            return new ServletEndpointRegistrar(servletPath + properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
        }
    }
}

自上而下观察整个类的代码,你会发现这些自动装配的套路都是一样的

  1. 如果当前是Servlet环境则装配这个bean
  2. 当存在类ResourceConfig以及不存在类DispatcherServlet时装配JerseyServletEndpointManagementContextConfiguration
  3. 当存在DispatcherServlet类时装配WebMvcServletEndpointManagementContextConfiguration
  4. 接下来如果还有面试官问你,你会了么?

1

1
0
分享到:
评论

相关推荐

    springBoot自动装配原理以及starter技术.pdf

    本文将深入解析Spring Boot自动装配的原理,并探讨starter技术。 首先,我们来看`@Import`注解,它是Spring框架的一个核心注解,用于导入其他配置类或组件。当我们在一个@Configuration注解的类中使用`@Import`导入...

    SpringBoot自动装配原理(简单易懂)

    SpringBoot自动装配原理详解 SpringBoot作为Java开发中的一款热门框架,因其简化配置、快速启动和集成众多功能的特性,深受开发者喜爱。其中,自动装配是SpringBoot的核心特性之一,它极大地减少了手动配置的工作量...

    SpringBoot启动及自动装配原理过程详解

    SpringBoot启动及自动装配原理过程详解 SpringBoot启动及自动装配原理过程详解是 SpringBoot 框架中一个非常重要的机制,它控制着整个应用程序的启动和自动装配过程。下面将详细介绍 SpringBoot 启动及自动装配...

    springboot源码解析(二):自动装配原理1

    总的来说,Spring Boot的自动装配原理包括以下几个步骤: 1. 扫描并注册bean定义。 2. 根据条件判断启用哪些自动配置类。 3. 应用`@ConfigurationProperties`绑定配置属性。 4. 使用`@Autowired`和`@Qualifier`进行...

    第三节-springboot源码解析-王炸篇.pdf

    Spring Boot的自动装配原理涉及到Spring Boot的核心特性,它简化了基于Spring的应用开发,通过自动配置减少了大量的配置工作。 在Spring Boot中,自动装配主要依赖于一系列的Starters。这些Starters为常用的依赖...

    深度剖析Spring Boot自动装配机制实现原理(csdn)————程序.pdf

    在Spring Boot中,自动装配是其核心特性之一,极大地简化了应用程序的配置。自动装配的目标是减少手动配置bean,让Spring框架能够根据项目依赖自动识别并管理bean。这个过程主要由`@EnableAutoConfiguration`注解...

    SpringBoot2的最佳实践 SpringBoot2基础入门+底层注解+自动配置+高级特性与原理解析

    课程内容以时下最新的技术点为主线,逐步的进行展开,分别细化的讲解了SpringBoot2基础课程,SpringBoot2底层注解,SpringBoot2自动化配置,SpringBoot2请求处理,SpringBoot2高级特性和原理分析,是不可错过的...

    SpringBoot源码精讲msb【完结】共21小时

    接下来是“springboot自动装配源码解析.mp4”,这部分内容深入解析了SpringBoot的自动配置机制,这是SpringBoot的一大特色。主要知识点可能包括: 1. **@EnableAutoConfiguration**:这个注解触发了SpringBoot的...

    某果学院springboot 源码解析

    #### 二、Spring Boot自动装配 Spring Boot 提供了一种机制,通过 `@SpringBootApplication` 注解简化了自动配置的过程。 **2.1 @SpringBootApplication注解** `@SpringBootApplication` 是一个组合注解,包含了...

    SpringBoot基础.pdf

    五、自动装配原理 1. @SpringBootApplication注解 -@SpringBootApplication是组合注解,包含了@Configuration、@EnableAutoConfiguration、@ComponentScan。 - @Configuration表示这个类可以作为Spring容器中的...

    SpringBoot.docx

    - **@Autowire**:自动装配 Bean,根据类型找到对应的 Bean 进行注入。 - **@Resource**:与 `@Autowire` 类似,但默认按名称注入,找不到时按类型注入。 - **@Bean**:标记方法为 Bean 生产者,返回的对象将被添加...

    SpringBoot揭秘 快速构建微服务体系-扶墙大师-高清-带目录pdf

    在Java开发领域,SpringBoot因其简化配置、自动装配和开箱即用的特性,已经成为主流的轻量级框架之一,尤其在微服务领域有着广泛的应用。 书中首先介绍了SpringBoot的基础知识,包括核心概念、起步依赖、Starter ...

    springboot面试题.docx

    **Spring Boot 自动装配原理** Spring Boot 的自动装配是通过 `@SpringBootApplication` 注解实现的。这个注解包含了 `@ComponentScan`、`@EnableAutoConfiguration` 和 `@Configuration` 三个注解。其中,`@...

    springboot全部例子

    8. `springboot_getbean`:在 Spring 中,我们可以使用 `@Autowired` 注解来自动装配 Bean,或者通过 `ApplicationContext` 获取 Bean。这个示例可能涉及 Spring 的依赖注入机制。 9. `springboot_mongotemplate`:...

    基于springboot的时间管理系统源码.zip

    在时间管理系统中,可能通过@Autowired注解自动装配数据库连接、定时任务调度器等组件。 数据库方面,通常SpringBoot会配合Spring Data JPA或MyBatis等ORM框架来操作数据。Spring Data JPA提供了一种简化JPA(Java ...

    springboot.zip

    在"springboot.zip"中,Mapper接口与XML文件定义了数据操作,而SpringBoot会通过自动扫描和装配,使得这些接口能在运行时与数据库交互。 三、MySQL数据库 MySQL作为开源的关系型数据库,因其性能优秀、易用性高而在...

    使用Java自定义注解模拟实现SpringBoot相关注解.zip

    `@Autowired`是Spring框架中的一个关键注解,用于自动装配bean。当我们想在类中注入某个依赖时,而无需明确地指定bean的名字,可以使用`@Autowired`。要模拟实现这个功能,我们需要创建一个自定义注解,例如`@...

    java基于springboot+vue的前后端分离项目(带报告).zip

    在实际开发过程中,开发者需要掌握SpringBoot的启动配置、自动装配原理、MVC框架、数据访问(如JPA或MyBatis)以及Vue.js的组件系统、路由管理、状态管理(Vuex)等相关技能。通过这个项目,学习者可以深入理解前后...

    基于springboot的人力资源管理系统源码数据库.doc

    - 自动配置:减少XML配置,通过注解实现自动装配; - 开箱即用:无需复杂配置,即可启动应用程序; - 内嵌Servlet容器:无需部署到Tomcat等容器中,可独立运行; - 易于监控、调整和维护:提供健康检查、指标...

Global site tag (gtag.js) - Google Analytics