当前位置: 首页 > news >正文

深度解析Spring Boot自动装配原理

废话不多说了,直接来看源码。

源码解析

@SpringBootApplication

我们在使用idea创建好Spring Boot项目时,会发现在启动类上添加了@SpringBootApplication注解,这个注解就是Spring Boot的核心所在。

点击注解可以查看到到它的实现

ementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
// AutoConfigurationExcludeFilter的作用是扫描到的配置类名字如果在自动配置类名集合中,就不解析
public @interface SpringBootApplication {
  • @Target(ElementType.TYPE) 设置当前注解可以标记在哪里

  • @Retention(RetentionPolicy.RUNTIME) 当注解标注的类编译以什么方式保留

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

  • @Inherited 是否会被继承

上面这几个注解并没有什么特别的。下面三个才是重点。我们可以看到@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan

在实际使用中我们也可以使用这三个注解替换@SpringBootApplication,效果是一样的

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

我们重点来关注下下面的三个注解。

@SpringBootConfiguration

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

这个注解用来标记当前类是一个SpringBoot的配置类,其本质就是一个@Configuration注解。也没有什么值得多说的。

@EnableAutoConfiguration

这个注解才是重中之重,真正的自动装配的灵魂所在。从名字就可以看出这个注解负责开启自动装配,我们来详细看下。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

我们先来看下需要重点关注的@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector

通过@Import注解引入的类同样会被当成配置类解析,这是spring的知识,这里就不说了。我们通过@Import注解引入了AutoConfigurationImportSelector,并且AutoConfigurationImportSelector实现了DeferredImportSelector。所以在启动时会调用到selectImports方法(有误,先简单理解后面会解释)。然后又会调用到getAutoConfigurationEntry。这个方法会负责自动装配。

调用链路:

@SpringBootApplication

-->@EnableAutoConfiguration

-->@Import(AutoConfigurationImportSelector.class)

-->org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports

-->org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

来先看一下源码

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 获取@EnableAutoConfiguration的属性AnnotationAttributes attributes = getAttributes(annotationMetadata);// 获取spring.factories中所有的AutoConfigurationList<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 去重(也就是按类名去重)configurations = removeDuplicates(configurations);// 获取需要排除的AutoConfiguration,可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude来配置Set<String> exclusions = getExclusions(annotationMetadata, attributes);// 排除checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 获取spring.factories中的AutoConfigurationImportFilter对AutoConfiguration进行过滤// 默认会拿到OnBeanCondition、OnClassCondition、OnWebApplicationCondition// 这三个会去判断上面的AutoConfiguration是否符合它们自身所要求的条件,不符合的会过滤掉,表示不会进行解析了// 会利用spring-autoconfigure-metadata.properties中的配置来进行过滤// spring-autoconfigure-metadata.properties文件中的内容是利用Java中的AbstractProcessor技术在编译时生成出来的configurations = getConfigurationClassFilter().filter(configurations);// configurations表示合格的,exclusions表示被排除的,把它们记录在ConditionEvaluationReportAutoConfigurationImportListener中fireAutoConfigurationImportEvents(configurations, exclusions);// 最后返回的AutoConfiguration都是符合条件的return new AutoConfigurationEntry(configurations, exclusions);
}
※自动装配的核心流程
  1. 获取

  • 获取@EnableAutoConfiguration的属性

  AnnotationAttributes attributes = getAttributes(annotationMetadata);

getAttributes的主要作用是获取配置类上添加的@EnableAutoConfiguration注解,并对注解进行解析。如果没有添加@EnableAutoConfiguration则不开启自动配置。

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {String name = getAnnotationClass().getName();  //EnableAutoConfiguration.class//解析注解,获取对应的属性值AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));//判断当前配置类上是否添加了@EnableAutoConfiguration注解,如果没有添加则不开启自动配置Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()+ " annotated with " + ClassUtils.getShortName(name) + "?");return attributes;
}

