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

自动装配原理

依赖属性配置

提供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 目录下存放了工程启动时需要加载的所有类(含自动配置类)

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

// 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

你可能感兴趣的:(SpringBoot,spring,boot,java,spring,自动装配,变更自动装配)