04-详解SpringBoot自动装配的原理,依赖属性配置的实现,源码分析

news2024/11/16 4:46:30

自动装配原理

依赖属性配置

提供Bean用来封装配置文件中对应属性的值

@Data
public class Cat {
    private String name;
    private Integer age;
}
@Data
public class Mouse {
    private String name;
    private Integer age;
}
cartoon:
  cat:
    name: "图多盖洛"
    age: 5
  mouse:
    name: "泰菲"
    age: 1

读取yml文件中的数据,将业务功能Bean运行需要的数据抽取出来封装到CartoonProperties对象中

  • 缺点: 要想封装yml文件中的数据这个Bean必须由Spring管控,但其实如果我们没有导入业务功能Bean就没必要读取yml文件中的数据
Component 
@ConfigurationProperties(prefix = "cartoon")
@Data // 需要给Cat和Mouse提供对应的getter和setter方法,才能把yml文件中的数据注入到Cat和Mouse对象中
public class CartoonProperties { 
    private Cat cat;
    private Mouse mouse;
}

在业务Bean中根据需要读取CartoonProperties对象中的数据,@EnableConfigurationProperties开启属性类的配置绑定功能并强制把其注册到容器中

  • 如果开发者在yml文件中配置了对应的属性的值就使用配置的值,如果没有配置就使用默认值
@Data
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{
    private Cat cat;
    private Mouse mouse;

    private CartoonProperties cartoonProperties;
	// 自动注入,如果有参的构造方法只有一个@Autowired注解可以省略
    public CartoonCatAndMouse(CartoonProperties cartoonProperties){
        this.cartoonProperties = cartoonProperties;
        cat = new Cat();
        // 如果开发者在yml文件中配置了对应的属性就使用配置的值,如果没有配置就使用默认值
        cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName()) ? cartoonProperties.getCat().getName() : "tom");
        cat.setAge(cartoonProperties.getCat()!=null && cartoonProperties.getCat().getAge()!=null ? cartoonProperties.getCat().getAge() : 3);
        
        mouse = new Mouse();
        mouse.setName(cartoonProperties.getMouse()!=null && StringUtils.hasText(cartoonProperties.getMouse().getName()) ? cartoonProperties.getMouse().getName() : "jerry");
        mouse.setAge(cartoonProperties.getMouse()!=null && cartoonProperties.getMouse().getAge()!=null ? cartoonProperties.getMouse().getAge() : 4);
    }

    public void play(){
        System.out.println(cat.getAge()+"岁的"+cat.getName()+"和"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
    }
}

使用@Import方式导入业务Bean,避免业务Bean强制加载,根据需要导入,降低Spring管控Bean的强度

  • 缺点: 自动配置类我们也没必要强制加载成容器的Bean,应当是满足某种条件时才加载
@SpringBootApplication
@Import(CartoonCatAndMouse.class)
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(App.class);
        CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);
        bean.play();
        System.out.println(ctx.getBean(Cat.class));
    }
}

自动配置源码分析

@SpringBootApplication底层相关的注解

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}
SpringBootApplication底层注解注解的底层注解
@SpringBootConfiguration@Configuration,说明Spring Boot程序的启动类也是一个配置类
@EnableAutoConfiguration@AutoConfigurationPackage --> @ Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportsSelector.class)
@ComponentScan指定扫描过滤的规则FilterType.CUSTOM和TypeExcludeFilter.class等, 默认扫描主程序所在包及其子包下的所有组件

@Import(AutoConfigurationPackages.Registrar.class)注解: 设置启动类的包作为基础扫描包, 后续将该包及其子包下注解标识类注册成Bean添加到容器中

  • AutoConfigurationPackages.RegistrarAutoConfigurationPackages(抽象类)的静态内部类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    // 记录所有要扫描的包,启动类所在的包及其子包
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};

    static classimplementsImportBeanDefinitionRegistrar,DeterminableImportsRegistrar{
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // metadata是元数据,可以获取启动类上的所有注解信息
            // new PackageImports(metadata).getPackageNames()获取到的就是启动类所在的包
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    }
}


public static void register(BeanDefinitionRegistry registry, String... packageNames){
    // 判断容器中是否加载过AutoConfigurationPackages
    if (registry.containsBeanDefinition(BEAN)){
        BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
        beanDefinition.addBasePackages(packageNames);
    }
    else{
        // 注册一个叫com...AutoConfigurationPackages的Bean,将要扫描的包封装到BasePackagesBeanDefinition对象中
        registry.registerBeanDefinition(BEANnew BasePackagesBeanDefinition(packageNames));
    }
}