第4行会对注解进行解析@EnableAutoConfiguration注解可以配置排除自动解析的类,这个配置的作用后面会详细说明。

  • 获取spring.factories中所有的AutoConfiguration

 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

getCandidateConfigurations

-->SpringFactoriesLoader.loadFactoryNames

-->loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {//FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;
}

这个方法就是SPI机制了。10行会去查找所有jar包中配置的META-INF/spring.factories。spring.factories中是以文本形式存在的key,value结构。(这里除了EnableAutoConfiguration还有其他的key,value)

然后下面的代码会对文件进行解析,放到result中返回,放加入到cache。result就是一个Map<String, List>。

返回到org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

这一行的factoryTypeName就是“org.springframework.boot.autoconfigure.EnableAutoConfiguration”,所以这里会根据key取出EnableAutoConfiguration对应的所有需要自动装配的类,返回List。返回的内容就是spring.factories中配置的下面的部分。

这样获取到了所有需要自动配置的类。如果自己去实现了starter。也可以遵守spi的规则,在自己的项目里添加META-INF/spring.factories。

  1. 去重

按类名去重

   configurations = removeDuplicates(configurations);

去重很简单,放到set里就可以了

protected final <T> List<T> removeDuplicates(List<T> list) {return new ArrayList<>(new LinkedHashSet<>(list));
}
  1. 排除

// 获取需要排除的AutoConfiguration,可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude来配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 排除
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);

getExclusions会从两个地方获取需要排除的类,一个是@EnableAutoConfiguration注解的exclude属性,另一个是spring.factories中配置的spring.autoconfigure.exclude

PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

这里比较严谨,在checkExcludedClasses 中会判断如果程序员自己指定要排除的类并不存在,是会报错的。

最后通过configurations.removeAll(exclusions);移除了程序员指定要移除的类,不进行自动装配。

  1. 过滤

// 获取spring.factories中的AutoConfigurationImportFilter对AutoConfiguration进行过滤
// 默认会拿到OnBeanCondition、OnClassCondition、OnWebApplicationCondition
// 这三个会去判断上面的AutoConfiguration是否符合它们自身所要求的条件,不符合的会过滤掉,表示不会进行解析了
// 会利用spring-autoconfigure-metadata.properties中的配置来进行过滤
// spring-autoconfigure-metadata.properties文件中的内容是利用Java中的AbstractProcessor技术在编译时生成出来的
configurations = getConfigurationClassFilter().filter(configurations);

在Spring Boot源码编译的时候会生成一个spring-autoconfigure-metadata.properties里面记录了一些类和他依赖的类。也就是这个类需要满足什么条件才会进行自动装配。

首先通过getConfigurationClassFilter获取过滤器。

getConfigurationClassFilter

-->getAutoConfigurationImportFilters

通过SPI,获取到spring.factories中的过滤器类。

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);}

得到3个过滤器。

拿到过滤器以后,会调用filter方法,在filter方法中通过过滤器的match方法,对自动装配类进行过滤。

最后过滤器中根据spring-autoconfigure-metadata.properties中的配置,使用多线程对自动配置的类进行过滤。把不符合条件的类过滤掉了。这样原来可能有100多个配置类,经过过滤以后就只剩下几十个了。有利于加快启动速度。

  1. 记录

// configurations表示合格的,exclusions表示被排除的,把它们记录在ConditionEvaluationReportAutoConfigurationImportListener中
fireAutoConfigurationImportEvents(configurations, exclusions);
  1. 返回

// 最后返回的AutoConfiguration都是符合条件的
return new AutoConfigurationEntry(configurations, exclusions);

AutoConfigurationGroup

我们回头看看,AutoConfigurationImportSelector实现了DeferredImportSelector,上面说会执行selectImports,实际上这里还需要仔细看下。 因为DeferredImportSelector里面重写了getImportGroup,因此进行了分组。

这个Group是一个内部类,实际上这一组也就只有AutoConfigurationImportSelector一个成员。

