前言
我记得我有次面试,面试官问我你用过SpringBoot吧,我说用过,然后他在问我你知道SpringBoot的自动装配吗?
我当时回答的是,有一个类可以读取到starter下面的META-INF/spring.factories文件,就可以完成了。
面试官一听你这不是说废话,叫我讲详细一点? 我…
所以我们从这几个方面来讲解一下?
什么是SpringBoot自动装配?
SpringBoot是如何实现自动装配的?
Spring如何实现一个自定义注解?
如何实现一个starter?
自动装配是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.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.1version>
dependency>
引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。
所以说,其实自动装配可以简单的理解为:通过注解或者一些简单的配置就能在SpringBoot的帮助下实现某款功能。
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容器。
主程序类,主入口类
@SpringBootApplication
public class DemoSpringApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringApplication.class, args);
}
}
首先看一下SpringBoot启动类中的核心注解@SpringBootApplication
@SpringBootApplication
是一个复合注解,大概就可以把@SpringBootApplication
看作是@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。这三个注解的作用分别是:
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制。
@@SpringBootConfiguration
:标记启动类为一个spring配置类
@ComponentScan
: 扫描包下的类中添加了@Component (@Service,@Controller,@Repostory,@RestController)注解的类 ,并添加的到spring的容器中,可以自定义不扫描某些 bean
用来表示注解作用范围,超过这个作用范围,编译的时候就会报错。
可选值 | 作用 |
---|---|
@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信息 |
指示具有注释类型的注释要保留多长时间。
可选值 | 作用 |
---|---|
RetentionPolicy.SOURCE | 注解信息只能在源文件中出现 |
RetentionPolicy.RUNTIME | 注解信息在执行时出现 |
RetentionPolicy.SOURCE | 注解信息在源文件中出现 |
表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的。但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理,所以注解类型信息也会被包括在生成的文档中。
它指明被注解的类会自动继承。更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类,父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中
@ComponentScan注解用于实现spring主键的注解扫描,会扫描特定包内的类上的注解
@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
注解中的exclude
和excludeName
。获取注解属性
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);
方法,我们通过见名知意我们可以知道是去除重复的元素。去掉重复的配置类
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 提供的条件注解如下:
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
注解的exclude
和excludeName
属性过滤 spring.factories配置文件的内容,由于@EnableAutoConfiguration注解的这两个属性默认为空,所以这步操作什么都没做),最终返回spring.factories
文件中剩余组件的类全名数组,并由IOC容器注册为Bean
以上解析了SpringBoot实现自动装配的源码,实际上我们在工作中基本用不到,只需了解即可,我们可能在面试中经常会被问到自动装配的原理,按照以上的解析,这里我们总结一下,面试可以这样简洁回答:
启动类的@SpringBootApplication注解由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,三个注解共同完成自动装配;
一、元注解
java提供了4种元注解用于注解其他注解,所有的注解都是基于这四种注解来定义的。
大家可以看我上面的四个元注解的解释?
二、读取注解
通过反射机制我们可以读取注解信息。
java在java.lang.reflect包下新增了AnnotatedElement接口,该接口定义了可以接受注解的元素为:Class(类)、Constructor(构造器)、Field(字段)、Method(方法)、Package(包)。
AnnotatedElement是所有注解元素的父接口,所有的注解元素都可以通过某个类反射获取AnnotatedElement对象,该对象有一下4个方法来访问Annotation信息。
1)
返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
2)Annotation[] getAnnotations():返回该程序元素上存在的所有注解
3)boolean isAnnotationPresent(Class 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 注解名{定义体}
注解参数可支持的类型:
注解参数的定义规则:
熟悉模式,有助于提升编写的starter
的规范性,编写自己的starter
之前先来学习Springboot
官方 starter
以及常见框架的整合starter
的编写方式 ,可以领略到其中的奥秘
选择一个官方的自动配置进行分析,这里就选择常见的配置端口号配置
使用端口号之前我们需要先引入 web 依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
如果你观察starter
多的话,也许你发已经发现了一个模式,Springboot 官方的starter
的名字都是 spring-boot-starter-xxxx
命名的。
查看spring-boot-starter-web
会发现,其实这个依赖只是一个空盒子,除了依赖其他 pom 之外,没有一行代码。
这时,发现了另外一个模式:starter 只依赖其他 pom,不做代码实现。
那么 spring-boot-starter-web 到底依赖了哪些内容?
spring-boot-starter-web 的依赖
观察这个依赖信息,然后再参照其他的官方 starter ,可以找到几个固定的引入,可以被称之为模式的依赖引入。
spring-boot-starter
spring-boot-autoconfigure
引入依赖只有配置端口号,像这样
server.port=8081
IDEA 中可以通过点击server.port
找到这个配置绑定的类文件。可以看到配置最终会注入到类ServerProperties
类的port
属性上。
Server 属性配置
那么这个ServerProperties
到底是哪里使用的呢?继续查找,找到一个和Servlet
的有关的调用。
找到这个属性之后,按住CTRL + 鼠标点击
就可以和我一样了
getPort 的调用
发现是被ServletWebServerFactoryCustomizer
类进行了调用,这个类里面定义了
用来使用配置的属性。继续查看这个类的调用,发现只有一个类使用这个类,
这个类是ServletWebServerFactoryAutoConfiguration
。
看到这个类是不是有些熟悉很像SpringBoot配置类,根据我们对注解的理解,这个类就是自动配置主要类了
。同时自动配置类都是以AutoConfiguration
结尾。
看这个类的几个注解的意思
只有在ServletRequest
类存在和是Web
应用时生效。
开启了ServerProperties
的配置绑定
自动配置仅仅是这些东西吗?根据之前文章里的分析,我们知道不止代码,至少还有一个指定自动配置类的配置文件需要读取。也就是spring.factories
文件
事实确实如此,可以在 spring.factories 中找到上面跟踪到的类。
也就是 ServletWebServerFactoryAutoConfiguration.
根据上面的分析,可以发现 Springboot 官方 starter 的几个模式。
XXXProperties
自动绑定 XXX
开头的配置信息,如:ServerProperties
。XXXProperties
定义到要使用的类中,如:ServletWebServerFactoryCustomizer
。XXXAutoConfiguration
,开启 XXXProperties
的自动配置,限定生效场景,创建需要的类到 Bean
工厂。如:ServletWebServerFactoryAutoConfiguration
。Springboot
官方如果把所有的框架都编写成 starter
,是不现实的。因此很多第三方框架需要主动集成到 springboot
,所以我们选择一个常用的框架分析它的 starter
实现。因为已经看过了 springboot
官方 starter
是如何配置的, 第三方框架也是类似,所以在下面观察的过程中会直接指出相同点,而不再做对比详细对比。
这里选择 mybatis-spring-boot-starter
进行学习分析
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
这里 mybatis
框架的 starter
依赖符合一定的规则,即 xxx-spring-boot-starter。
观察这个 starter
,发现它也没有做任何的代码实现,这一点和 springboot
官方一致。
查看 mybatis-spring-boot-autoconfigure
的内容发现和 springboot
官方的 autoconfigure
结构上是差不多的。
mybatis-spring-boot-autoconfigure
mybatis
的自动配置也是通过 spring.factories
来指明自动配置,然后通过 XxxAutoConfiguration
绑定 XxxProperties
来进行自动配置.
在原理上,和上面 springboot
官方的 starter
是相同的,所以不做过多的介绍了。
编写自己的 starter,说了那么多,终于到了实操环节,通过上面的介绍,我们可以大致得出编写自己的starter
步骤。
创建名字为 xxx-spring-boot-starter
的启动器项目。
创建名字为 xxx-spring-boot-autoconfigure
的项目。
xxxProperties
.xxxProperties
.XXXAutoConfiguration
注入配置。spring.factories
文件,用于指定要自动配置的类。启动器项目为空项目,用来引入 xxx-spring-boot-autoconfigure
等其他依赖。
项目引入starter
,配置需要配置的信息。
由于启动器不需要代码实现,只需要依赖其他项目,所以直接创建一个空的 maven 项目
。但是名字要规范。
这里创建的 starter
是 myapp-spring-boot-starter
pom 文件
非常简单,只需要引入接下来要创建的myapp-spring-boot-autoconfigure
<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.0modelVersion>
<groupId>com.mr.startergroupId>
<artifactId>myapp-spring-boot-starterartifactId>
<version>1-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>com.mr.startergroupId>
<artifactId>myapp-spring-boot-autoconfigureartifactId>
<version>1-SNAPSHOTversion>
dependency>
dependencies>
project>
结合上面对 starter
的分析,直接创建一个名字为 myapp-spring-boot-autoconfigure
的项目。
项目中只引入 springboot
父项目以及 spring-boot-starter
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.mr.startergroupId>
<artifactId>myapp-spring-boot-autoconfigureartifactId>
<version>1-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
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
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.mr.startergroupId>
<artifactId>myapp-spring-boot-starter-testartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>myapp-spring-boot-starter-testname>
<description>myapp-spring-boot-starter-testdescription>
<properties>
<java.version>8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.mr.startergroupId>
<artifactId>myapp-spring-boot-starterartifactId>
<version>1-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
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