Hexo

点滴积累 豁达处之

0%

03 spring 实现自动配置

Spring中纷繁复杂的配置文件一直让广大开发人员颇有微词,但是随着业务的复杂度越来越高,这也是在所难免的事情。

Spring Boot实现自动配置的基础

引子

问题是什么

Spring中纷繁复杂的配置文件一直让广大开发人员颇有微词,但是随着业务的复杂度越来越高,这也是在所难免的事情。

哪怕是要创建一个简单的Web应用,需要配置的东西也是一坨坨的,这个过程虽然不复杂但是很繁琐,而且非常容易出错。所以聪明的开发人员想出了很多办法来解决这个问题,比如Maven的Archetype创建,又或者各个公司内部的脚手架小工具。但是这些方案总是有这样那样的问题,比如维护不方便,不好定制等等。

在云计算,弹性计算以及微服务越来越普及的今天,急需要一种自动配置的方式,从而方便地按需部署。所以Spring Boot应用而生,而自动配置作为Spring Boot的闪光点之一,自然非常受人关注。

Spring Boot的自动配置功能,其实从本质上说并没有引入什么新功能,它只是将Spring现存的能力做了一次组合和封装。那么在深入了解Spring Boot的自动配置原理之前,可以先了解一下Spring的这些已知能力,打下良好地基础。

和配置相关的Spring已有能力

@Profile

很多时候,我们开发的Spring应用,需要根据所在环境的不同注册相应的Bean到上下文中。

比如本地开发环境中,数据库连接对象往往指向的是开发数据库;在测试环境中,又会指向测试数据库;而到了线上,指向的自然是生产数据库。

为了满足这个常见需求,Spring 3.1中引入了Profile的概念。比如在下面的代码中,配置类会根据所在环境(Profile)的不同,向上下文中注入对应的Bean实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class DataSourceConfiguration
{
@Bean
@Profile("DEV")
public DataSource devDataSource() {
// ...
}

@Bean
@Profile("TEST")
public DataSource testDataSource() {
// ...
}

@Bean
@Profile("PROD")
public DataSource prodDataSource() {
// ...
}
}

那么,如何声明应用所处的Profile呢?还是有几种选择:

  1. 配置文件,比如在application.properties中声明spring.profiles.active=DEV
  2. 启动参数,比如-Dspring.profiles.active=DEV

这个Profile的概念很直观,但是由于它仅仅是依赖一个字符串的值作出决策,所以不够灵活和强大。因此就有了下面@Conditional注解和Condition接口的诞生。

@Conditional以及Condition接口

它们是Spring 4中引入的新功能。

Condition接口和@Conditional接口通常会一起配合使用。

Condition接口的定义如下:

1
2
3
4
5
6
7
8
9
10
public interface Condition {

/**
* 决定Condition是否被满足
* @param context 上下文信息
* @param metadata 类或方法上的元数据注解信息
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

下面是一个该接口的实现类:

1
2
3
4
5
6
7
public class CustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 通过一系列计算决定Condition是否被满足,如果满足返回true,反之返回false
return true;
}
}

@Conditional注解的定义如下:

1
2
3
4
5
6
7
8
9
10
11
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

/**
* 该注解接受的默认参数类型是一个Class数组,这些Class都应该实现Condition接口,并且当其中的matches都返回true的时候,对应的Bean才会被注入
*/
Class<? extends Condition>[] value();

}

然后一般在Java Config类中将它们结合在一起使用:

1
2
3
4
5
6
7
8
9
@Configuration
public class CustomConfiguration {
// 当CustomCondition中的matches返回true的时候才会注入CustomClass这个类的实例Bean。
@Bean
@Conditional(CustomCondition.class)
public CustomClass customClass() {
return new CustomClass();
}
}

因此通过将@Conditional注解和Condition接口的能力进行结合,就可以实现根据外界条件来决定一个Bean是不是应该被加载到Spring上下文中。对比一下@Profile注解,可以发现这种方式提供了更高的灵活度。

