SpringBoot的自动装配原理

SpringBoot自动装配

前言

我记得我有次面试,面试官问我你用过SpringBoot吧,我说用过,然后他在问我你知道SpringBoot的自动装配吗?
我当时回答的是,有一个类可以读取到starter下面的META-INF/spring.factories文件,就可以完成了。
面试官一听你这不是说废话,叫我讲详细一点? 我…

所以我们从这几个方面来讲解一下?

  1. 什么是SpringBoot自动装配?

  2. SpringBoot是如何实现自动装配的?

  3. Spring如何实现一个自定义注解?

  4. 如何实现一个starter?


1.什么是SpringBoot自动装配?

自动装配是SpringBoot的核心,一般提到自动装配就会和SpringBoot联系在一起。实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过SPI的方式,做了进一步优化。

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用mybatis-plus的话,直接在项目中引入对应的 starter 即可。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

所以说,其实自动装配可以简单的理解为:通过注解或者一些简单的配置就能在SpringBoot的帮助下实现某款功能。

1.1 什么是SPI

SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要用于框架中开发,例如Dubbo、Spring、Common-Logging,JDBC等采用采用SPI机制,针对同一接口采用不同的实现提供给不同的用户,从而提高了框架的扩展性。

Java内置的SPI通过java.util.ServiceLoader类解析classPath和jar包的META-INF/services/目录下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用。

Spring里也有类似的SPI,思路根上面类似,从classpath下所有jar包的META-INF/spring.factories配置文件中加载标识为EnableAutoConfiguration的配置类,然后将其中定义的bean注入到Spring容器。

2.SpringBoot是如何实现自动装配

主程序类,主入口类

@SpringBootApplication
public class DemoSpringApplication {

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

}

首先看一下SpringBoot启动类中的核心注解@SpringBootApplication

在这里插入图片描述

2.1 @SpringBootApplication注解解释

@SpringBootApplication是一个复合注解,大概就可以把@SpringBootApplication看作是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan注解的集合。这三个注解的作用分别是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制。

  • @@SpringBootConfiguration:标记启动类为一个spring配置类

  • @ComponentScan: 扫描包下的类中添加了@Component (@Service,@Controller,@Repostory,@RestController)注解的类 ,并添加的到spring的容器中,可以自定义不扫描某些 bean

@Target

用来表示注解作用范围,超过这个作用范围,编译的时候就会报错。

可选值作用
@Target(ElementType.TYPE)接口、类、枚举、注解
@Target(ElementType.FIELD)字段、枚举的常量
@Target(ElementType.METHOD)方法
@Target(ElementType.PARAMETER)方法参数
@Target(ElementType.CONSTRUCTOR)构造函数
@Target(ElementType.LOCAL_VARIABLE)局部变量
@Target(ElementType.ANNOTATION_TYPE)注解
@Target(ElementType.PACKAGE)包,用于记录java文件的package信息
@Retention

指示具有注释类型的注释要保留多长时间。

可选值作用
RetentionPolicy.SOURCE注解信息只能在源文件中出现
RetentionPolicy.RUNTIME注解信息在执行时出现
RetentionPolicy.SOURCE注解信息在源文件中出现
@Documented

表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的。但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理,所以注解类型信息也会被包括在生成的文档中。

@Inherited

它指明被注解的类会自动继承。更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类,父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中


以上四个全是Java的元注解,以下是Spring的,我现在就来讲解
@ComponentScan

@ComponentScan注解用于实现spring主键的注解扫描,会扫描特定包内的类上的注解

在这里插入图片描述

@SpringBootConfiguration

在这里插入图片描述

@EnableAutoConfiguration

@EnableAutoConfiguration只是一个简单地注解,自动装配核心功能的实现实际是通过AutoConfigurationImportSelector类。

在这里插入图片描述

第一步: AutoConfigurationImportSelector源码解释

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

通过源码可知,AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports方法,该方法返回所有需要被注册为bean的类全名的数组集合

