栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

深度剖析Spring Boot自动装配机制实现原理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深度剖析Spring Boot自动装配机制实现原理

在前面的分析中,Spring framework一直在致力于解决一个问题,就是如何让bean的管理变得更简单,如何让开发者尽可能的少关注一些基础化的bean的配置,从而实现自动装配。所以,所谓的自动装配,实际上就是如何自动将bean装载到Ioc容器中来。

实际上在spring 3.x版本中,Enable模块驱动注解的出现,已经有了一定的自动装配的雏形,而真正能够实现这一机制,还是在spirng 4.x版本中,conditional条件注解的出现。ok,我们来看一下spring boot的自动装配是怎么一回事。

自动装配的演示
 
     org.springframework.boot
     spring-boot-starter-data-redis
 
spring:
    redis:
      host: 127.0.0.1 
      port: 6379
 @Autowired
    private RedisTemplateredisTemplate;

按照下面的顺序添加starter,然后添加配置,使用RedisTemplate就可以使用了? 那大家想没想过一个问题,为什么RedisTemplate可以被直接注入?它是什么时候加入到Ioc容器的呢? 这就是自动装配。自动装配可以使得classpath下依赖的包相关的bean,被自动装载到Spring Ioc容器中,怎么做到的呢?

深入分析EnableAutoConfiguration

EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。

再回到EnableAutoConfiguration这个注解中,我们发现它的import是这样

@import(AutoConfigurationimportSelector.class)
public @interface EnableAutoConfiguration {

但是从EnableAutoCOnfiguration上面的import注解来看,这里面并不是引入另外一个Configuration。而是一个importSelector。这个是什么东西呢?

AutoConfigurationimportSelector是什么?

Enable注解不仅仅可以像前面演示的案例一样很简单的实现多个Configuration的整合,还可以实现一些复杂的场景,比如可以根据上下文来激活不同类型的bean,@import注解可以配置三种不同的class

  1. 第一种就是前面演示过的,基于普通bean或者带有@Configuration的bean进行诸如
  2. 实现importSelector接口进行动态注入

实现importBeanDefinitionRegistrar接口进行动态注入

CacheService

public class CacheService {
}

LoggerService

public class LoggerService {
}

EnableDefineService
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented 
@Inherited  --允许被继承
@import({GpDefineimportSelector.class})
public @interface EnableDefineService {

    String[] packages() default "";
}
GpDefineimportSelector
public class GpDefineimportSelector implements importSelector {
    @Override
    public String[] selectimports(Annotationmetadata annotationmetadata) {
        //获得指定注解的详细信息。我们可以根据注解中配置的属性来返回不同的class,
        //从而可以达到动态开启不同功能的目的
    
annotationmetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true)
            .forEach((k,v) -> {
                log.info(annotationmetadata.getClassName());
                log.info("k:{},v:{}",k,String.valueOf(v));
            });
        return new String[]{CacheService.class.getName()};
    }
}
EnableDemoTest
@SpringBootApplication
@EnableDefineService(name = "gupao",value = "gupao")
public class EnableDemoTest {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca=SpringApplication.run(EnableDemoTest.class,args);
        System.out.println(ca.getBean(CacheService.class));
        System.out.println(ca.getBean(LoggerService.class));
    }
}

了解了selector的基本原理之后,后续再去分析AutoConfigurationimportSelector的原理就很简单了,它本质上也是对于bean的动态加载。

@EnableAutoConfiguration注解的实现原理

了解了importSelector和importBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了

它会通过import导入第三方提供的bean的配置类:AutoConfigurationimportSelector

@import(AutoConfigurationimportSelector.class)

从名字来看,可以猜到它是基于importSelector来实现基于动态bean的加载功能。之前我们讲过Springboot @Enable*注解的工作原理importSelector接口selectimports返回的数组(类的全类名)都会被纳入到spring容器中。

那么可以猜想到这里的实现原理也一定是一样的,定位到AutoConfigurationimportSelector这个类中的selectimports方法

