前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot自动装配原理

SpringBoot自动装配原理

作者头像
西柚dzh
发布2022-06-09 17:33:56
3160
发布2022-06-09 17:33:56
举报
文章被收录于专栏:dcmickey小站dcmickey小站

SpringBoot自动装配原理

SpringBoot的主配置

这里先看自动装配,extends SpringBootServletInitializer这块下一步再探讨

1、主入口

@SpringBootApplication注解标注的类为SpringBoot的主配置类,SpringBoot就会运行这个类的main方法来启动SpringBoot项目 main方法运行了一个run()方法,run方法传入自己

代码语言:javascript
复制
@SpringBootApplication
public class StarrySpringBootApplication extends SpringBootServletInitializer {

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

    @Override//为了打包springboot项目
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }
}

spring的run方法源码

代码语言:javascript
复制
/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified source using default settings.
     * @param primarySource the primary source to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

2、@SpringBootApplication启动注解

是一个组合注解,核心是三个Annotation组成

  • @SpringBootConfiguration(本质是一个@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan

SpringBootApplication源码

代码语言:javascript
复制
/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;

}

3、先看看@SpringBootConfiguration注解

这个注解实际上就是代表了一个配置类,表名该类是一个Spring的配置类,相当于一个beans.xml文件 具体可查看网上的文献:基于注解开发初探 https://www.jianshu.com/p/81880251a700

SpringBootConfiguration源码

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;

}

4、@ComponentScan

@ComponentScan的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中

用法 // 1、只包含有Controller注解bean @ComponentScan(value="com.qiu",includeFilters = { @Filter(type=FilterType.ANNOTATION,classes={Controller.class}),} ,useDefaultFilters = false) // 2、排除含有controller注解的bean @ComponentScan(value="com.qiu",excludeFilters = { @Filter(type=FilterType.ANNOTATION,classes={Controller.class}) })

ComponentScan源码

代码语言:javascript
复制
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//可以重复使用
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
        //使用包名
        @AliasFor("basePackages")
        String[] value() default {};
        @AliasFor("value")
        String[] basePackages() default {};
        //使用具体类型名称
        Class<?>[] basePackageClasses() default {};
        ....其他属性
        //指定扫描的时候只需要包含哪些组件
        Filter[] includeFilters() default {};
        //指定扫描的时候按照什么规则排除那些组件
        Filter[] excludeFilters() default {};
        boolean lazyInit() default false;
        //过滤规则类
        @Retention(RetentionPolicy.RUNTIME)
        @Target({})
        @interface Filter {
        //FilterType定义按什么过滤类型来进行过滤,
        /**
        FilterType.ANNOTATION:按照注解
        FilterType.ASSIGNABLE_TYPE:按照给定的类型;
        FilterType.ASPECTJ:使用ASPECTJ表达式
        FilterType.REGEX:使用正则指定
        FilterType.CUSTOM:使用自定义规则
        */
        FilterType type() default FilterType.ANNOTATION;
        @AliasFor("classes")
        Class<?>[] value() default {};
        @AliasFor("value")
        Class<?>[] classes() default {};
        String[] pattern() default {};
    }
}

5、@EnableAutoConfiguration

在spring中有关于@Enablexxx的注解是开启某一项功能的注解。其原理是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到Ioc容器

@EnableAsync 开启异步 @EnableScheduling 表示开启spring的定时任务

@EnableAutoConfiguration==代表开启springboot的自动装配==

EnableAutoConfiguration源码

代码语言:javascript
复制
@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 {};
}

@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class)

先来看看@AutoConfigurationPackage

这个注解是自动配置包,主要是使用的@Import来给Spring容器中导入一个组件 ,这里导入的是Registrar.class

AutoConfigurationPackage源码

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
   String[] basePackages() default {};
   Class<?>[] basePackageClasses() default {};
}

再来看Registrar类,这是一个内部静态类

==就是通过这个方法获取扫描的包路径==,这里就需要我们做一个小小的debug来验证下,请接着往下看

首先是Registrar类源码

代码语言:javascript
复制
/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * 存储注册导入过来的基本包
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
   }

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

}

在第10行打个断点,开始debug

如果不能调试 File->Settings-> Build, Execution, Deployment ->Debugger->Stepping 把Do not step into the classes下的java.*,javac.*取消勾选,然后保存即可

从下图可以看出metadata就是我们的springboot的主启动类,getPackageNames获取到这个类的包名,

==说白了就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。所以包名一定要注意==