第二步:我们再来看看AutoConfigurationImportSelector类的selectImports方法

在这里插入图片描述

第三步:详解getAutoConfigurationEntry()方法

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的

在这里插入图片描述

isEnabled(annotationMetadata) 判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中设置

在这里插入图片描述

AnnotationAttributes attributes = getAttributes(annotationMetadata);,用于获取@EnableAutoConfiguration注解中的excludeexcludeName。获取注解属性

在这里插入图片描述

getCandidateConfigurations(annotationMetadata, attributes);,获取需要自动装配的所有配置类,读取META-INF/spring.factories。读取所有预配置类

在这里插入图片描述

走进 getCandidateConfigurations(annotationMetadata, attributes); 方法

在这里插入图片描述

1.SpringFactoriesLoader.loadFactoryNames方法源码:

在这里插入图片描述

我们进入方法loadSpringFactories(ClassLoader classLoader),知道该方法返回一个Map并获取key为类全名,value即需要被装配的类的类全名数值集合

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.getCandidateConfigurations方法最终返回的结果

在这里插入图片描述

我们可以看到spring.factories中有很多的配置,难道我们都要加载吗?当然不是跟随我下一步

removeDuplicates(configurations); 方法,我们通过见名知意我们可以知道是去除重复的元素。去掉重复的配置类

在这里插入图片描述
removeDuplicates方法最终返回的结果

在这里插入图片描述

getExclusions(annotationMetadata, attributes); ,得到排除。此操作就是将exclude和excludeName属性内容绑定到环境变量去中,由于这两个属性默认为空所以略过

在这里插入图片描述

checkExcludedClasses(configurations, exclusions): 方法名上可以知道该方法是检查configurations内容中哪些是需要排除的,由于exclusions默认为空,这里实际没做什么有效的操作,所以不再深入分许

configurations.removeAll(exclusions): 方法名上可以看出是移除需要排除的元素,由于exclusions默认为空,所以也没有执行什么东西

getConfigurationClassFilter().filter(configurations); ,将不满足需求的全部过滤,重点

在这里插入图片描述

filter.match(candidates, this.autoConfigurationMetadata);,这个方法我就不过多的讲解了

在这里插入图片描述
因为,这一步有经历了一遍筛选过滤,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

Spring Boot 提供的条件注解如下:

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

fireAutoConfigurationImportEvents(configurations, exclusions):关闭spring监听器中的自动装配事件

new AutoConfigurationEntry(configurations, exclusions):最终的返回的结果

在这里插入图片描述

综合上述@EnableAutoConfiguration注解通过@Import注解导入ImportSelector的子类 AutoConfigurationImportSelector类,通过该类selectImports方法加载读取所有spring-boot-starter-autoconfigure依赖下的spring-autoconfigure-metadata.properties配置文件和spring.factories配置文件的内容,并根据 AutoConfigurationImportSelector类下的AutoConfigurationImportFilter过滤器的过滤规则和spring-autoconfigure-metadata.properties 配置文件的内容过滤掉 spring.factories文件中需要被过滤掉的组件元素(当然这之前还有一步根据@EnableAutoConfiguration注解的excludeexcludeName属性过滤 spring.factories配置文件的内容,由于@EnableAutoConfiguration注解的这两个属性默认为空,所以这步操作什么都没做),最终返回spring.factories文件中剩余组件的类全名数组,并由IOC容器注册为Bean

2.2 总结

以上解析了SpringBoot实现自动装配的源码,实际上我们在工作中基本用不到,只需了解即可,我们可能在面试中经常会被问到自动装配的原理,按照以上的解析,这里我们总结一下,面试可以这样简洁回答:

启动类的@SpringBootApplication注解由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,三个注解共同完成自动装配;

  • @SpringBootConfiguration 注解标记启动类为配置类
  • @ComponentScan 注解实现启动时扫描启动类所在的包以及子包下所有标记为bean的类由IOC容器注册为bean
  • @EnableAutoConfiguration通过 @Import 注解导入 AutoConfigurationImportSelector类,然后通过AutoConfigurationImportSelector 类的 selectImports 方法去读取需要被自动装配的组件依赖下的spring.factories文件配置的组件的类全名,并按照一定的规则过滤掉不符合要求的组件的类全名,将剩余读取到的各个组件的类全名集合返回给IOC容器并将这些组件注册为bean

3.Spring实现自定义注解

一、元注解

java提供了4种元注解用于注解其他注解,所有的注解都是基于这四种注解来定义的。
大家可以看我上面的四个元注解的解释?

二、读取注解

通过反射机制我们可以读取注解信息。
java在java.lang.reflect包下新增了AnnotatedElement接口,该接口定义了可以接受注解的元素为:Class(类)、Constructor(构造器)、Field(字段)、Method(方法)、Package(包)。

AnnotatedElement是所有注解元素的父接口,所有的注解元素都可以通过某个类反射获取AnnotatedElement对象,该对象有一下4个方法来访问Annotation信息。

1)<T extends Annotation> T getAnnotation(Class<T> annotationClass)
返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

2)Annotation[] getAnnotations():返回该程序元素上存在的所有注解

3)boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)
判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

4)Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响

注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
//在运行期保留注解信息
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    //类名注解,默认即为当前类名
    String name() default "className";
    
}
package com.mr.annotation;


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//字段注解
@Target(ElementType.FIELD)
//在运行期保留注解信息
@Retention(RetentionPolicy.RUNTIME)
//在生成javac时显示该注解的信息
@Documented
//标明MyAnnotation1注解可以被使用它的子类继承
@Inherited
public @interface MyAnnotation1 {
    String name() default "fieldName";

    String getFieldValue() default "getField";

    String setFieldValue() default "setField";

    public enum FieldValue {MYTEST, MYFIELD, MYVALUE};

    FieldValue realValue() default FieldValue.MYFIELD;
}

实体类:

package com.mr.annotation;

import com.mr.annotation.MyAnnotation1.FieldValue;

@MyAnnotation(name = "myTest")
public class MyTest {

    @MyAnnotation1
    String myTest;

    @MyAnnotation1(name = "test", getFieldValue = "1", setFieldValue = "2", realValue = FieldValue.MYVALUE)
    String testValue;

    public String getMyTest() {
        return myTest;
    }

    public void setMyTest(String myTest) {
        this.myTest = myTest;
    }

    public String getTestValue() {
        return testValue;
    }

    public void setTestValue(String testValue) {
        this.testValue = testValue;
    }

}

测试类:

package com.mr.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TestAnnotation {
    public static void main(String[] args) {
        MyTest myTest = new MyTest();

        // 获取类的所有注解
        Annotation[] annotations = myTest.getClass().getAnnotations();
        for (Annotation anno : annotations) {
            if (anno instanceof MyAnnotation) {
                MyAnnotation myAnnotation = (MyAnnotation) anno;
                System.out.println("className:" + myAnnotation.name());
            } else if (anno instanceof MyAnnotation1) {
                MyAnnotation1 myAnnotation1 = (MyAnnotation1) anno;
                System.out.println("FiledName:" + myAnnotation1.name());
                System.out.println("setFieldValue" + myAnnotation1.setFieldValue());
                System.out.println("getFieldValue" + myAnnotation1.getFieldValue());
                System.out.println("realValue" + myAnnotation1.realValue());
            }
        }

        // 获取所有注解字段
        Field[] fields = myTest.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(MyAnnotation1.class)) {
                MyAnnotation1 myAnno = (MyAnnotation1) field.getAnnotation(MyAnnotation1.class);
                System.out.println(field.getName() + "-name:" + myAnno.name());
                System.out.println(field.getName() + "-getFieldValue:" + myAnno.getFieldValue());
                System.out.println(field.getName() + "-setFieldValue:" + myAnno.setFieldValue());
                System.out.println(field.getName() + "-realValue:" + myAnno.realValue());
            }
        }

        // 获取所有方法
        Method[] methods = myTest.getClass().getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyAnnotation1.class)) {
                MyAnnotation1 myAnno1 = (MyAnnotation1) method.getAnnotation(MyAnnotation1.class);
                System.out.println(myAnno1.getClass());
            }
        }
    }
}