在运行时会每个成员都会先执行AutoConfigurationGroup中的process,将所有成员的annotationMetadata放入到一个map中,所有成员都收集好以后,会调用AutoConfigurationGroup中的selectImports方法一起执行。 所以大家在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports打断点debug是不行的。 需要打在AutoConfigurationGroup的selectImport才行。

    @Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() -> String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));// 获取所有自动配置类,并赋值给autoConfigurationEntries和entriesAutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);// 每个自动配置类对应annotationMetadata,selectImports()方法会取出来用for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}
}@Override
public Iterable<Entry> selectImports() {if (this.autoConfigurationEntries.isEmpty()) {return Collections.emptyList();}Set<String> allExclusions = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());Set<String> processedConfigurations = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));processedConfigurations.removeAll(allExclusions);// 给自动配置类排序return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream().map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)).collect(Collectors.toList());
}

再补充一下DeferredImportSelector会在所有的用户定义的bean解析完以后才会去解析。这就是为什么如果用户自定义了一个Bean,自动配置就不会去解析进行自动注入了。例如如果用户自己定义了ServletWebServerFactory。那么Spring Boot就不会去自动装配容器相关的配置了。条件注解这里就不讲解了。

@AutoConfigurationPackage

这个注解import了AutoConfigurationPackages.Registrar.class

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

Registrar实现了ImportBeanDefinitionRegistrar,在启动时会执行registerBeanDefinitions方法,注册PackageImports

重点看下PackageImports的构造方法,这个方法会去找注解上是否配置了扫描路径,如果没有配置则会将启动类(MyApplicatoin)所在的包路径作为扫描路径,封装成BasePackagesBeanDefinition注册到Spring容器中。

PackageImports(AnnotationMetadata metadata) {AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {packageNames.add(basePackageClass.getPackage().getName());}// MyApplicatoin类所在的包if (packageNames.isEmpty()) {packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));}this.packageNames = Collections.unmodifiableList(packageNames);
}

后续其他中间件就可以直接拿到这个路径了。比如如果程序员没有指定mybaits的扫描路径,mybatis就可以直接拿到这个路径去扫描mapper。

@ComponentScan

再来看下这个注解。excludeFilters配置了两个值,用于指定不需要扫描的情况。

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

AutoConfigurationExcludeFilter

AutoConfigurationExcludeFilter类实现了TypeFilter,在扫描时会通过match方法进行过滤。

首先判断当前类上是否添加了@Configuration注解

private boolean isConfiguration(MetadataReader metadataReader) {return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}

然后判断下当前的这个类是否在spring.factories中也配置了。如果在spring.factories中也经配置了。那么在扫描的时候就会排除掉,等着自动装配通过spring.factories进行装配。

private boolean isAutoConfiguration(MetadataReader metadataReader) {return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}

TypeExcludeFilter

TypeExcludeFilter也实现了TypeFilter

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {// 从Spring容器中获取TypeExcludeFilter,然后进行匹配,匹配的则不解析for (TypeExcludeFilter delegate : getDelegates()) {if (delegate.match(metadataReader, metadataReaderFactory)) {return true;}}}return false;
}

getDelegate方法会从Spring容器中获取TypeExcludeFilter,然后取出来一个一个的去匹配。 这就为程序员提供了一个扩展,我们可以继续TypeExcludeFilter自定义过滤逻辑。但是这里需要注意,如果直接定义一个类继续TypeExcludeFilter,例如下面的写法。这种是不会生效的。

@Component
public class MtbTypeExcludeFilter extends TypeExcludeFilter {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return metadataReader.getAnnotationMetadata().getClassName().equals("com.mtb.service.UserService");}
}
@Bean
public TypeExcludeFilter typeExcludeFilter(){return new MtbTypeExcludeFilter();
}

因为@ComponentScan是在扫描阶段使用的,扫描以后才会去解析发现Bean。所以这里的顺序是有问题的,这样写是不会生效的。

如果想让自定义的TypeExcludeFilter生效,需要利用Spring Boot的机制。