接着我们看@Import(AutoConfigurationImportSelector.class)

@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:AutoConfigurationImportSelector

AutoConfigurationImportSelector源码

代码语言:javascript
复制
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
            
    //  这个地方才可以断点,上面的selectImports设置断点是没用的
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        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);
    }       
...
}

再次debug看看:

断点需要打在==getAutoConfigurationEntry方法上==,selectImports方法断点不会生效

会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件:

有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。

那他是如何获取到这些配置类的呢,看看上面这个方法:

会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:

看看

getSpringFactoriesLoaderFactoryClass()是啥

getBeanClassLoader()是啥

Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了

引申注解认识

@Scope设置组件的作用域

代码语言:javascript
复制
// 1、原型案例
@Scope("prototype")
@Bean()
public User user() {
    return new User();
}
// 2、单例 默认就是单例
@Scope("singleton")

@Lazy bean组件懒加载

@Lazy注解用于标识bean是否需要延迟加载,默认是true。 没加注解主要容器启动就会实例化bean 加上@Lazy注解则第一次调用的时候才会加载

@Conditional按照条件注册Bean

Conditional源码

代码语言:javascript
复制
//作用于方法和类
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
//参数是:继承于Condition的类数组
    Class<? extends Condition>[] value();
}
//condition接口,自定义的condition类需要实现该接口
public interface Condition {
/**
     * ConditionContext:判断条件能使用的上下文(环境)
     * AnnotatedTypeMetadata:注释信息
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

使用案例

代码语言:javascript
复制
1、判断是否是linux系统
public class LinuxCondition implements Condition{
/**
 * ConditionContext:判断条件能使用的上下文(环境)
 * AnnotatedTypeMetadata:注释信息
 */
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) 
{
    //1.能获取到ioc使用的Beanfactory
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //2、获取到类加载器
    ClassLoader classLoader = context.getClassLoader();
    //3.获取到当前环境信息
    Environment environment = context.getEnvironment();
    //4.获取到bean定义的注册类信息
    BeanDefinitionRegistry registry = context.getRegistry();
    //=============这里主要是为了获取当前系统的环境变脸
    String property=environment.getProperty("os.name");
    if (property.contains("linux")) {
        return true;//放行
    }
    return false;
}

}

2、判断是否是windows系统
public class WindowsCondition implements Condition{
/**
 * ConditionContext:判断条件能使用的上下文(环境)
 * AnnotatedTypeMetadata:注释信息
 */
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
{
    Environment environment = context.getEnvironment();
    String property=environment.getProperty("os.name");
    if (property.contains("Windows")) {
        return true;//放行
    }
    return false;
}
}
3、@Conditional:按照一定的逻辑进行判断,满足条件给容器注入bean
public class ConditionalConfig {
//注入windows
@Conditional(value= {WindowsCondition.class})
@Bean
public User user1() {
    User user=new User();
    user.setUserName("bill");
    return user;
}
//注入linux
@Conditional(value= {LinuxCondition.class})
@Bean
public User user2() {
    User user=new User();
    user.setUserName("linus");
    return user;
}
4、idea中更换操作系统方法:-Dos.name=linux

介绍完了条件注解在spring中的使用,在Springboot中条件注解的分类:

Class conditions:@ConditionalOnClass和@ConditionalOnMissingClass,表示类是否在类路径下的条件注解 Bean conditions:@ConditionalOnBean和@ConditionalOnMissingBean,表示Bean是否被定义的条件注解 Property conditions:@ConditionalOnProperty,使用prefix和name属性用来表示是否有值,默认的话,只要该属性存在值,且不为false,即可匹配 Resource conditions:@ConditionalOnResource表示是否存在指定的resouce的条件注解 Web application conditions:@ConditionalOnWebApplication和@ConditionalOnNotWebApplication,当项目是web项目,或者不是web项目的条件注解 SpEL expression conditions:@ConditionalOnExpression,根据SPEL表达式执行结果作为条件


版权属于:dingzhenhua

本文链接:https://cloud.tencent.com/developer/article/2019361

转载时须注明出处及本声明

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot自动装配原理
    • 1、主入口
      • 2、@SpringBootApplication启动注解
        • 3、先看看@SpringBootConfiguration注解
          • 4、@ComponentScan
            • 5、@EnableAutoConfiguration
              • @Scope设置组件的作用域
              • @Lazy bean组件懒加载
              • @Conditional按照条件注册Bean
          • 引申注解认识
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档