测试结果:

在这里插入图片描述

三、自定义注解

自定义注解是通过@interface来声明的,其中的每一个方法实际上是声明了一个配置参数,参数名称即为方法名,参数类型即为返回值类型。

自定义注解的格式:public @interface 注解名{定义体}

注解参数可支持的类型:

  1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
  2. String类型
  3. Class类型
  4. enum类型
  5. Annotation类型
  6. 以上所有类型的数组

注解参数的定义规则:

  1. 只能使用public或默认2种访问修饰,例如:String getName();这里getName()就是使用了默认访问权限。
  2. 参数类型只能使用上面提到的6种情况
  3. 如果只有一个参数成员,最好将参数名定义为:value()。
  4. 注解元素必须有确定值,要么在定义的时候设置默认值,要么在使用注解的时候设置参数值。

4.如何实现一个starter

熟悉模式,有助于提升编写的starter的规范性,编写自己的starter之前先来学习Springboot官方 starter以及常见框架的整合starter的编写方式 ,可以领略到其中的奥秘

Springboot 官方模式

选择一个官方的自动配置进行分析,这里就选择常见的配置端口号配置

引入依赖

使用端口号之前我们需要先引入 web 依赖

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

如果你观察starter多的话,也许你发已经发现了一个模式,Springboot 官方的starter的名字都是 spring-boot-starter-xxxx命名的。

查看spring-boot-starter-web会发现,其实这个依赖只是一个空盒子,除了依赖其他 pom 之外,没有一行代码。

spring-boot-starter-web
在这里插入图片描述

这时,发现了另外一个模式:starter 只依赖其他 pom,不做代码实现。
那么 spring-boot-starter-web 到底依赖了哪些内容?

spring-boot-starter-web 的依赖
在这里插入图片描述
观察这个依赖信息,然后再参照其他的官方 starter ,可以找到几个固定的引入,可以被称之为模式的依赖引入。

  1. 依赖spring-boot-starter
  2. 依赖spring-boot-autoconfigure
自动配置

引入依赖只有配置端口号,像这样

server.port=8081

IDEA 中可以通过点击server.port找到这个配置绑定的类文件。可以看到配置最终会注入到类ServerProperties类的port属性上。

在这里插入图片描述

Server 属性配置

那么这个ServerProperties到底是哪里使用的呢?继续查找,找到一个和Servlet的有关的调用。
找到这个属性之后,按住CTRL + 鼠标点击就可以和我一样了
在这里插入图片描述

getPort 的调用

发现是被ServletWebServerFactoryCustomizer类进行了调用,这个类里面定义了
在这里插入图片描述

用来使用配置的属性。继续查看这个类的调用,发现只有一个类使用这个类,
在这里插入图片描述
这个类是ServletWebServerFactoryAutoConfiguration

在这里插入图片描述
看到这个类是不是有些熟悉很像SpringBoot配置类,根据我们对注解的理解,这个类就是自动配置主要类了。同时自动配置类都是以AutoConfiguration结尾。

看这个类的几个注解的意思

优先级别较高。
在这里插入图片描述

只有在ServletRequest类存在和是Web应用时生效。
在这里插入图片描述

开启了ServerProperties的配置绑定
在这里插入图片描述

导入了几个类。
在这里插入图片描述

同时注入配置到 Bean 工厂以供其他地方调用。
在这里插入图片描述

自动配置仅仅是这些东西吗?根据之前文章里的分析,我们知道不止代码,至少还有一个指定自动配置类的配置文件需要读取。也就是spring.factories文件
在这里插入图片描述

