wangwang
wangwang
文章45
标签44
分类5
SpringBoot源码剖析

SpringBoot源码剖析

SpringBootܻ原理深入及源码剖析

依赖管理

问题一:为什么某些Maven依赖不需要指定版本号?

因为spring-boot-starter-parent中已经定义了常用依赖匹配的版本号.

按住Ctrl,点击spring-boot-starter-parent的版本号

image-20221013155539028

可见pom文件中的parent是spring-boot-dependencies,然后再次按住Ctrl,点击版本号

image-20221013155647320

我们会发现这个pom中维护了一大堆依赖的版本号

image-20221013155755730

问题二:Springboot是如何引入这些jar包的

在我们做web项目时,除了上面的parent外,还会引入spring-boot-starter-web,而在这个依赖中,则定义了web项目需要的jar依赖.

image-20221013160305222

依据不同的使用场景,spring官方也提供了不同的starter封装

image-20221013160428204

问题三:Springboot是如何进行自动配置的

我们进入查看启动类上的SpringBootApplication注解

@Target({ElementType.TYPE}) // 注解适用范围,Type表示注解可以描述类,接口,注解或枚举类
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented // 表示注解可以记录在javadoc中
@Inherited // 表示可以被子类继承该注解
@SpringBootConfiguration // 表明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan( // 包扫描器
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}
1.@SpringBootConfiguration

声明当前类为配置类



@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
2.@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
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class}) //导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

我们继续查看Registrar中的实现

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

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

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

可以发现其中有一个registerBeanDefinitions方法,就是将主程序类所在包及所有子包下的组件扫描的Spring容器中

  • @Import({AutoConfigurationImportSelector.class})

将AutoConfigurationImportSelector类导入到容器中,这个类可以将所有符合条件的@Configuration注解类配置加载到Spring的IOC容器中.

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

深入loadMetadata方法

 static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
         // 需要加载的配置类的类路径
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
            // 读取spring-autoconfigure-metadata.properties信息生成的url
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();

            // 解析urls枚举对象中的信息,封装成properties对象并加载
            while(urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
            }
            // 根据封装好的properties对象生成AutoConfigurationMetada对象
            return loadMetadata(properties);
        } catch (IOException var4) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
        }
    }

深入getCandidateConfigurations方法

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        /**
        * getSpringFactoriesLoaderFactoryClass 返回EnableAutoConfiguration.class
        * getBeanClassLoader 返回beanClassLoader
        */
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "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;
    }

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

    protected ClassLoader getBeanClassLoader() {
        return this.beanClassLoader;
    }

继续点开loadFactoryNames方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                // 如果类加载器不为空,则加载spring.factories文件,封装其中Configuration文件为Enumeration
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                MultiValueMap<String, String> result = new LinkedMultiValueMap();
                // 循环Enumeration对象,封装为Properties对象
                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()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

实际上,加载的就是这个路径下的配置文件

image-20221013164539737

以web项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration就会生效,打开这个配置类,就会发现该类通过全注解的方式对Spring所需环境进行了默认配置.

总结

springboot底层自动配置的步骤是:

1.springboot应用启动

2.@SpringBootApplication起作用

3.@EnableAutoConfiguration

4.@AutoConfigurationPackage:扫描加载启动类同级及子级目录下的配置类

5.@Import(AutoConfigurationImportSelector.class):加载classpath上jar中的META-INF/spring.factories文件中指定的配置类

3.@ComponentScan

扫描启动类下面的bean对象

总结
|- @SpringBootConfiguration
 |- @Configuration //通过javaConfig的方式添加组件到IOC容器中
|- @EnableAutoConfiguration
 |- @AutoConfigurationPackage //自动配置包,与@ComponmentScan扫描到的添加到IOC中
 |- @Import(AutoConfigurationImportSelector.class) // 扫描META-INF/spring.factories中指定的bean到IOC容器中
|- @ComponentScan //包扫描

自定义Starter

1.引入自动配置依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

2.定义JavaBean

@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simple")
@Data
public class SimpleBean {

    private int id;

    private String name;

}

3.编写配置类

@Configuration
public class MyAutoConfiguration {

    static {
        System.out.println("MyAutoConfiguration init....");
    }

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }

}

4.配置启动类加载路径

在resources下创建/META-INF/spring.factories文件,指定配置类全路径

image-20221014135815091

5.测试starter

在测试包中引入自定义starter

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>my-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

在配置文件application.properties中加入前缀配置

simple.id=1
simple.name=test

编写测试方法

    @Autowired
    private SimpleBean simpleBean;

    @Test
    public void testStarter(){
        System.out.println(simpleBean);
    }

执行原理

我们从启动类开始断点查看程序执行步骤,首先是调用了SpringApplication的run方法.

在run方法中,主要做了两个操作,一个是SpringApplication的初始化,以及调用run()启动项目

(new SpringApplication(primarySources)).run(args);

1.SpringApplication初始化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //把项目启动类.class设置为属性存储起来
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    //判断当前WebApplicationType类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //设置初始化器,最后会调用这些初始化器
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //设置监听器
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    //用于推断并设置项目main()方法启动的主程序启动类
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

2.run()方法执行

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        // 1.获取监听器并启动
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 2.根据SpringApplicationRunListeners以及参数来准备环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            // 准备Banner打印器,就是console中输出的艺术字体
            Banner printedBanner = this.printBanner(environment);
            // 3.创建Spring容器
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 4.Spring容器前置处理
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 5.刷新容器
            this.refreshContext(context);
            // 6.容器刷新后置处理
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            // 7.发出结束执行的事件
            listeners.started(context);
            // 返回容器
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

image-20221014143737733

Springboot缓存管理

默认缓存

1.启动类使用@EnableCaching注解

@SpringBootApplication
@EnableCaching // 开启SpringBoot基于注解的缓存管理支持
public class Springboot01DemoApplication {

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

}

2.查询方法添加@Cacheable注解配置缓存

// 根据id查询实体类
@Cacheable(cacheNames = "a")
public A findById(String id){
    A a = dao.findById(id)
    return a;    
}

springboot默认使用的是SimpleCacheConfiguration配置类,用CurrentMap当作底层数据结构进行数据缓存

整合Reids进行缓存

1.添加Spring Data Redis依赖启动器
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

当我们加入此依赖后,Springboot会使用RedisCacheConfiguration当作生效的自动配置类,容器使用的缓存管理器是RedisCacheManager类,创建的Cache是RedisCache

2.添加redis连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
3.将缓存对象进行序列化

对缓存的实体类实现Serializable,添加序列化ID

4.使用上面同样的注解,即可实现基于Redis的缓存
本文作者:wangwang
本文链接:https://www.wangwangit.com/SpringBoot%E4%B8%80/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可