添加spring.factories,添加初始化器。然后在初始化器中添加TypeExcludeFilter。

public class MtbApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.getBeanFactory().registerSingleton("mtbTypeExcludeFilter", new MtbTypeExcludeFilter());}
}

只要在扫描前将自定义的filter注册到spring容器中就可以了。

补充说明:因为@EnableAutoConfiguration的自动装配是通过DeferredImportSelector实现的,所以会先扫描,然后再进行自动装配的解析。

学习了Spring Boot的自动装配原理后,我们来看下如何去自己实现一个Starter。

自定义starter

关于分析的过程就不多说了,大家可以自己去看下mybatis或者redis的starter。这里直接开干了

命名规范

首先了解下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分为两个部分,starter和autoconfigure。 其中starter只是一个空的jar文件,负责管理依赖。autoconfigure才是真正实现自动装配的地方。

实战示例

创建工程

下面我们也来动手模拟一个。先新建一个Project作为最上层的父工程

再创建两个module

删除启动类、resources文件夹、test文件夹

修改pom

mtb-spring-boot的pom,添加基础依赖

<?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.mtb</groupId><artifactId>mtb-spring-boot</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><modules><module>mtb-spring-boot-starter</module><module>mtb-spring-boot-autoconfigure</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies>
</project>

mtb-spring-boot-starter的pom。添加对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><parent><groupId>com.mtb</groupId><artifactId>mtb-spring-boot</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>mtb-spring-boot-starter</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>com.mtb</groupId><artifactId>mtb-spring-boot-autoconfigure</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>
</project>

mtb-spring-boot-autoconfigure的pom

<?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>com.mtb</groupId><artifactId>mtb-spring-boot</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>mtb-spring-boot-autoconfigure</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><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

package com.mtb.config;import org.springframework.boot.context.properties.ConfigurationProperties;/*** @Description:* @Author: 毛同彬* @CreateDate: 2023/3/8 20:03* @Version: 1.0*/@ConfigurationProperties("mtb.hello")
public class HelloProperties {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}

IndexController

package com.mtb.controller;import com.mtb.config.HelloProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Description:* @Author: 毛同彬* @CreateDate: 2023/3/8 20:06* @Version: 1.0*/@RestController
public class IndexController {HelloProperties helloProperties;public IndexController(HelloProperties helloProperties) {this.helloProperties = helloProperties;}@RequestMapping("/")public String index() {return helloProperties.getName() + "欢迎您!";}
}

HelloAutoConfitguration

package com.mtb.config;import com.mtb.controller.IndexController;
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;/*** @Description:* @Author: 毛同彬* @CreateDate: 2023/3/8 20:07* @Version: 1.0*/@Configuration
@ConditionalOnProperty(value = "mtb.hello.name")
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfitguration {@AutowiredHelloProperties helloProperties;@Beanpublic IndexController indexController() {return new IndexController(helloProperties);}
}

添加spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.mtb.config.HelloAutoConfitguration

测试

创建一个test工程用来测试

pom中引入mtb-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.7.0</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>mtb-spring-boot-starter-test</artifactId><version>0.0.1-SNAPSHOT</version><name>mtb-spring-boot-starter-test</name><description>mtb-spring-boot-starter-test</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>com.mtb</groupId><artifactId>mtb-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

启动后访问http://localhost:8080/

不要慌,访问不到是正常的。因为我们在spring.factories中指定了自动配置类HelloAutoConfitguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.mtb.config.HelloAutoConfitguration

而这个配置类上添加了条件注解。只有property中存在 mtb.hello.name配置时才会解析。然后HelloProperties才会生效,才会创建IndexController这个Bean。不然IndexController的Bean不会创建,肯定访问不到。

我们在spring.properties中添加下配置。

再重启一下。

OK,大功告成!

分享就到这里了,欢迎大家评论、转发加关注。懒得留言就点个赞吧,谢谢大家!

相关文章:

深度解析Spring Boot自动装配原理