事实确实如此,可以在 spring.factories 中找到上面跟踪到的类。
也就是 ServletWebServerFactoryAutoConfiguration.

根据上面的分析,可以发现 Springboot 官方 starter 的几个模式。

  1. 使用 XXXProperties 自动绑定 XXX 开头的配置信息,如:ServerProperties
  2. XXXProperties 定义到要使用的类中,如:ServletWebServerFactoryCustomizer
  3. 编写一个 XXXAutoConfiguration ,开启 XXXProperties 的自动配置,限定生效场景,创建需要的类到 Bean 工厂。如:ServletWebServerFactoryAutoConfiguration

第三方集成模式

Springboot 官方如果把所有的框架都编写成 starter,是不现实的。因此很多第三方框架需要主动集成到 springboot,所以我们选择一个常用的框架分析它的 starter 实现。因为已经看过了 springboot 官方 starter 是如何配置的, 第三方框架也是类似,所以在下面观察的过程中会直接指出相同点,而不再做对比详细对比。

这里选择 mybatis-spring-boot-starter 进行学习分析

引入依赖
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

这里 mybatis 框架的 starter 依赖符合一定的规则,即 xxx-spring-boot-starter
观察这个 starter,发现它也没有做任何的代码实现,这一点和 springboot 官方一致。

mybatis-spring-boot-starter
在这里插入图片描述

自动配置

查看 mybatis-spring-boot-autoconfigure 的内容发现和 springboot 官方的 autoconfigure结构上是差不多的。

mybatis-spring-boot-autoconfigure
在这里插入图片描述

mybatis 的自动配置也是通过 spring.factories 来指明自动配置,然后通过 XxxAutoConfiguration 绑定 XxxProperties 来进行自动配置.

在这里插入图片描述

MybatisAutoConfiguration
在这里插入图片描述

在原理上,和上面 springboot 官方的 starter是相同的,所以不做过多的介绍了。

编写自己的 starter

编写自己的 starter,说了那么多,终于到了实操环节,通过上面的介绍,我们可以大致得出编写自己的starter步骤。

  1. 创建名字为 xxx-spring-boot-starter 的启动器项目。

  2. 创建名字为 xxx-spring-boot-autoconfigure的项目。

    • 编写属性绑定类 xxxProperties.
    • 编写服务类,引入 xxxProperties.
    • 编写自动配置类XXXAutoConfiguration注入配置。
    • 创建 spring.factories 文件,用于指定要自动配置的类。
  3. 启动器项目为空项目,用来引入 xxx-spring-boot-autoconfigure等其他依赖。

  4. 项目引入starter,配置需要配置的信息。

创建空项目

在这里插入图片描述

创建启动器项目

由于启动器不需要代码实现,只需要依赖其他项目,所以直接创建一个空的 maven 项目。但是名字要规范。

在这里插入图片描述

这里创建的 startermyapp-spring-boot-starter
在这里插入图片描述

pom 文件非常简单,只需要引入接下来要创建的myapp-spring-boot-autoconfigure

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mr.starter</groupId>
    <artifactId>myapp-spring-boot-starter</artifactId>
    <version>1-SNAPSHOT</version>

    <properties>
		<!-- 与1.8配置在一起,设置编码集-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!-- 启动器 -->
    <dependencies>

        <!--  引入自动配置项目 -->
        <dependency>
            <groupId>com.mr.starter</groupId>
            <artifactId>myapp-spring-boot-autoconfigure</artifactId>
            <version>1-SNAPSHOT</version>
        </dependency>

    </dependencies>

</project>
创建自动配置项目

结合上面对 starter 的分析,直接创建一个名字为 myapp-spring-boot-autoconfigure 的项目。
在这里插入图片描述

项目中只引入 springboot 父项目以及 spring-boot-starter

