SpringBoot笔记

spring官网:Spring | Home

maven管理:

@SpringBootApplication
public class DemoApplication {

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


}

从以上来看,我们已经把一个springboot项目构建完毕了,那么我们的依赖里面并没有指定具体的version,那这个是怎么引入进来的呢,x现在我们就来看看parent里面做了那些操作

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.5.4</version>
  </parent>

可以看出parent还有一个spring-boot-dependencies的封装

可以看出以上已经对依赖管理做好的版本控制,具体版本太多我就不一一列出来了 

所以说SpringBoot可以理解为封装再封装,将封装做到极致来简化开发

 

 我们已经完成了一个SpringBoot项目,可以看出确实比之前SpringFramework简洁了,当然这不是我随意写的都是以官网方式为准的:Spring | Spring Quickstart Guide

SpringBoot自动配置

server:
  port: 8888

以上我们只对启动端口进行了修改,那么springBoot是如何自动加载的呢

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties

跟进源码可以看出,server前缀的配置文件都是由这个类来维护的,这么说那我们是不是也可以自定义配置

@ConfigurationProperties(prefix = "studentyaml")
@ToString
@Data
public class StudentYaml {
    private String name;
    private Boolean sex;
    private Date birth;
    private Integer age;
    private Address address;
    private String[] interests;
    private List<String> fridends;
    private Map<String, Double> score;
    private Set<Double> weightHis;
    private Map<String, List<Address>> allFridendsAddress;
}
studentyaml:
  name: muse
  sex: true
  birth: 2021/9/5
  age: 1
  street: 王府井⼤街
  interests: [看书, 写字, 玩电⼦游戏]
  fridends:
    - 李雷
    - 韩梅梅
  score: {math: 100,english: 80}
  weight-his:
    - 176
    - 199
    - 160
    - 179

       以上我们也基于ServerProperties自定义了一个StudentYaml,那么我们来看看,springBoot启动的时候是否会自动加载我们自定义的配置呢

ConfigurableApplicationContext可以理解整个SpringBoot的上下文对象(全局变量类型Mybatis中的Configuration)

可以看出我们自定义的配置并没有自动给配置进去,那么我们可以看看ServerProperties还有那么操作我们是不是遗漏了

@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration

@EnableConfigurationProperties({ServerProperties.class})这行才是我们实际关注的,那我们也来加上试试

 可以看出我们已经实现了SpringBoot的自定义配置

总结以上可以看出,

@EnableConfigurationProperties({StudentYaml.class})这个注解才是SpringBoot强大的自动配置功能,下面我们再来跟进源码看看基本的实现方式

自动配置原理

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication

 由@SpringbootApplication可以看出核心注解@EnableAutoConfiguration(开启自动配置)

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration

@EnableAutoConfiguration核心@AutoConfigurationPackage(自动配置包)@Import(导入注解)

我们先来看@AutoConfigurationPackage

@Import({Registrar.class})
public @interface AutoConfigurationPackage

核心主要也是将Registar引入

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

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

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

以上代码就是Registar核心,显然已经看不懂了,那么我们可以跟进dubug看下

可以看出这步主要是为了设置DemoApplication的根目录以及子级目录,所以说我们要把DemoApplication放在项目的最高级中。

@Import({AutoConfigurationImportSelector.class})这又有哪些操作呢(导入所有符合自动注入的bean注入到ioc)

以上可以看出 

AutoConfigurationImportSelector只是实现了DeferImportSelector,并不是直接实现了ImportSelecor,所以spring会先调用getImportGroup,拿到Group类之后,在调用process,再调用selectImports获取需要引入配置集合,然后交给spring解析处理,所以AutoConfigurationImportSelector导入的时候,不会直接调用
public String[] selectImports(AnnotationMetadata annotationMetadata)

因此我们来看看process和selectImports做了那些操作

process:

 由debug可以看出,这里主要是为了拿出配置类和排除类,然后维护到本地

autoConfigurationEntries中,为下一步selectImports准备,那么我们来看看具体是怎么获取的配置类
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            // 获取标注@EnableAutoConfiguration的元数据。
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 获取所有的配置类
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
               //去重
            configurations = this.removeDuplicates(configurations);
             // 获取所有的排除配置
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            // 校验需要配置和需要排除的是否存在冲突,存在直接抛出异常
            this.checkExcludedClasses(configurations, exclusions);
            // 过滤掉需要排除的配置
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            //监听器记录配置类和排除类
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            // 数据组装
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

以上只是为了拿到配置类和排除类做处理,以下才是获取配置类核心代码:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

 第一次url:file:/E:/resporitory/org/springframework/boot/spring-boot/2.5.4/spring-boot-2.5.4.jar!/META-INF/spring.factories

第二次url:file:/E:/resporitory/org/springframework/spring-beans/5.3.9/spring-beans-5.3.9.jar!/META-INF/spring.factories

第三次url:file:/E:/resporitory/org/springframework/boot/spring-boot-autoconfigure/2.5.4/spring-boot-autoconfigure-2.5.4.jar!/META-INF/spring.factories

从debug可以看出,目前url只会有三次

将以上文件中的数据去重之后以hashmap的方式存储到本地缓存 

selectImports:

以上说到process可以获取所有的配置类和排除类,那再来看看selectImports又做了那些操作

public Iterable<Entry> selectImports() {
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            } else {
        // 取出排除配置
                Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
        // 取出需要的配置
                Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
        // 去除需要排除的配置并封装数据返回
                processedConfigurations.removeAll(allExclusions);
                return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
                    return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
                }).collect(Collectors.toList());
            }
        }

看完以上代码之后,个人感觉这步操作毫无任何意义,只是再做了一次取出配置操作

总结以上代码

@Import({AutoConfigurationImportSelector.class})这段主要是引入了AutoConfigurationGroup中的autoConfigurationEntries,因此我们在配置文件中可以自动加载到这些已经自动配置好的类

以上就是个人所理解的自动配置的原理 

SpringBoot自动配置特征

1.按需加载原则

看完以上代码,我们就能感觉到每次我们启动SpringBoot项目的时候,是不是每次都要加载那么自动配置的类,那么问题来了,如果我们有些自动配置的类都不需要,为啥还需要加载那么多无关紧要的呢,我们再来看看SpringBoot是怎么解决这个问题呢

@Configuration(
    proxyBeanMethods = false
)
//  只有在引入RabbitTemplate.class和Channel.class
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration
以上代码就是主要我们以上按需加载的问题
@ConditionalOnClass({RabbitTemplate.class, Channel.class})

2.容错兼容

@Bean
@ConditionalOnBean(MultipartResolver.class) // 作⽤于该⽅法上,要求我们的IOC中存在
MultipartResolver类型的bean
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //
//要求IOC容器中不存在名称为multipartResolver的bean
public MultipartResolver multipartResolver(MultipartResolver resolver) {
 // 调⽤者IOC,resolver就是从IOC中获取到的类型为MultipartResolver的bean(是
MultipartResolver类型的但是名称不是“multipartResolver”)
 return resolver; // 保存到IOC中的bean的名字就是“multipartResolver”
}

这样可以保证注入到IOC容器的MultipartResolver的名字就是multipartResolver

3.用户配置优先

@Bean(
        name = {"multipartResolver"}
    )
       // 在IOC中不存在MultipartResolver类的时候,才会自动配置
    @ConditionalOnMissingBean({MultipartResolver.class})
    public StandardServletMultipartResolver multipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
        return multipartResolver;
    }

以上是为了解决,如果我们已经自定义配置了,那么已用户为准,不然我就自动配置了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值