【原理分析】细说SpringBoot的自动装配原理「建议收藏」

【原理分析】细说SpringBoot的自动装配原理「建议收藏」1.什么是SpringBoot?  对于spring框架,我们接触得比较多的应该是springmvc、和spring。而spring的核心在于IOC(控制反转对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系)和DI(依赖注入IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(DependencyInjection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

在这里插入图片描述

1.什么是SpringBoot?

  对于spring框架,我们接触得比较多的应该是spring mvc、和spring。而spring的核心在于IOC(控制反转对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系)和DI(依赖注入IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道)。而这些框架在使用的过程中会需要配置大量的xml,或者需要做很多繁琐的配置。

  springboot框架是为了能够帮助使用spring框架的开发者快速高效的构建一个基于spirng框架以及spring生态体系的应用解决方案。它是对“约定优于配置”这个理念下的一个最佳实践。因此它是一个服务于框架的框架,服务的范围是简化配置文件。

2.初步认识Spring Boot

  我们可以使用 https://start.spring.io

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

  为了让大家看到效果,我们使用spring mvc来构建一个web应用,而springboot帮我们简化了非常多的逻辑使得我们非常容易去构建一个web项目。

  springboot提供了spring-boot-starter-web自动装配模块

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

  在当前项目下运行mvn spring-boot:run 或者直接运行main方法就可以启动一个使用了嵌入式tomcat服务请求的web应用,但是我们没有提供任何服务web请求的controller,所以访问任何路径都会返回一个springboot默认的错误页面(whitelabel error page)

  所以,我们可以创建一个Controller来实现请求

@RestController
public class HelloController { 
   

    @GetMapping("/say")
    public String sayHello(){ 
   
        return "hello Mic";
    }
}

  访问http://localhost:8080/say 就可以获得一个请求结果。 这样就完成了一个非常简单的web应用。 springboot是一个约定优于配置的产物,所以在快速构建web应用的背后,其实有很多的约定。

  1. 项目结构层面,静态文件和页面模版的存放位置变成了src/main/resources对应的子目录下
  2. 自动嵌入tomcat作为web容器对外提供http服务,默认使用8080端口监听
  3. 自动装配springmvc必要的组件

3.Spring Boot四大核心

EnableAutoConfiguration 自动装配
Starter组件, 开箱即用
Actuator 监控
Spring Boot Cli 为Spring Cloud 提供了Spring Boot 命令行功能

  那今天主要给大家讲讲Enable*这个注解

4.Enable* 注解的作用

  Enable是启用的意思,相当于开启某一个功能

  • EnableScheduling
  • EnableHystrix
  • EnableAsync
  • EnableAutoConfiguration
  • EnableWebMvc

  仍然是在spring3.1版本中,提供了一系列的@Enable开头的注解,Enable主机应该是在JavaConfig框架上更进一步的完善,是的用户在使用spring相关的框架是,避免配置大量的代码从而降低使用的难度

  比如常见的一些Enable注解:EnableWebMvc,(这个注解引入了MVC框架在Spring 应用中需要用到的所有bean);

  比如说@EnableScheduling,开启计划任务的支持;

  找到EnableAutoConfiguration,我们可以看到每一个涉及到Enable开头的注解,都会带有一个@Import的注解。

5.深入分析Spring Boot中的自动装配

  在Spring Boot中,不得不说的一个点是自动装配,它是starter的基础,也是Spring Boot的核心, 那什么叫自动装配?或者什么叫装配呢?

  回顾一下Spring Framework,它最核心的功能是IOC和AOP, IoC容器的主要功能是可以管理对象的生命周期。也就是bean的管理。我们把Bean对象托管到Spring Ioc容器的这个过程称为装配,那什么是自动装配呢?我们慢慢来介绍

  首先大家看下这张图,我们先不解释。等把今天的内容讲完,我们再回头来通过这张图来总结~

在这里插入图片描述

  自动装配在SpringBoot是基于EnableAutoConfiguration来实现的。那么在深入分析EnableAutoConfiguration之前,我们来看一下传统意义上的装配方式。

5.1简单分析@Configuration

  Configuration这个注解大家应该有用过,它是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。因为SpringBoot本质上就是一个spring应用,所以通过这个注解来加载IOC容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个IoC容器的配置类。

public class DemoClass { 
   

    public void say(){ 
   
        System.out.println("sya hello ... ");
    }
}
@Configuration
@Import(UserClass.class)
public class DemoConfiguration { 
   

    @Bean
    public DemoClass getDemoClass(){ 
   
        return new DemoClass();
    }
}
public class DemoConfigurationMain { 
   

    public static void main(String[] args) { 
   
        ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfiguration.class);
        DemoClass bean = ac.getBean(DemoClass.class);
        bean.say();
    }
}

  这种形式就是通过注解的方式来实现IoC容器,传统意义上的spring应用都是基于xml形式来配置bean的依赖关系。然后通过spring容器在启动的时候,把bean进行初始化并且,如果bean之间存在依赖关系,则分析这些已经在IoC容器中的bean根据依赖关系进行组装。

  直到Java5中,引入了Annotations这个特性,Spring框架也紧随大流并且推出了基于Java代码和Annotation元信息的依赖关系绑定描述的方式。也就是JavaConfig。

  从spring3开始,spring就支持了两种bean的配置方式,一种是基于xml文件方式、另一种就是JavaConfig

Configuration的本质

  如果大家细心一点就会发现Configuration注解的本质是一个Component注解,这个注解会被AnnotationConfigApplicationContext加载,而AnnotationConfigApplicationContext是ApplicationContext的一个具体实现,表示根据配置注解启动应用上下文。
  因此我们在Main方法中通过AnnotationConfigApplicationContext去加载JavaConfig后,可以得到Ioc容器中的bean的实例
  JavaConfig的方式在前面的代码中已经演示过了,任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。而在这个配置类中,任何标注了@Bean的方法,它的返回值都会作为Bean定义注册到Spring的IOC容器,方法名默认成为这个bean的id

5.2简单分析@ComponentScan

  ComponentScan这个注解是大家接触得最多的了,相当于xml配置文件中的<context:component-scan>。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到spring的Ioc容器中。

  标识需要装配的类的形式主要是:@Component、@Repository、@Service、@Controller这类的注解标识的类。

public static void main(String[] args) { 
   
    ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfiguration.class);
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    System.out.println("********************************");
    for (String beanName:beanDefinitionNames
         ) { 
   
        System.out.println(beanName);
    }
}

