【Spring高级】Springboot自动配置类原理

news/2024/9/25 17:18:48/

目录

  • 如何引入第三方库
  • 第三方库与当前项目Bean重复
  • 自定义自动配置类

自动配置类通常位于Spring Boot的自动配置模块中,并且被标记为 @Configuration类。这些类使用 @Conditional注解来检查某些条件是否满足,如果满足,则创建和配置相关的bean。这些条件可能包括检查类路径上是否存在特定的类、检查应用程序的属性设置、检查是否存在特定的bean等。

自动配置类还可用于自动配置各种常见的Spring组件和第三方库。这些自动配置类大大简化了应用程序的配置过程,使得开发者可以更加专注于实现业务逻辑,而不是花费大量时间在繁琐的配置上。

如何引入第三方库

看如下示例:

java">package com.cys.spring.chapter16;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.GenericApplicationContext;public class TestAutoConfig {@SuppressWarnings("all")public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);context.refresh();for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");System.out.println(context.getBean(Bean1.class));}@Configuration // 本项目的配置类@Import({AutoConfiguration1.class, AutoConfiguration2.class})static class Config {}@Configuration // 第三方的配置类static class AutoConfiguration1 {@Beanpublic Bean1 bean1() {return new Bean1("第三方");}}static class Bean1 {private String name;public Bean1() {}public Bean1(String name) {this.name = name;}@Overridepublic String toString() {return "Bean1{" +"name='" + name + '\'' +'}';}}@Configuration // 第三方的配置类static class AutoConfiguration2 {@Beanpublic Bean2 bean2() {return new Bean2();}}static class Bean2 {}
}

运行:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.cys.spring.chapter16.TestAutoConfig$AutoConfiguration1
bean1
com.cys.spring.chapter16.TestAutoConfig$AutoConfiguration2
bean2
>>>>>>>>>>>>>>>>>>>>>>>>>
Bean1{name='第三方'}

首先在我们当前应用的配置类Config中,使用@Import({AutoConfiguration1.class, AutoConfiguration2.class})将模拟出的第三方的配置类AutoConfiguration1AutoConfiguration2导入到了我们自己的应用程序中,接着就可以使用他们俩的配置类中创建的Bean。

接着我们优化一下,不把导入的类名写死在代码,而是配合ImportSelector,将其写在配置文件中,文件名需要是src/main/resources/META-INF/spring.factories

配置文件内容:

com.cys.spring.chapter16.TestAutoConfig$MyImportSelector=\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration1,\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration2

然后修改测试类如下:

java">@Configuration // 本项目的配置类
@Import(MyImportSelector.class)
//    @Import({AutoConfiguration1.class, AutoConfiguration2.class})
static class Config {
}/*** 创建一个ImportSelector,返回配置文件的类名*/
static class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);return names.toArray(new String[0]);}
}

这样也是没问题的。

第三方库与当前项目Bean重复

如果第三方库与当前项目Bean重复,默认是引入的第三方的先创建,然后自己程序的再创建,且后创建的可以覆盖前面的。

可以设置为不允许覆盖,就会有Bean重复的报错,设置方法如下:

java">setAllowBeanDefinitionOverriding(false);

修改后如下:

java">public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();// 如果有同名的Bean,是否允许覆盖context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);context.registerBean("config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);context.refresh();for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");System.out.println(context.getBean(Bean1.class));}

运行后报错:

Exception in thread "main" org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in com.cys.spring.chapter16.TestAutoConfig$Config: Cannot register bean definition [Root bean: class [null];  defined in com.cys.spring.chapter16.TestAutoConfig$Config] for bean 'bean1': There is already [Root bean: class [null]; defined in class path resource [com/cys/spring/chapter16/TestAutoConfig$AutoConfiguration1.class]] bound.

意思就是无法注册TestAutoConfig C o n f i g 中的 b e a n 1 的 b e a n d e f i n i t i o n ,因为在 T e s t A u t o C o n f i g Config中的bean1的 bean definition,因为在TestAutoConfig Config中的bean1beandefinition,因为在TestAutoConfigAutoConfiguration1.class中已经存在。

实际生产中不建议这么做,建议开启覆盖。

那么如果要调整注册 bean definition的顺序呢,让自己程序的先注册,第三方的后注册呢?也可以,但实际生产也不建议这么做。

真要修改的话可以创建ImportSelector时,实现DeferredImportSelector,达到延迟注册的目的。如下

java">static class MyImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);return names.toArray(new String[0]);}}

自定义自动配置类

在Springboot中,自动配置通常使用@EnableAutoConfiguration。源码如下:

java">//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.boot.autoconfigure;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;
import org.springframework.context.annotation.Import;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

他也是使用了@Import注解,去找spring.fatories文件中找AutoConfigurationImportSelector的key。AutoConfigurationImportSelectory中也有个selectImports,其源码如下:

java">public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();private static final String[] NO_IMPORTS = new String[0];private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";private ConfigurableListableBeanFactory beanFactory;private Environment environment;private ClassLoader beanClassLoader;private ResourceLoader resourceLoader;private AutoConfigurationImportSelector.ConfigurationClassFilter configurationClassFilter;public AutoConfigurationImportSelector() {}public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
}