<?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">
    <modelVersion>4.0.0</modelVersion>

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

    <groupId>com.mr.starter</groupId>
    <artifactId>myapp-spring-boot-autoconfigure</artifactId>
    <version>1-SNAPSHOT</version>

    <properties>
    <!-- 与1.8配置在一起,设置编码集-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>

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

    </dependencies>

</project>

项目的总体结构看图

在这里插入图片描述
HelloProperties中通过注解 @ConfigurationProperties(prefix = "myapp.hello")让类中的属性与 myapp.hello开头的配置进行绑定。

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

@ConfigurationProperties(prefix = "myapp.hello")
public class HelloProperties {

    private String suffix;

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

然后在 HelloService中的 sayHello方法使用 HelloProperties 中自动绑定的值。

public class HelloService {
    HelloProperties helloProperties;

    public String sayHello(String name) {
        return "Hello " + name + "," + helloProperties.getSuffix();
    }

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }
}

为了让 HelloService 可以自动注入且能正常使用 HelloProperties,所以我们在 HelloServiceAutoConfiguration 类中把 HelloProperties.class 引入,然后把 HelloService 注入到 Bean

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

/**
 * web应用才生效
 */
@ConditionalOnWebApplication
/**
 * 让属性文件生效
 */
@EnableConfigurationProperties(HelloProperties.class)
/***
 * 声明是一个配置类
 */
@Configuration
public class HelloServiceAutoConfiguration {

    @Autowired
    private HelloProperties helloProperties;

    @Bean
    public HelloService helloService() {
        HelloService helloService = new HelloService();
        helloService.setHelloProperties(helloProperties);
        return helloService;
    }
}

最后在 spring.factories中只需要指定要自动配置的类即可。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mr.starter.HelloServiceAutoConfiguration

到这里,自动配置项目就完成了。可以在 myapp-spring-boot-autoconfigure项目执行 mvn install 把自动配置项目打包到本地仓库,然后使用相同的命令把 myapp-spring-boot-starter 安装到仓库。因为后者依赖于前者项目,所以这里前者需要先进 mvn install

使用自定义的启动器

创建一个 springboot项目myapp-spring-boot-starter-test
在这里插入图片描述
在这里插入图片描述

引入 web 依赖,引入自己编写的 myapp-spring-boot-starter

<?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>

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

    <groupId>com.mr.starter</groupId>
    <artifactId>myapp-spring-boot-starter-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>myapp-spring-boot-starter-test</name>
    <description>myapp-spring-boot-starter-test</description>

    <properties>
        <java.version>8</java.version>
    </properties>

    <dependencies>

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

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

        <!--*********************************************************-->

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

        <!-- 引入自己的 starter -->
        <dependency>
            <groupId>com.mr.starter</groupId>
            <artifactId>myapp-spring-boot-starter</artifactId>
            <version>1-SNAPSHOT</version>
        </dependency>

        <!--*********************************************************-->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编写一个HelloController注入自动配置里的HelloService用于测试。

import com.mr.starter.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String sayHello(String name) {
        return helloService.sayHello(name);
    }
}

由于 autoConfigure 项目中定义了 sayHello 方法会输出“Hello”+传入的 name + 配置的 hello.suffix,所以我们在 springboot 配置文件中配置这个属性。

myapp.hello.suffix=来一个springboot的starter

运行测试项目,访问 /hello 路径传入一个 name 看看自动配置有没有生效。
在这里插入图片描述

访问测试

从测试结果可以看到自动配置的早上好已经生效了。到这里自己编写的starter也已经完工。

参考文献

https://www.jb51.net/article/229978.htm
https://blog.csdn.net/qq_24078621/article/details/125216990
https://blog.csdn.net/wszjha123/article/details/126777093
https://blog.csdn.net/m0_46316970/article/details/125898849
https://cloud.tencent.com/developer/article/2062579
https://blog.csdn.net/qq_43778308/article/details/118313437
https://javastack.blog.csdn.net/article/details/108138366
https://www.cnblogs.com/xslient/p/16363023.html
https://cloud.tencent.com/developer/article/1532250

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值