5.3 Import注解

  import 注解是什么意思呢? 联想到xml形式下有一个<import resource/> 形式的注解,就明白它的作用了。import就是把多个分来的容器配置合并在一个配置中。在JavaConfig中所表达的意义是一样的。为了能更好的理解后面讲的EnableAutoConfiguration,我们详细的和大家来介绍下import注解的使用

方式一:直接填class数组

  我们先在不同的两个package下创建对应的bean。
  比如:
在这里插入图片描述
  那么DemoConfigurationMain中执行的代码应该是加载不到demo2中的UserClass的
在这里插入图片描述
  这时我们可以通过@import来直接加载
在这里插入图片描述

或者
在这里插入图片描述

方式二:ImportSelector方式【重点】

  第一种方式如果导入的是一个配置类,那么该配置类中的所有的都会加载,如果想要更加的灵活,动态的去加载的话可以通过Import接口的第二种使用方式,也就是ImportSelector这种方式,我们来看看怎么使用。
LogService

public class LogService { 
   
}

CacheService

public class CacheService { 
   
}

GpDefineImportSelector

public class GpDefineImportSelector implements ImportSelector { 
   
    /** * AnnotationMetadata 注解元数据 * @param annotationMetadata * @return * 要被IOC容器加载的bean信息 */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) { 
   
        // 我们可以基于注解元数据信息来动态的返回要加载的bean信息
        annotationMetadata
                .getAllAnnotationAttributes(EnableDefineService.class.getName(),true)
        .forEach((k,v)->{ 
   
            System.out.println(annotationMetadata.getClassName());
            System.out.println("---> " + k+":" + String.valueOf(v));
        });

        return new String[]{ 
   CacheService.class.getName()};
    }
}

EnableDefineService

