SpringBoot自动装配原理

SpringBoot的主配置

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

1、主入口

@SpringBootApplication注解标注的类为SpringBoot的主配置类,SpringBoot就会运行这个类的main方法来启动SpringBoot项目

main方法运行了一个run()方法,run方法传入自己

@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方法源码

/**
     * 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源码

/**
 * 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源码

@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源码

@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源码

@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源码

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

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

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

首先是Registrar类源码

/**
 * {@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源码

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设置组件的作用域

// 1、原型案例
@Scope("prototype")
@Bean()
public User user() {
    return new User();
}
// 2、单例 默认就是单例
@Scope("singleton")

@Lazy bean组件懒加载

@Lazy注解用于标识bean是否需要延迟加载,默认是true。

没加注解主要容器启动就会实例化bean

加上@Lazy注解则第一次调用的时候才会加载

@Conditional按照条件注册Bean

Conditional源码

//作用于方法和类
@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);
}

使用案例

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表达式执行结果作为条件