Spring Boot Starter原理

Spring Boot 是遵循约定优于配置这个理念产生的,将已有的Spring组件整合起来,对一些常见的应用场景进行了默认的配置。

简化方式包括:基于场景启动器,基于注解开发。

为了阅读方便,本篇主要讲解基于场景。

一、基于场景(starter)

SpringBoot拥有很多方便使用的starter(Spring提供的starter命名规范spring-boot-starter-xxx.jar,第三方提供的starter命名规范xxx-spring-boot-starter.jar),比如spring-boot-starter-log4j、mybatis-spring-boot-starter.jar等,各自都代表了一个相对完整的功能模块。

SpringBoot-starter是一个集成接合器,完成两件事:

  • 引入模块所需的相关jar包
  • 自动配置各自模块所需的属性

1.1 引入模块所需的相关jar包

其原理是Maven继承依赖关系。

项目 pom.xml:

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

 依赖的 spring-boot-starter-web 的 pom.xml:

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.4.5</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.4.5</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.4.5</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.3.6</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.6</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

注意被依赖的pom文件中的依赖 scope 的值为 compile ,只有依赖范围为compile时可以访问。

1.2 自动配置各自模块所需的属性

starter 除了要整合导入相关的依赖外,还要能够自动配置。

自动配置的思路:

  • 在每个 starter 内都有个自动配置类
  • 将 starter 内的自动配置类导入到项目的配置类

下面从项目的主入口追踪这一过程:

主程序最大的特点就是其类上放了一个@SpringBootApplication注解,这也正是SpringBoot项目启动的核心;

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

 @SpringBootApplication,可以发现它是一个组合注解;

@SpringBootConfiguration//核心
@EnableAutoConfiguration//核心
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@EnableAutoConfiguration,顾名思义,这个注解一定和自动配置相关;

@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationImportSelector.class)//自动配置导入选择

 AutoConfigurationImportSelector,这个类中存在一个方法可以帮我们获取所有的配置;

/*获取候选的配置*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                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;
    }

实际上它返回了一个List,这个List是由loadFactoryNames()方法返回的,其中传入了一个getSpringFactoriesLoaderFactoryClass(),我们可以看看这个方法的内容;

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

getCandidateConfigurations 方法,其中有一条断言,代表从项目路径中找到所有的(包括依赖中的)META-INF/spring.factories文件然后加载,具体过程如下;


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        // 即 org.springframework.boot.autoconfigure.EnableAutoConfiguration
        String factoryTypeName = factoryType.getName();
        // 返回 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                // 要读取的资源路径
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
                // 将文件转换成 k-v 格式
                ...
            } catch (IOException var14) {

            }
        }
}

META-INF/spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的配置都是常会用场景的自动配置类;

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
...

 其中,WebMvcAutoConfiguration 的内容如下,作用是自动配置 WebMvc:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
    public static final String DEFAULT_PREFIX = "";
    public static final String DEFAULT_SUFFIX = "";
    private static final String SERVLET_LOCATION = "/";

    public WebMvcAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},
        matchIfMissing = false
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    @Bean
    @ConditionalOnMissingBean({FormContentFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.formcontent.filter",
        name = {"enabled"},
        matchIfMissing = true
    )
    public OrderedFormContentFilter formContentFilter() {
        return new OrderedFormContentFilter();
    }


    interface ResourceHandlerRegistrationCustomizer {
        void customize(ResourceHandlerRegistration registration);
    }
    ...

}

这里涉及到两个问题,一是 META-INF/spring.factories 文件中配置了这么多的自动配置类,为何只有我们依赖的生效了,二是xxxAutoConfiguration是如何自动配置的,或者说 application.properties 中的配置项都有哪些?

第一个问题通过 @Condition 实现

如上文的 @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) 和 @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

常用 Condition 注解:

注解解析
@ConditionalOnBean当容器里有指定的Bean的条件下。
@ConditionalOnClass当类路径下有指定的类的条件下。
@ConditionalOnExpression基于SpEL表达式作为判断条件。
@ConditionalOnJava基于JVM版本作为判断条件。
@ConditionalOnJndi在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication当前项目不是Web项目的条件下。
@ConditionalOnProperty指定的属性是否有指定的值。
@ConditionalOnResource类路径是否有指定的值。
@ConditionalOnSingleCandidate当指定Bean在容器中只有一个, 或者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplicatio当前项目是Web项目的条件下。

第二个问题通过 @EnableConfigurationProperties({xxxProperties.class}) 实现,从 applications.properties 读取相应的配置项

@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {
    private org.springframework.validation.DefaultMessageCodesResolver.Format messageCodesResolverFormat;
    private Locale locale;
    private WebMvcProperties.LocaleResolver localeResolver;
    private final WebMvcProperties.Format format;
    private boolean dispatchTraceRequest;
    private boolean dispatchOptionsRequest;
    private boolean ignoreDefaultModelOnRedirect;
    private boolean publishRequestHandledEvents;
    ...
    @Deprecated
    public void setDateFormat(String dateFormat) {
        this.format.setDate(dateFormat);
    }

    public WebMvcProperties.Format getFormat() {
        return this.format;
    }

    public boolean isIgnoreDefaultModelOnRedirect() {
        return this.ignoreDefaultModelOnRedirect;
    }
    ...
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值