@Target({ 
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(GpDefineImportSelector.class)
public @interface EnableDefineService { 
   

    String[] packages() default "";

}

EnableDemoTest

@EnableDefineService()
@SpringBootApplication
public class EnableDemoTest { 
   

    public static void main(String[] args) { 
   
        ApplicationContext ac = new AnnotationConfigApplicationContext(EnableDemoTest.class);//SpringApplication.run(EnableDemoTest.class,args);
        System.out.println(ac.getBean(CacheService.class));
        System.out.println(ac.getBean(LogService.class));
    }
}

方式三:ImportBeanDefinitionRegistrar方式

  这种方式和第二种方式很相似,同样的要实现 ImportBeanDefinitionRegistrar 接口

public class GpImportDefinitionRegister implements ImportBeanDefinitionRegistrar { 
   
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { 
   
        // 指定bean的定义信息
        RootBeanDefinition beanDefinition = new RootBeanDefinition(CacheService.class);
        RootBeanDefinition beanDefinition1 = new RootBeanDefinition(LogService.class);
        // 注册一个bean
        beanDefinitionRegistry.registerBeanDefinition("cacheService1111",beanDefinition);
        beanDefinitionRegistry.registerBeanDefinition("cacheService2222",beanDefinition1);

    }
}

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

5.4 深入分析EnableAutoConfiguration原理

  了解了ImportSelectorImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了
  它会通过import导入第三方提供的bean的配置类:AutoConfigurationImportSelector
在这里插入图片描述
AutoConfigurationImportSelector

public String[] selectImports(AnnotationMetadata annotationMetadata) { 
   
    if (!this.isEnabled(annotationMetadata)) { 
   
        return NO_IMPORTS;
    } else { 
   
        try { 
   
// 加载META-INF/spring-autoconfigure-metadata.properties 下的元数据信息

            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选加载的配置信息 META-INF/spring.factories
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去掉重复的配置信息
            configurations = this.removeDuplicates(configurations);
// 排序
            configurations = this.sort(configurations, autoConfigurationMetadata);
            // 获取 注解中配置的 exclusion 信息
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查
            this.checkExcludedClasses(configurations, exclusions);
// 移除需要排除的信息
            configurations.removeAll(exclusions);
// 过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
            configurations = this.filter(configurations, autoConfigurationMetadata);
// 广播事件
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            // 返回要被加载的类数组
return (String[])configurations.toArray(new String[configurations.size()]);
        } catch (IOException var6) { 
   
            throw new IllegalStateException(var6);
        }
    }
}

  本质上来说,其实EnableAutoConfiguration会帮助springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解@Conditional,选择性的针对需要加载的bean进行条件过滤

5.5 SpringFactoriesLoader

  为了给大家补一下基础,我在这里简单分析一下SpringFactoriesLoader这个工具类的使用。它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据key进行加载。

  首先,SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,根据key来加载对应的类到spring IoC容器中。接下来带大家实践一下
  通过mybatis整合来看
  引入mybatis的依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>

在这里插入图片描述

  查看 MybatisAutoConfiguration 里面的源码,发现在其中加载了SqlSessionFactory等信息。

通过实际案例来实现

  创建一个新的maven项目,引入相关的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.6.RELEASE</version>
    </dependency>
</dependencies>

创建一个bean类

public class GpCoreService { 
   

    public void crudService(){ 
   
        System.out.println("gupao core service run ...");
    }
}

以及对应的配置类

@Configuration
public class GpConfiguration { 
   

    @Bean
    public GpCoreService gpCoreService(){ 
   
        return new GpCoreService();
    }

}

然后打包
在这里插入图片描述
在另一个项目中导入该jar包

<dependency>
    <groupId>org.gupao.edu</groupId>
    <artifactId>Gp-Core</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

  通过下面代码获取依赖包中的属性

  运行结果会报错,原因是GuPaoCore并没有被Spring的IoC容器所加载,也就是没有被EnableAutoConfiguration导入

public static void main(String[] args) { 
   
    ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
    String[] beanDefinitionNames = run.getBeanDefinitionNames();
    System.out.println(run.getBean(GpCoreService.class));
}

解决方案

  在GuPao-Core项目resources下新建文件夹META-INF,在文件夹下面新建spring.factories文件,文件中配置,key为自定配置类EnableAutoConfiguration的全路径,value是配置类的全路径

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupao.edu.GpConfiguration

  重新打包,重新运行SpringBootStudyApplication这个类。
  可以发现,我们编写的那个类,就被加载进来了。

5.6深入理解条件过滤

  在分析AutoConfigurationImportSelector的源码时,会先扫描spring-autoconfiguration-metadata.properties文件,最后在扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低SpringBoot的启动时间。
  修改Gupao-Core

  在META-INF/增加配置文件,spring-autoconfigure-metadata.properties。

com.gupao.edu.GpConfiguration.ConditionalOnClass=com.gupao.edu.service.GpTestService

在这里插入图片描述

格式:自动配置的类全名.条件=值

上面这段代码的意思就是,如果当前的classpath下存在TestClass,则会对GuPaoConfig这个Configuration进行加载
演示过程(spring-boot)

1.沿用前面spring-boot工程的测试案例,直接运行main方法,发现原本能够被加载的GuPaoCore,发现在ioc容器中找不到了。
2.在当前工程中指定的包com.gupaoedu下创建一个TestClass以后,再运行上面这段代码,程序能够正常执行

好了~到此如果跟着一块动手的话相信你对于SpringBoot的自动装配应该有了不一样的理解,有问题的欢迎留言交流!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最新视频已录制完成,扫码免费赠送!备注:CSDN

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/171526.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • linux 下shell中if的“-e,-d,-f”是什么意思「建议收藏」

    linux 下shell中if的“-e,-d,-f”是什么意思「建议收藏」-efilename如果filename存在,则为真-dfilename如果filename为目录,则为真-ffilename如果filename为常规文件,则为真-Lfilename如果filename为符号链接,则为真-rfilename如果filename可读,则为真-wfilename如果filename可写,则为真-xfilenam

  • hexdump -C_linux生成dump

    hexdump -C_linux生成dump描述:hexdump命令一般用来查看”二进制”文件的十六进制编码,从手册上查看,其查看的内容还要很多,诸如:ascii,decimal,hexadecimal,octal参数:hexdump[-bcCdovx][-eformat_string][-fformat_file][-nlength][-sskip]file示例:新增一个文本文件,在test文本中添加如下内容:…

  • Python 读取txt文件

    Python 读取txt文件1.首先将数据加载到Python中,看需要做哪些处理。2、从显示的内容可以看出,两个数字之间是以空格,作为分隔符,这里读成一行了。使用sep=””处理,打印查看效果。3、使用分隔符后,分成了三列。但是还有一个问题,第一行被当成了表头,解决方法:使用names=[]给每列命名~ok啦,现在可以实现读取txt文件的任务了~…

  • 圆周率前10000位是多少_圆周率的前二十位是多少

    圆周率前10000位是多少_圆周率的前二十位是多少小编整理的π(圆周率)的小数点后10000位,欢迎来看。

  • ov7725摄像头人脸识别_监控摄像头图像倒置怎么办

    ov7725摄像头人脸识别_监控摄像头图像倒置怎么办前言:摄像头的工作原理大致为:景物通过镜头(LENS)生成的光学图像投射到图像传感器表面上,然后转为电信号,经过A/D[1](模数转换)转换后变为数字图像信号,再送到数字信号处理芯片(DSP)中加工处理,再传输给其他显示硬件就可以显示看到图像了我要讲解的是0V7725摄像头,带FIFO缓存,以及通过STM32F103MCU进行控制,在采用ILI9341控制器芯片的液晶屏(分辨率240*32

  • 小树311_森林小道

    小树311_森林小道原题链接森森开了一家快递公司,叫森森快递。因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从0到(N−1)编号。由于道路限制,第i号城市(i=0,⋯,N−2)与第(i+1)号城市中间往返的运输货物重量在同一时刻不能超过C​i​​ 公斤。公司开张后很快接到了Q张订单,其中j张订单描述了某些指定的货物要从S​j​​ 号城市运输到T​j​​ 号城市。这里我们简单地假设所有货物都有无限货源,森森会不定时地挑选其中一部分货物进行运输。安全起见,这些货物不会在中

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号