selectimports
public String[] selectimports(Annotationmetadata annotationmetadata) {
   if (!isEnabled(annotationmetadata)) {
      return NO_importS;
   }
// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationmetadata
   AutoConfigurationmetadata autoConfigurationmetadata = AutoConfigurationmetadataLoader
         .loadmetadata(this.beanClassLoader);
// 获取所有候选配置类EnableAutoConfiguration
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
         autoConfigurationmetadata, annotationmetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(
      AutoConfigurationmetadata autoConfigurationmetadata,
      Annotationmetadata annotationmetadata) {
   if (!isEnabled(annotationmetadata)) {
      return EMPTY_ENTRY;
   }
//获取元注解中的属性
   AnnotationAttributes attributes = getAttributes(annotationmetadata);
//使用SpringFactoriesLoader 加载classpath路径下meta-INFspring.factories中,
//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value
   List configurations = getCandidateConfigurations(annotationmetadata,
         attributes);
//去重
   configurations = removeDuplicates(configurations);
//应用exclusion属性
   Set exclusions = getExclusions(annotationmetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
   configurations = filter(configurations, autoConfigurationmetadata);
   //广播事件
fireAutoConfigurationimportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

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

SpringFactoriesLoader

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

首先,SpringFactoriesLoader的作用是从classpath/meta-INF/spring.factories文件中,根据key来加载对应的类到spring IoC容器中。接下来带大家实践一下

创建外部项目jar

  org.springframework
  spring-context
  4.3.13.RELEASE


创建bean以及config
public class GuPaoCore {
    public String study(){
        System.out.println("good good study, day day up");
        return "GuPaoEdu.com";
    }
}
@Configuration
public class GuPaoConfig {
    @Bean
    public GuPaoCore guPaoCore(){
        return new GuPaoCore();
    }
}

创建另外一个工程(spring-boot)

把前面的工程打包成jar,当前项目依赖该jar包


    com.gupaoedu.practice
    Gupao-Core
    1.0-SNAPSHOT


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

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

@SpringBootApplication
public class SpringBootStudyApplication {
    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext ac=SpringApplication.run(SpringBootStudyApplication.class, args);
        GuPaoCore gpc=ac.getBean(GuPaoCore.class);
        System.out.println(gpc.study());
    }
}
解决方案

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

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupaoedu.practice.GuPaoConfig

重新打包,重新运行SpringBootStudyApplication这个类。

可以发现,我们编写的那个类,就被加载进来了。

@EnableAutoConfiguration注解的实现原理

了解了importSelector和importBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了

它会通过import导入第三方提供的bean的配置类:AutoConfigurationimportSelector

@import(AutoConfigurationimportSelector.class)

从名字来看,可以猜到它是基于importSelector来实现基于动态bean的加载功能。之前我们讲过Springboot @Enable*注解的工作原理importSelector接口selectimports返回的数组(类的全类名)都会被纳入到spring容器中。

那么可以猜想到这里的实现原理也一定是一样的,定位到AutoConfigurationimportSelector这个类中的selectimports方法

selectimports
public String[] selectimports(Annotationmetadata annotationmetadata) {
   if (!isEnabled(annotationmetadata)) {
      return NO_importS;
   }
// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationmetadata 
   AutoConfigurationmetadata autoConfigurationmetadata = AutoConfigurationmetadataLoader
         .loadmetadata(this.beanClassLoader);
// 获取所有候选配置类EnableAutoConfiguration
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
         autoConfigurationmetadata, annotationmetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(
      AutoConfigurationmetadata autoConfigurationmetadata,
      Annotationmetadata annotationmetadata) {
   if (!isEnabled(annotationmetadata)) {
      return EMPTY_ENTRY;
   }
//获取元注解中的属性
   AnnotationAttributes attributes = getAttributes(annotationmetadata);
//使用SpringFactoriesLoader 加载classpath路径下meta-INFspring.factories中,
//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value
   List configurations = getCandidateConfigurations(annotationmetadata,
         attributes);
//去重
   configurations = removeDuplicates(configurations);
//应用exclusion属性
   Set exclusions = getExclusions(annotationmetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
   configurations = filter(configurations, autoConfigurationmetadata);
   //广播事件
fireAutoConfigurationimportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

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

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mic带你学架构!
如果本篇文章对您有帮助,还请帮忙点个关注和赞,您的坚持是我不断创作的动力。欢迎关注「跟着Mic学架构」公众号公众号获取更多技术干货!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/602615.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号