主要方法getAutoConfigurationEntry

java">protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}
}

进入方法getCandidateConfigurations

java">protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");return configurations;
}

他也是使用方法SpringFactoriesLoader.loadFactoryNames找配置类名称,其中key是this.getSpringFactoriesLoaderFactoryClass()方法返回值,

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

进入方法看到,他最后找的类名为EnableAutoConfiguration的key。

根据这个,我们在spring.factories中添加这个key:

com.cys.spring.chapter16.TestAutoConfig$MyImportSelector=\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration1,\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration2org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration1,\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration2

并在配置类上加上@EnableAutoConfiguration:

java">package com.cys.spring.chapter16;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;import java.io.IOException;public class TestSelfAutoConfiguration {@SuppressWarnings("all")public static void main(String[] args) throws IOException {AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();StandardEnvironment env = new StandardEnvironment();env.getPropertySources().addLast(new SimpleCommandLinePropertySource("--spring.datasource.url=jdbc:mysql://localhost:3306/test","--spring.datasource.username=root","--spring.datasource.password=root"));context.setEnvironment(env);context.registerBean("config", Config.class);context.refresh();for (String name : context.getBeanDefinitionNames()) {String resourceDescription = context.getBeanDefinition(name).getResourceDescription();if (resourceDescription != null)System.out.println(name + " 来源:" + resourceDescription);}context.close();}@Configuration // 本项目的配置类
//    @Import(MyImportSelector.class)
//    @Import(AutoConfigurationImportSelector.class)@EnableAutoConfigurationstatic class Config {@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory() {return new TomcatServletWebServerFactory();}}static class MyImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);}}@Configuration // 第三方的配置类static class AutoConfiguration1 {@Beanpublic Bean1 bean1() {return new Bean1();}}@Configuration // 第三方的配置类static class AutoConfiguration2 {@Beanpublic Bean2 bean2() {return new Bean2();}}static class Bean1 {}static class Bean2 {}
}

运行后检查,返现bean1和bean2


http://www.ppmy.cn/news/1426669.html

相关文章

HttpServletRequest/Response

HttpServletRequest 一些常用类的用法 package Demo;import javax.jws.WebService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import ja…

python中isinstance()作用

Python 中的 isinstance() 函数具有以下作用 &#xff08;类似Java 中的 instanceof &#xff09; 类型检查&#xff1a; isinstance() 用于判断一个对象是否属于特定的类型&#xff08;或类型集合&#xff09;。它接受两个参数&#xff1a;一个是待检测的对象&#xff0c;另一…

web、android和ios共同能够实现滑动及同步测试(实测)

web、android和ios共同能够实现滑动及同步测试&#xff08;实测&#xff09; 1、三者滑动效果 描述&#xff1a;在web页面拼好了之后&#xff0c;使用android和ios进行测试的时候&#xff0c;android轮播图能够实现触摸滑动&#xff0c;但是ios不可以&#xff0c;于是添加一下…

提升性能:QML Canvas 绘图优化技巧

减少绘制操作&#xff1a; 当我们有一个动态更新的图形&#xff0c;例如实时更新的数据可视化图表&#xff0c;可以通过设置一个定时器来控制更新频率&#xff0c;而不是每次数据更新都重新绘制整个图形。 使用硬件加速&#xff1a; 通过将Canvas的renderTarget属性设置为Canv…

springboot 启动非web应用

问题描述 非web应用&#xff0c;启动完成自动退出 问题原因 因为任务完成了&#xff0c;所以系统退出了。需要给spring一个任务&#xff0c;而且这个任务无法解决 包括&#xff1a; web定时任务一个无法完成的任务 解决方案 其中一个是&#xff1a; 非web不自动退出 注意…

深入微服务框架:构建高效、可扩展与弹性的现代应用架构

前言&#xff1a;当今快速迭代和多变的商业环境中&#xff0c;传统的单体应用程序面临着一系列挑战&#xff0c;包括难以管理复杂性、缺乏灵活性以及无法有效扩展等问题。随着业务需求的不断增长和技术栈的不断演进&#xff0c;企业亟需一种更加模块化、易于管理和扩展的应用程…

探索C语言数据结构:利用顺序表完成通讯录的实现

在好久之前我就已经学习过顺序表&#xff0c;但是在前几天再次温习顺序表的时候&#xff0c;我惊奇的发现顺序编表可以完成我们日常使用的通讯录的功能&#xff0c;那么今天就来好好通过博客总结一下通讯录如何完成吧。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留…

设计模式- 建造者模式(Builder Pattern)结构|原理|优缺点|场景|示例

目录 设计模式&#xff08;分类&#xff09; 设计模式&#xff08;六大原则&#xff09; 创建型 工厂方法 抽象工厂模式 单例模式 建造者模式 原型模式 结构型 适配器模式 建造者模式&#xff08;Builder Pattern&#xff09;是一种创…