static final class BasePackagesBeanDefinition extends GenericBeanDefinition {
    private final Set<String> basePackages = new LinkedHashSet<>();
	BasePackagesBeanDefinition(String... basePackages) {
        setBeanClass(BasePackages.class);
        setRole(BeanDefinition.ROLE INFRASTRUCTURE);
        addBasePackages(basePackages);
    }
}

@Import(AutoConfigurationImportSelector.class): 指定工程启动时需要向容器中添加的所有自动配置类XxxAutoConfiguration

  • spring-boot-autoconfigure-xxx.jar包里面的META-INF/spring.factories 目录下存放了工程启动时需要加载的所有类(含自动配置类)

在这里插入图片描述

// Spring中的Bean只要实现了XxxAware相关的接口并实现接口的setXxx方法,就可以在当前Bean中使用对应的对象
// Ordered表示加载的顺序,因为有些Bean加载的时候是要依赖其他Bean的,每个Bean都有对应的加载顺序
// DeferredImportSelector表示推迟的导入选择器
public class AutoConfigurationImportSelector implements DeferredImportSelector,BeanClassLoaderAware,ResourceLoaderAwareBeanFactoryAwareEnvironmentAwareOrdered {   
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    // 使用ApplicationContext接口中的相关方法
    String[] beans = applicationContext.getBeanDefinitionNames();
    for (String bean : beans) {
        System.out.println(bean);
    }
}

@Override
// selectImports方法的返回值是一个String类型数组,数组的元素就是我们要批量导入的组件
public String[] selectImports (AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }    
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}


protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){
    // 判断元注解也就是我们的启动类是否是可用的
    if(!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY:
    } 
    // 获取启动类上的所有注解及其属性,@EnableAutoConfiguration注解有exclude,excludeName两个属性可以按照Class对象和全类名排除不需要加载的类        
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取候选的配置,读取META-INF/spring.factories中的数据,将所有自动配置类的全类名添加到一个List集合中并返回
    List<String> configurations= getCandidateConfigurations(annotationMetadata, attributes);
    // 排除不需要导入的配置类
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations,exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations,exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

// 调用loadFactoryNames方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations =  SpringFactoriesloader.loadFactoryNames(getSpringFactoriesloaderFactoryClass(),
                                                                          getBeanClassLoader());
    Assert.notEmpty(configurations, message: "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;
}

// 调用loadSpringFactories方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable Classloader classloader){
    // 获取类加载器
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == nul1) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 获取字符串
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classloaderToUse).getorDefault(factoryTypeName, Collections,emptylist());
}

// 最终调用的方法
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 {
    // 通过类加载器加载外部资源,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面的META-INF/spring.factories目录下的自动配置类
    Enumeration<URL> urls = classLoader,getResources(FACTORIES_ESOURCE_OCATION);
    while (urls.hasMoreElements()){
        URL url= urls.nextElement();
        UrlResource resource = new UrlResource(url);
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    }
    
}

测试XxxAutoConfiguration

AopAutoConfiguration自动配置类的生效条件

@Configuration(proxyBeanMethods = false)
// 判断是否存在一个配置文件有spring.aop的前缀属性,默认是存在的
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = "auto",
    havingValue = "true",
    matchIfMissing = true
)
public class AopAutoConfiguration {
    public AopAutoConfiguration() {
    }
	...
}

RedisAutoConfiguration自动配置类的生效条件及其绑定的属性配置类 RedisProperties

@Configuration(proxyBeanMethods = false)
// 要加载RedisAutoConfiguration必须有RedisOperations,这个类在spring-boot-starter-data-redis中
@ConditionaionClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({LettuceConnectionConfiguration,class, JedisConnectionConfiguration,class})
public class RedisAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<0bject, 0bject> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<ObjectObject> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

RedisProperties封装了yml文件中的spring.redis属性的值

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    // .....
}

自动配置流程

第一步: 将开发过程使用的常用技术列表整理成一个技术集A即所有的自动配置类XxAutoConfiguration,工程启动时默认会全部加载到内存中

  • 这些自动配置类虽然会全部加载到内存中,但不会全部生效, 只有满足实际条件的自动配置类及其内部的才会注册成容器中的Bean
  • 每个自动配置类都对应一个属性配置类,用来封装配置文件中指定前缀的属性值,自动配置类需要用时会从xxxProperties对象中获取

第二步: 将这些常用技术需要设置的参数整理成一个设置集B即所有的属性配置类xxxxProperties用来封装yml文件中对应属性的值