在@Profile注解中,唯一决定是否加载一个Bean的是一个字符串,即所在环境的标识符例如DEV,TEST,PRODUCTION这类。但是在@Conditional的方案中,决定是否加载的逻辑被抽象到了Condition接口中,这样能够做的判断就非常多了。

@ConfigurationProperties以及@EnableConfigurationProperties

要实现自动配置,还有一个比较关键的环节。我们都知道Bean之所以有意义是因为每个Bean中都有自己独特的字段,而很多情况下,Bean中的字段都是可配置的。因此,要实现自动配置,我们首先需要对一些关键字段给予一个默认值,当然也需要提供一个机制让用户能够对这些字段进行覆盖。在Spring Boot中,提供了一个新的机制叫做Configuration Properties(可配置的属性),它有几个特点:

  1. 类型安全的配置方式(基于Java类),基于Spring Conversion API完成类型转换
  2. 可以通过配置文件,启动参数等多种方式完成覆盖
  3. 和JavaConfig类无缝集成

这个机制有两个关键的注解类型,即@ConfigurationProperties和@EnableConfigurationProperties。下面我们就来看看它们的定义以及如何应用:

定义方式

@ConfigurationProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {

@AliasFor("prefix")
String value() default "";

@AliasFor("value")
String prefix() default "";

// ...
}

只捡主要的属性介绍,这个注解中比较关键的就是value/prefix属性。这两个属性是一个意思,互相设置了@AliasFor。

@EnableConfigurationProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 用于支持@ConfigurationProperties标注的Beans。
* @ConfigurationProperties注解的Bean也可以通过标准方式(比如使用@Bean方法),但是这个注解提供了一个更方便的方法。
*
* @author Dave Syer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

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

}
应用方式

以Spring Web应用中需要的HttpEncodingFilter这个Filter的自动配置为例,看看如何应用这两注解:

1
2
3
4
5
6
7
8
9
10
11
12
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {

public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

private Charset charset = DEFAULT_CHARSET;

private Boolean force;

// ...

}

这是一个配置类,它设置了前缀spring.http.encoding。因此,这个配置类中的字段都会对应某一个具体的配置KEY,比如charset字段对应的配置KEY为spring.http.encoding.charse; force字段对应的是spring.http.encoding.force。同时可以给每个字段设置一个默认值,比如上述的charset字段就有一个默认值DEFAULT_CHARSET,对应的是UTF-8的字符集。

如果我们不想用默认的UTF-8编码方式,想换成GBK,那么在配置文件(如application.properties中),可以进行如下覆盖:

1
spring.http.encoding.charset=GBK1

这里有个地方值得一提,在配置文件中的GBK明明是字符串类型的值,而在对应的配置属性类这个属性是Charset类型的,那么肯定是有一个步骤完成了从字符串到Charset类型的转换工作。完成这个步骤的就是Spring框架中的Conversion API。这里只贴一张相关的图,不进行深入分析,感兴趣的同学可以在配置属性类的setCharset方法上打个断点然后深挖一下。genericConverter

从这张图可以看出,Spring Conversion API中维护了一系列Converters,它实际上是一个从源类型到目标类型的Map对象。从String类型到Charset类型的Converter也被注册到了这个Map对象中。因此,当发现配置文件中的值类型和配置Java类中的字段类型不匹配时,就会去尝试从这个Map中找到相应的Converter,然后进行转换。

自定义注解实现自动配置

下面我们尝试来创建一个自定义的注解,实现Bean的按需加载。假设我们要创建的注解名为@DatabaseType。

具体的需求是:当启动参数中的dbType=MYSQL的时候,创建一个数据源为MySQL的UserDAO对象;当启动参数中的dbType=ORACLE的时候,创建一个数据源为Oracle的UserDAO对象。

最终配置类的代码可以是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class DatabaseConfiguration
{
@Bean
@DatabaseType("MYSQL")
public UserDAO mysqlUserDAO() {
return new MySQLUserDAO();
}

@Bean
@DatabaseType("ORACLE")
public UserDAO oracleUserDAO() {
return new OracleUserDAO();
}
}