废话不多说了&#xff0c;直接来看源码。源码解析SpringBootApplication我们在使用idea创建好Spring Boot项目时&#xff0c;会发现在启动类上添加了SpringBootApplication注解&#xff0c;这个注解就是Spring Boot的核心所在。点击注解可以查看到到它的实现ementType.TYPE) Re…...

Redis性能分析相关-channel=[id: 0xbee27bd4, L:/127.0.0.1:63156

redis宕机...

Linux:环境变量

目录一、环境变量的理解&#xff08;1&#xff09;什么是环境变量&#xff1f;&#xff08;2&#xff09;Linux中的环境变量二、环境变量的使用&#xff08;1&#xff09;PATH环境变量&#xff08;2&#xff09;和变量相关的指令三、环境变量与普通变量的区别在平时使用电脑的时…...

Codeforces Round 703 (Div. 2)(A~D)

A. Shifting Stacks给出一个数组&#xff0c;每次可以将一个位置-1&#xff0c;右侧相邻位置1&#xff0c;判断是否可以经过若干次操作后使得数列严格递增。思路&#xff1a;对于每个位置&#xff0c;前缀和必须都大于该位置应该有的最少数字&#xff0c;即第一个位置最少是0&a…...

Django项目5——基于tensorflow serving部署深度模型——windows版本

1&#xff1a;安装docker for windows 可能需要安装WLS2&#xff0c;用于支持Linux系统&#xff0c;参照上面的教程安装 2&#xff1a;在Powershell下使用docker docker pull tensorflow/serving3&#xff1a;在Powershell下启动tensorflow serving docker run -p 8500:8500 …...

MySQL基础篇3

第一章 多表关系实战 1.1 实战1&#xff1a;省和市 方案1&#xff1a;多张表&#xff0c;一对多 方案2&#xff1a;一张表&#xff0c;自关联一对多 id1 name‘北京’ p_id null; id2 name‘昌平’ p_id1 id3 name‘大兴’ p_id1 id3 name‘上海’ p_idnull id4 name‘浦东’…...

携程 x TiDB丨应对全球业务海量数据增长,一栈式 HTAP 实现架构革新

随着新冠病毒疫情的缓解和控制&#xff0c;全球旅游业逐渐开始重新复苏。尤其在一些度假胜地&#xff0c;游客数量已经恢复到疫情前的水平。 携程作为全球领先的一站式旅行平台&#xff0c;旗下拥有携程旅行网、去哪儿网、Skyscanner 等品牌。携程旅行网向超过 9000 万会员提供…...

记一次Kafka warning排查过程

1、前因 在配合测试某个需求的时候&#xff0c;正好看到控制台打印了个报错&#xff0c;如下&#xff1a; 2023-03-06 17:05:58,565[325651ms][pool-28-thread-1][org.apache.kafka.common.utils.AppInfoParser][WARN] - Error registering AppInfo mbean javax.management.I…...

MySQL学习笔记(6.视图)

1. 视图作用 (1). 简化业务&#xff0c;将多个复杂条件&#xff0c;改为视图 (2). mysql对用户授权&#xff0c;只能控制表权限&#xff0c;通过视图可以控制用户字段权限。 (3). 可以避免基本表变更&#xff0c;影响业务。只需更改视图即可。 2. 视图&#xff08;创建&…...

java多线程与线程池-01多线程知识复习

多线程知识复习 文章目录 多线程知识复习第1章 多线程基础1.1.2 线程与进程的关系1.2 多线程启动1.2.1 线程标识1.2.2 Thread与Runnable1.2.3 run()与start()1.2.4 Thread源码分析1.3 线程状态1.3.1 NEW状态1.3.2 RUNNABLE状态1.3.3 BLOCKED状态1.3.4 WAITING状态1…...

Typescript - 将命名空间A导入另一个命名空间B作为B的子命名空间,并全局暴露命名空间B

前言 最近相统一管理 ts 中的类型声明&#xff0c;这就需要将各模块下的命名空间整合到全局的命名空间下&#xff0c;牵涉到从别的文件中引入命名空间并作为子命名空间在全局命名空间中统一暴露。 将命名空间A导入另一个命名空间B作为B的子命名空间 文件说明 assets.ts 文件中…...

Windows下实现Linux内核的Python开发(WSL2+Conda+Pycharm)

许多软件可以通过Python交互&#xff0c;但没有开发Windows版本&#xff0c;这个时候装双系统或虚拟机都很不方便&#xff0c;可以采取WSL2CondaPycharm的策略来进行基于Linux内核的Python开发。启动WSL2&#xff0c;安装Linux内核教程&#xff1a;旧版 WSL 的手动安装步骤 | M…...

新闻发布网站分析及适用场景

在当今数字时代&#xff0c;发布新闻的渠道已经不再局限于传统媒体&#xff0c;越来越多的企业、组织和个人开始使用互联网平台发布新闻稿&#xff0c;以提升品牌知名度和影响力。本文将介绍一些可以发布新闻的网站&#xff0c;并分析其特点和适用场景。一、新闻稿发布平台1.新…...

云原生时代顶流消息中间件Apache Pulsar部署实操之Pulsar IO与Pulsar SQL

文章目录Pulsar IO (Connector连接器)基础定义安装Pulsar和内置连接器连接Pulsar到Cassandra安装cassandra集群配置Cassandra接收器创建Cassandra Sink验证Cassandra Sink结果删除Cassandra Sink连接Pulsar到PostgreSQL安装PostgreSQL集群配置JDBC接收器创建JDBC Sink验证JDBC …...

Input子系统(一)启动篇

代码路径 基于AndroidS&#xff08;12.0&#xff09;代码 system/core/libutils/Threads.cppframeworks/base/services- java/com/android/server/SystemServer.java- core- java/com/android/server/input/InputManagerService.java- jni/com_android_server_input_InputMan…...

WuThreat身份安全云-TVD每日漏洞情报-2023-03-08

漏洞名称:Agilebio Lab Collector 远程命令执行 漏洞级别:高危 漏洞编号:CVE-2023-24217,CNNVD-202303-375 相关涉及:Agilebio Lab Collector 4.234 漏洞状态:EXP 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2023-05536 漏洞名称:PrestaShop “Xen Forum”模…...

ABP IStringLocalizer部分场景不生效的问题

问题描述&#xff1a; 本地项目依赖注入本地化服务时候生效&#xff0c;第三方项目调用本地接口时候出现本地化失效的问题。 解决方案&#xff1a; 第三方服务封装的 GetHttp 请求的请求头中添加 语言相关信息 request.Headers.Add("accept-language", "zh-C…...

数组(四)-- LC[167] 两数之和-有序数组

1 两数之和 1.1 题目描述 题目链接&#xff1a;https://leetcode.cn/problems/two-sum/description/ 1.2 求解思路 1. 暴力枚举 最容易想到的方法是枚举数组中的每一个数 x&#xff0c;寻找数组中是否存在 target - x 参考代码 class Solution(object):def twoSum(self, n…...

Mac电脑,python+appium+安卓模拟器使用步骤

1、第一步&#xff0c;环境搭建&#xff0c;参考这位博主的文章&#xff0c;很齐全 https://blog.csdn.net/qq_44757414/article/details/128142859 我在最后一步安装appium-doctor的时候&#xff0c;提示权限不足&#xff0c;换成sudo appium-doctor即可 2、第二步&#xff0…...

Linux命令·find进阶

find是我们很常用的一个Linux命令&#xff0c;但是我们一般查找出来的并不仅仅是看看而已&#xff0c;还会有进一步的操作&#xff0c;这个时候exec的作用就显现出来了。 exec解释&#xff1a;-exec 参数后面跟的是command命令&#xff0c;它的终止是以;为结束标志的&#xff0…...

R语言ggplot2 | 用百分比格式表示数值

&#x1f4cb;文章目录Percent() 函数介绍例子1&#xff0c;在向量中格式化百分比&#xff1a;例子2&#xff0c;格式化数据框列中的百分比&#xff1a;例子3&#xff0c;格式化多个数据框列中的百分比&#xff1a;如何使用percent()函数在绘图过程展示通常在绘图时&#xff0c…...

【代码训练营】day53 | 1143.最长公共子序列 1035.不相交的线 53. 最大子序和

所用代码 java 最长公告子序列 LeetCode 1143 题目链接&#xff1a;最长公告子序列 LeetCode 1143 - 中等 思路 这个相等于上一题的不连续状态 dp[i] [j]&#xff1a;以[0, i-1]text1和以[0, j-1]text2 的最长公共子序列的长度为dp[i] [j]递推公式&#xff1a; 相同&#x…...

消息队列理解

为什么使用消息队列 使⽤消息队列主要是为了&#xff1a; 减少响应所需时间和削峰。降低系统耦合性&#xff08;解耦/提升系统可扩展性&#xff09;。 当我们不使⽤消息队列的时候&#xff0c;所有的⽤户的请求会直接落到服务器&#xff0c;然后通过数据库或者 缓存响应。假…...

【Linux内核一】在Linux系统下网口数据收发包的具体流向是什么?

在TCP/IP网络分层模型里&#xff0c;整个协议栈被分成了物理层、链路层、网络层&#xff0c;传输层和应用层。物理层对应的是网卡和网线&#xff0c;应用层对应的是我们常见的Nginx&#xff0c;FTP等等各种应用。Linux实现的是链路层、网络层和传输层这三层。 在Linux内核实现中…...

南京、西安集成电路企业和高校分布一览(附产业链主要厂商及高校名录)

前言 3月2日&#xff0c;国务院副总理刘鹤在北京调研集成电路企业发展&#xff0c;并主持召开座谈会。刘鹤指出&#xff0c;集成电路是现代化产业体系的核心枢纽&#xff0c;关系国家安全和中国式现代化进程。他表示&#xff0c;我国已形成较完整的集成电路产业链&#xff0c;也…...

后端Java随机比大小游戏实战讲解

## - 利用print打印输出提示用户 ## - 利用Scanner函数抓取数据 ## - 利用Math方法实现随机数 #### 1.首先用到的是print函数&#xff0c;对用户进行提醒进一步的操作 通过System.out.print();提示用户进行选择买大买小。 #### 2.然后利用Scanner函数&#xff0c;对用户输出…...

dolphinschedule使用shell任务结束状态研究

背景&#xff1a;配置的dolphin任务&#xff0c;使用的是shell&#xff0c;shell里包含了spark-submit 如下截图。 dolphin shell 介绍完毕&#xff0c;开始说明现象。 有天有人调整了集群的cdp配置&#xff0c;executor-cores max1 我之前这里写的是2&#xff0c;所以spark任…...

如何用postman实现接口自动化测试

postman使用 开发中经常用postman来测试接口&#xff0c;一个简单的注册接口用postman测试&#xff1a; 接口正常工作只是最基本的要求&#xff0c;经常要评估接口性能&#xff0c;进行压力测试。 postman进行简单压力测试 下面是压测数据源&#xff0c;支持json和csv两个格…...

AHRS(航姿参考系统)IMU(惯性测量单元)和INS的分析对比研究-2023-3-8

名称 AHRS俗称航姿参考系统 IMU 惯性测量单元 INS 惯性导航系统 英文 全称 &#xff08;Attitude and Heading Reference System&#xff09; &#xff08;Inertial Measurement Unit&#xff09; Inertial Navigation System&#xff09; 组成 加速度计&#xff0c;磁…...

企业管理经典书籍推荐

几乎每一位成功的商业人士都有着良好的阅读习惯。并且他们阅读涉猎的范围也大多与企业管理和领导力有关。而关于企业管理经典书籍&#xff0c;我推荐你看以下这两本。一本是《经理人参阅&#xff1a;企业管理实务》&#xff0c;另一本是《经理人参阅&#xff1a;领导力提升》。…...