第三步: 开放设置集B的配置覆盖接口,若开发者在yml文件中配置了某属性的值,对应属性配置对象中的对应属性就可以获取到值,如果没有配置对应属性为默认值

  • SpringBoot默认会在底层配好所有的组件, 但是如果用户自己配置了以用户的优先,如直接通过定义@Bean替换底层的组件或者去修改这个组件获取的配置文件值

第四步: 生效的自动配置类从对应属性配置对象中获取值然后为要创建的组件赋值(约定大于配置),若获取的属性值是null就使用默认值,如果不是就使用获取到的值

第五步: 加载用户自定义的Bean和导入的其他坐标,检测每个自动配置类的加载条件是否满足, 最后初始化SpringBoot的基础环境

变更自动配置

@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration")// 排除加载的自动配置类
//@Import(CartoonCatAndMouse.class) // 根据条件装配这个自动配置类
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(App.class);
        CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);
        bean.play();
        System.out.println(ctx.getBean(Cat.class));
    }
}

SpringBoot中自带的自动配置类有130个,后面的技术若想要实现自动配置功能需要手动在t工程中的resources/META-INF目录下添加spring.factories文件

  • SpringBoot默认会扫描我们当前工程里面所有的META-INF/factories文件(每个jar包都是一个工程)
# Auto Configure 
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.itheima.bean.CartoonCatAndMouse

在配置文件中排除加载的自动配置类

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1201239.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

11.12总结

这一周主要写了个人中心的几个功能&#xff0c;资料修改&#xff0c;收货地址的创建和修改删除&#xff0c;还有主页界面和商品界面

ZYNQ_project:ram_dual_port

伪双端口ram&#xff1a;写端口&#xff1a;clk_w,en_A,we_A,addr_A,din_A;读端口:clk_r,en_B,addr_B;dout_B. 设计读写模块&#xff0c;写入256个数据&#xff0c;再读出256个数据。 输入时钟100Mhz&#xff0c;输出时钟50Mhz。 多bit数据&#xff0c;高速时钟域到低速时钟…

Excel中使用数据验证、OFFSET实现自动更新式下拉选项

在excel工作簿中&#xff0c;有两个Sheet工作表。 Sheet1&#xff1a; Sheet2&#xff08;数据源表&#xff09;&#xff1a; 要实现Sheet1中的“班级”内容&#xff0c;从数据源Sheet2中获取并形成下拉选项&#xff0c;且Sheet2中“班级”内容更新后&#xff0c;Sheet1中“班…

小黑子—springMVC:第二章

springMVC入门2.0 4、小黑子的springMVC拦截器4.1 Interceptor简介4.2 拦截器快速入门4.3 拦截器执行顺序4.4 拦截器执行原理 5、小黑子的springMVC全注解开发5.1 spring-mvc.xml中组件转化为注解形式5.1.1 消除spring-mvc.xml一二三 5.1.2 消除web.xml 6、小黑子的springMVC组…

SpringBoot之手写starter

SpringBoot之手写starter 在开始之前呢&#xff0c;我们需要了解一些概念 1、starter介绍 spring boot 在配置上相比spring要简单许多, 其核心在于spring-boot-starter, 在使用spring boot来搭建一个项目时, 只需要引入官方提供的starter, 就可以直接使用, 免去了各种配置。…

数据分析面试题1

1.右表为一组数据&#xff0c;尝试进行简单分析&#xff0c;并给出结论&#xff08;使用公式和图表辅助&#xff09; ①理解数据 userid&#xff1a;用户id神兽印记消耗数量 ②数据清洗 冻结首行&#xff0c;将列标题的英文字段转换成汉字字段检查是否有重复项&#xff1a;…

计算机指令考前小记

RTL寄存器传送语言&#xff1a;简化对指令功能的说明 R[r]&#xff1a;存储器r的内容M[addr]&#xff1a;存储单元addr的内容M[R[r]]&#xff1a;寄存器r的内容所指的存储单元的内容 汇编指令movw 4(%ebp),%ax的RTL语言为&#xff1a;R[ax] <- M[R[ebp]4] 将寄存器EBP的内…

【C++】【Opencv】minMaxLoc()函数详解和示例

minMaxLoc&#xff08;&#xff09;函数 是 OpenCV 库中的一个函数&#xff0c;用于找到一个多维数组中的最小值和最大值&#xff0c;以及它们的位置。这个函数对于处理图像和数组非常有用。本文通过参数和示例详解&#xff0c;帮助大家理解和使用该函数。 参数详解 函数原型…

CSS特效007:绘制3D文字,类似PS效果