可以知道,@DatabaseType这个注解能够接受一个字符串作为参数。然后将该参数和启动参数中的dbType值进行比对,如果相等的话,就会进行对应Bean的注入工作。@DatabaseType的实现如下:

1
2
3
4
5
6
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType {
String value();
}

其中比较重要的是@Conditional(DatabaseTypeCondition.class),表明它的觉得实际上也是委托给了DatabaseTypeCondition这个类:

1
2
3
4
5
6
7
8
9
10
public class DatabaseTypeCondition implements Condition {
// 这里也展示了AnnotatedTypeMetadata这个参数的用法
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
String type = (String) attributes.get("value");
String enabledDBType = System.getProperty("dbType", "MYSQL");
return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));
}
}

通过matches方法的第二个参数AnnotatedTypeMetadata,可以得到指定注解类型的所有属性,本例中就是@DatabaseType这个注解。然后将注解中的value值和启动参数dbType(不存在时使用MYSQL作为默认值)进行比对,然后返回相应的值来决定是否需要注入Bean。

所以,通过这个例子我们也可以发现。通常而言为了程序的可读性,可以将@Conditional和Condition接口的实现类给封装到一个业务含义更加明确的注解类型中,比如上面的@DatabaseType类型。它的意义就很明确,当类型是MySQL的该如何如何,当类型为Oracle的时候又当如何如何。

在后面深究Spring Boot的自动配置机制时,可以发现它也在@Conditional和Condition接口的基础上,定义了很多相关类型,用于更好地定义自动配置行为。

总结

本文介绍了Spring中和自动配置相关的两个概念:

  1. @Profile
  2. @Conditional以及Condition接口

SpringBoot的自动配置原理

原理

自动配置流程图

https://www.processon.com/view/link/5fc0abf67d9c082f447ce49b

先从启动类开始入手:

@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot

需要运行这个类的main方法来启动SpringBoot应用;

SpringBootApplication