css实战中&#xff0c;怎么绘制3D文字呢&#xff1f; 实际上理论很简单&#xff0c;使用text-shadow&#xff0c;根据需要调整阴影的颜色、大小、偏移量等参数&#xff0c;以达到你想要的立体效果。下面是一个简单的示例。关键点就是知道如何设置text-shadow。 效果图 源代码 …

Scikit-LLM:一款大模型与 scikit-learn 完美结合的工具!

Scikit-LLM 是文本分析领域的一项重大变革&#xff0c;它将像 ChatGPT 这样强大的语言模型与 scikit-learn 相结合&#xff0c;提供了一套无与伦比的工具包&#xff0c;用于理解和分析文本。 有了 scikit-LLM&#xff0c;你可以发现各种类型的文本数据中的隐藏模式、情感和上下…

python类中的抽象函数,以及继承后子类的比较

抽象函数的定义方式 导包 from abs import ABCMeta,abstractmethod声明抽象类 class Area(object):abstractmethoddef area(self):pass在抽象类中&#xff0c;不用写构造函数&#xff0c;抽象类不能进行实例化 继承抽象类的子类必须将抽象类中的函数进行重写&#xff08;不重…

Mathtype公式自动转Word自带公式

Mathtype公式自动转Word自带公式 前言/word技巧探索过程参考资料&#xff08;有效与无效&#xff09;全自动方案/代码/教程 前言/word技巧 word公式 用ALT号可以输入简单latex显示公式&#xff1b;复杂度&#xff0c;需要引入latex包的不行&#xff1b;显示不出来的话按一下en…

3分钟带你了解前端缓存-HTTP缓存

前情提要 前端缓存分为下面三大类&#xff0c;本文主要讲解HTTP缓存~ 1. HTTP缓存 强缓存协商缓存 2. 浏览器缓存 本地小容量缓存本地大容量缓存 3. 应用程序缓存 HTML5应用程序缓存 缓存作用 减少了冗余的数据传输减少服务器的负担提高了网站的性能加快加载网页速度 …

初阶JavaEE(17)Linux 基本使用和 web 程序部署

接上次博客&#xff1a;初阶JavaEE&#xff08;16&#xff09;博客系统&#xff08;Markdown编辑器介绍、博客系统功能、博客系统编写&#xff1a;博客列表页 、博客详情页、实现登录、实现强制登录、显示用户信息、退出登录、发布博客&#xff09;-CSDN博客 目录 Linux 基本…

Unity中Shader雾效的实现方法一

文章目录 前言一、在片元着色器中使用如下公式计算最终的颜色 lerp(雾效颜色&#xff0c;物体颜色&#xff0c;雾效混合因子)1、获取雾效颜色2、物体的颜色一般通过纹理采样得到&#xff0c;此处用 1 代替测试3、获取 雾效混合因子&#xff08;由 雾的距离 和 雾的浓度决定&am…

生成式AI - Knowledge Graph Prompting:一种基于大模型的多文档问答方法

大型语言模型&#xff08;LLM&#xff09;已经彻底改变了自然语言处理&#xff08;NLP&#xff09;任务。它们改变了我们与文本数据交互和处理的方式。这些强大的AI模型&#xff0c;如OpenAI的GPT-4&#xff0c;改变了理解、生成人类类似文本的方式&#xff0c;导致各种行业出现…

mysql主从复制-使用心得

文章目录 前言环境配置主库从库 STATEMENTbinloggtidlog-errorDistSQL总结 前言 mysql 主从复制使用感受&#xff0c;遇到一些问题的整理&#xff0c;也总结了一些排查问题技巧。 环境 mysql5.7 配置 附&#xff1a;千万级数据快速插入配置可以参考&#xff1a;mysql千万数…

把字符串转换为整数函数atoi

今天我们来认识一个函数&#xff0c;叫atoi&#xff0c;我们开始研究它吧&#xff01; 1.认识atoi 1.函数功能&#xff1a;将字符串转换为整数 只能将整数字符串转换为整数&#xff0c;不能转换字符字符串 2.头文件&#xff1a;#include<stdlib.h> 3.使用格式&#xff1a…

从0到0.01入门React | 001.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

sqli-labs关卡12(基于post提交的双引号闭合的字符型注入)通关思路

文章目录 前言一、回顾第十一关知识点二、靶场第十二关通关思路1、判断注入点2、爆显位个数3、爆显位位置4、爆数据库名5、爆数据库表名6、爆数据库列名7、爆数据库数据 总结 前言 此文章只用于学习和反思巩固sql注入知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的…