1
2
3
4
5
6
7
8
9
10
@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 {

注解说明:

  • @Target(ElementType.TYPE)

    • 设置当前注解可以标记在哪
  • @Retention(RetentionPolicy.RUNTIME)

    • 当注解标注的类编译以什么方式保留

        • RetentionPolicy.RUNTIME

          • 会被jvm加载
  • @Documented

    • java doc 会生成注解信息
  • @Inherited

    • 是否会被继承
  • @SpringBootConfiguration**:Spring Boot的配置类;**

    • 标注在某个类上,表示这是一个Spring Boot的配置类;
  • @Configuration:配置类上来标注这个注解;

    • 配置类 —– 配置文件;配置类也是容器中的一个组件;@Component
  • @EnableAutoConfiguration:开启自动配置功能;

    • 以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置,会帮我们自动去加载 自动配置类
  • @ComponentScan : 扫描包

    • 相当于在spring.xml 配置中 但是并没有指定basepackage,如果没有指定spring底层会自动扫描当前配置类所有在的包
  • TypeExcludeFilter

    • springboot对外提供的扩展类, 可以供我们去按照我们的方式进行排除
  • AutoConfigurationExcludeFilter

    • 排除所有配置类并且是自动配置类中里面的其中一个

@EnableAutoConfiguration

这个注解里面,最主要的就是@EnableAutoConfiguration,这么直白的名字,一看就知道它要开启自动配置,SpringBoot要开始骚了,于是默默进去@EnableAutoConfiguration的源码。

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage

将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用

@AutoConfigurationPackage

1
2
@Import(AutoConfigurationPackages.Registrar.class)  // 保存扫描路径, 提供给spring-data-jpa 需要扫描 @Entity
public @interface AutoConfigurationPackage {
  • 就是注册了一个保存当前配置类所在包的一个Bean

@Import(**AutoConfigurationImportSelector**.class)

可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,而AutoConfigurationImportSelector实现了DeferredImportSelectorSpring内部在解析@Import注解时会调用getAutoConfigurationEntry方法,这块属于Spring的源码,有点复杂,我们先不管它是怎么调用的。 下面是2.3.5.RELEASE实现源码:

getAutoConfigurationEntry方法进行扫描具有META-INF/spring.factories文件的jar包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从META-INF/spring.factories中获得候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 排重
configurations = removeDuplicates(configurations);
//根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 根据:@EnableAutoConfiguration.exclude
// @EnableAutoConfiguration.excludeName
// spring.autoconfigure.exclude 进行排除
checkExcludedClasses(configurations, exclusions);
// exclusions 也排除
configurations.removeAll(exclusions);
// 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 这个方法是调用实现了AutoConfigurationImportListener  的bean..  分别把候选的配置名单,和排除的配置名单传进去做扩展
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

任何一个springboot应用,都会引入spring-boot-autoconfigure,而spring.factories文件就在该包下面。spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:

等同于

@Import({

1
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ ...省略 org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration       

})

每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;

  • 每一个自动配置类进行自动配置功能;

后续: @EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(…)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中

  • 以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

private final Encoding properties;

public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}

@Configuration(proxyBeanMethods = false)

  • 标记了@Configuration Spring底层会给配置创建cglib动态代理。 作用:就是防止每次调用本类的Bean方法而重新创建对象,Bean是默认单例的

@EnableConfigurationProperties(ServerProperties.class)

  • 启用可以在配置类设置的属性 对应的类
  • @xxxConditional根据当前不同的条件判断,决定这个配置类是否生效?

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解作用 (判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

我们怎么知道哪些自动配置类生效;

我们可以通过设置配置文件中:启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

1
2
3
4
5
6
7
8
9
10
11
12
 ============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:---**表示自动配置类启用的**
-----------------
...省略...

Negative matches:---**没有匹配成功的自动配置类**
-----------------
...省略...

下面我么就以HttpEncodingAutoConfiguration(Http编码自动配置)为例说明自动配置原理;

该注解如下:

1
2
3
4
5
6
@Configuration( proxyBeanMethods = false)
@EnableConfigurationProperties({ServerProperties.class})
@ConditionalOnWebApplication( type = Type.SERVLET)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty( prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
  • @Configuration:表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件。
  • @ConditionalOnWebApplication:Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效。
  • @ConditionalOnClass:判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器。
  • @ConditionalOnProperty:判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的

即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的。

  • @EnableConfigurationProperties({ServerProperties.class}):将配置文件中对应的值和 ServerProperties绑定起来;并把 ServerProperties加入到 IOC 容器中。并注册ConfigurationPropertiesBindingPostProcessor用于将@ConfigurationProperties的类和配置进行绑定

所以只有知道了自动配置的原理及源码 才能灵活的配置SpringBoot

SpringBoot_autoconfig001

自定义starter

简介

SpringBoot 最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们通过引入springboot 为我提供的这些场景启动器,我们再进行少量的配置就能使用相应的功能。即使是这样,springboot也不能囊括我们所有的使用场景,往往我们需要自定义starter,来简化我们对springboot的使用。

如何自定义starter

我们参照@WebMvcAutoConfiguration为例,我们看看们需要准备哪些东西,下面是WebMvcAutoConfiguration的部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {

@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {

@Bean
@ConditionalOnBean({View.class})
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(2147483637);
return resolver;
}
}
}

我们可以抽取到我们自定义starter时同样需要的一些配置。

1
2
3
4
5
6
7
8
9
10
11
@Configuration  //指定这个类是一个配置类
@ConditionalOnXXX //指定条件成立的情况下自动配置类生效
@AutoConfigureOrder //指定自动配置类的顺序
@Bean //向容器中添加组件
@ConfigurationProperties //结合相关xxxProperties来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中

自动配置类要能加载需要将自动配置类,配置在META-INF/spring.factories中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

模式:

我们参照 spring-boot-starter 我们发现其中没有代码:

SpringBoot_autoconfig002

我们在看它的pom中的依赖中有个 spring-boot-starter

我们再看看 spring-boot-starter 有个 spring-boot-autoconfigure

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

我们再看看 spring-boot-starter 有个 spring-boot-autoconfigure

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

关于web的一些自动配置都写在了这里 ,所以我们有总结:

SpringBoot_autoconfig003

  • 启动器(starter)是一个空的jar文件,仅仅提供辅助性依赖管理,这些依赖可能用于自动装配或其他类库。
  • 需要专门写一个类似spring-boot-autoconfigure的配置模块
  • 用的时候只需要引入启动器starter,就可以使用自动配置了

命名规范

官方命名空间

  • 前缀:spring-boot-starter-
  • 模式:spring-boot-starter-模块名
  • 举例:spring-boot-starter-web、spring-boot-starter-jdbc

自定义命名空间

  • 后缀:-spring-boot-starter
  • 模式:模块-spring-boot-starter
  • 举例:mybatis-spring-boot-starter

自定义starter实例

我们需要先创建一个父maven项目:springboot_custome_starter

两个Module: tulingxueyuan-spring-boot-starter 和 tulingxueyuan-spring-boot-starter-autoconfigurer

springboot_custome_starter

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>tulingxueyuan-spring-boot-starter</module>
<module>tulingxueyuan-spring-boot-autoconfigure</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<packaging>pom</packaging>
<groupId>com.tulingxueyuan.springboot</groupId>
<artifactId>springboot_custome_starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_custome_starter</name>
<description>SpringBoot自定义starter</description>

<properties>
<java.version>1.8</java.version>
</properties>

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

</project>
  1. tulingxueyuan-spring-boot-starter

    pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot_custome_starter</artifactId>
<groupId>com.tulingxueyuan.springboot</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>
启动器(starter)是一个空的jar文件,
仅仅提供辅助性依赖管理,
这些依赖需要自动装配或其他类库。
</description>

<artifactId>tulingxueyuan-spring-boot-starter</artifactId>

<dependencies>
<!--引入autoconfigure-->
<dependency>
<groupId>com.tulingxueyuan.springboot</groupId>
<artifactId>tulingxueyuan-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

<!--如果当前starter 还需要其他的类库就在这里引用-->
</dependencies>

</project>

如果使用spring Initializr创建的需要删除 启动类、resources下的文件,test文件。

  1. tulingxueyuan-spring-boot-starter-autoconfigurer

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot_custome_starter</artifactId>
<groupId>com.tulingxueyuan.springboot</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>tulingxueyuan-spring-boot-autoconfigure</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--‐导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>


</project>

HelloProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.starter.tulingxueyuan;

import org.springframework.boot.context.properties.ConfigurationProperties;

/***
* @Author 徐庶 QQ:1092002729
* @Slogan 致敬大师,致敬未来的你
*/
@ConfigurationProperties("tuling.hello")
public class HelloProperties {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

IndexController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.starter.tulingxueyuan;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/***
* @Author 徐庶 QQ:1092002729
* @Slogan 致敬大师,致敬未来的你
*/
@RestController
public class IndexController {

HelloProperties helloProperties;

public IndexController(HelloProperties helloProperties) {
this.helloProperties=helloProperties;
}

@RequestMapping("/")
public String index(){
return helloProperties.getName()+"欢迎您";
}

}

HelloAutoConfitguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.starter.tulingxueyuan;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/***
* @Author 徐庶 QQ:1092002729
* @Slogan 致敬大师,致敬未来的你
*
* 给web应用自动添加一个首页
*/
@Configuration
@ConditionalOnProperty(value = "tuling.hello.name")
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfitguration {

@Autowired
HelloProperties helloProperties;

@Bean
public IndexController indexController(){
return new IndexController(helloProperties);
}
}

spring.factories

在 resources 下创建文件夹 META-INF 并在 META-INF 下创建文件 spring.factories ,内容如下:

SpringBoot_autoconfig004

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.starter.tulingxueyuan.HelloAutoConfitguration

到这儿,我们的配置自定义的starter就写完了 ,我们hello-spring-boot-starter-autoconfigurer、hello-spring-boot-starter 安装成本地jar包。

测试应用starter

1
2
3
4
5
<dependency>
<groupId>com.tulingxueyuan.springboot</groupId>
<artifactId>tulingxueyuan-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

参考链接