Spring Boot 2 原理简介

Spring Boot 2 原理

springboot帮我们做了什么

通常搭建一个基于spring的web应用,我们需要做以下工作:

  1. pom文件中引入相关jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar ...

  2. 配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 ...

  3. 配置数据库连接、配置spring事务

  4. 配置视图解析器

  5. 开启注解、自动扫描功能

  6. 配置完成后部署tomcat、启动调试

  7. ......

  • 搭个初始项目不一会就一个小时甚至半天过去了。而用springboot后,一切都变得很简便快速。下来我们来一步步分析springboot的起步依赖与自动配置这两个核心原理。

起步依赖

1. 引入jar


<parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>2.0.3.RELEASE</version>

        <relativePath/> <!-- lookup parent from repository -->

</parent>

<!--mybatis 开发包-->

<dependency>

        <groupId>org.mybatis.spring.boot</groupId>

        <artifactId>mybatis-spring-boot-starter</artifactId>

        <version>1.3.1</version>

</dependency>

<!--springboot web模块支持-->

<dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

        <groupId>mysql</groupId>

        <artifactId>mysql-connector-java</artifactId>

        <scope>runtime</scope>

</dependency>

<!--druid 的数据源-->

<dependency>

        <groupId>com.alibaba</groupId>

        <artifactId>druid</artifactId>

        <version>1.0.31</version>

</dependency>

  • spring-boot-starter-web包自动帮我们引入了web模块开发需要的相关jar包,

  • mybatis-spring-boot-starter帮我们引入了dao开发相关的jar包。

  • spring-boot-starter-xxx是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter。

image
  • 可以看出在这个mybatis-spring-boot-starter 中,并没有任何源码,只有一个pom文件,它的作用就是帮我们引入了相关jar包。

2. 配置数据源


spring:

        datasource:

                url: jdbc:mysql://127.0.0.1:3306/mybatis_test

                username: root

                password: root

                driver-class-name: com.mysql.jdbc.Driver

                type: com.alibaba.druid.pool.DruidDataSource

                dbcp2:

                        min-idle: 5

                        initial-size: 5

                        max-total: 5

                        max-wait-millis: 200

  • stater机制帮我们完成了项目起步所需要的的相关jar包。那问题又来了,传统的spring应用中不是要在application.xml中配置很多bean的吗,比如dataSource的配置,transactionManager的配置 ... springboot是如何帮我们完成这些bean的配置的?下面我们来分析这个过程

自动配置

基于java代码的bean配置

  • 以mybatis为例,在上面的截图中,我们发下mybatis-spring-boot-starter这个包帮我们引入了mybatis-spring-boot-autoconfigure这个包,如下图:
image
image
image
  • 熟悉@Configuration&@Bean这两个bean的同学或许已经知道了。这两个注解一起使用就可以创建一个基于java代码的配置类,可以用来替代相应的xml配置文件。

  • @Configuration注解的类可以看作是能生产让Spring IoC容器管理的Bean实例的工厂。

  • @Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册到spring容器中。

  • 传统的基于xml的bean配置方法如下:


<beans>  

    <bean id = "car" class="com.itpsc.Car">  

        <property name="wheel" ref = "wheel"></property>  

    </bean>  

    <bean id = "wheel" class="com.itpsc.Wheel"></bean>  

</beans>

  • 相当于用基于java代码的配置方式:

@Configuration  

public class Conf {  

        @Bean  

        public Car car() {  

                Car car = new Car();  

                car.setWheel(wheel());  

                return car;  

        }  

        @Bean   

        public Wheel wheel() {  

                return new Wheel();  

        }  

}

  • 所以上面的MybatisAutoConfiguration这个类,自动帮我们生成了SqlSessionFactory这些Mybatis的重要实例并交给spring容器管理,从而完成bean的自动注册。

自动配置条件依赖

  • 从MybatisAutoConfiguration这个类中使用的注解可以看出,要完成自动配置是有依赖条件的。

@Configuration

@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

@ConditionalOnBean({DataSource.class})

@EnableConfigurationProperties({MybatisProperties.class})

@AutoConfigureAfter({DataSourceAutoConfiguration.class})

public class MybatisAutoConfiguration {

//....

}

  • 这些是springboot特有的,常见的条件依赖注解有:

  • @ConditionalOnBean,仅在当前上下文中存在某个bean时,才会实例化这个Bean。

  • @ConditionalOnClass,某个class位于类路径上,才会实例化这个Bean。

  • @ConditionalOnExpression,当表达式为true的时候,才会实例化这个Bean。

  • @ConditionalOnMissingBean,仅在当前上下文中不存在某个bean时,才会实例化这个Bean。

  • @ConditionalOnMissingClass,某个class在类路径上不存在的时候,才会实例化这个Bean。

  • @ConditionalOnNotWebApplication,不是web应用时才会实例化这个Bean。

  • @AutoConfigureAfter,在某个bean完成自动配置后实例化这个bean。

  • @AutoConfigureBefore,在某个bean完成自动配置前实例化这个bean。

  • 所以要完成Mybatis的自动配置,需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类,需要存在DataSource这个bean且这个bean完成自动注册。

  • 进入DataSourceAutoConfiguration这个类,可以看到这个类属于这个包:

  • org.springframework.boot.autoconfigure.jdbc

  • 这个包又属于spring-boot-autoconfigure-2.0.3.RELEASE.jar这个包,自动配置这个包帮们引入了jdbc、kafka、logging、mail、mongo等包。很多包需要我们引入相应jar后自动配置才生效。

springbootautoconfig.png
jars.png

bean参数获取

  • 到此我们已经知道了bean的配置过程,但是还没有看到springboot是如何读取yml或者properites配置文件的的属性来创建数据源的?

  • 在DataSourceAutoConfiguration类里面,我们注意到使用了EnableConfigurationProperties这个注解。


@Configuration

@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})

@EnableConfigurationProperties({DataSourceProperties.class})

@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})

public class DataSourceAutoConfiguration {

...

}

  • DataSourceProperties中封装了数据源的各个属性,且使用了注解ConfigurationProperties指定了配置文件的前缀。

@ConfigurationProperties(

        prefix = "spring.datasource"

)

public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

        private ClassLoader classLoader;

        private String name;

        private boolean generateUniqueName;

        private Class<? extends DataSource> type;

        private String driverClassName;

        private String url;

        private String username;

        private String password;

        private String jndiName;

        ...

}

  • @EnableConfigurationProperties与@ConfigurationProperties这两个注解有什么用呢?

  • @ConfigurationProperties注解的作用是把yml或者properties配置文件转化为bean。

  • @EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在spring容器中是获取不到yml或者properties配置文件转化的bean的。

bean发现

  • springboot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包的中的类,那么依赖包中的bean是如何被发现和加载的?

  • 我们通常在启动类中加@SpringBootApplication这个注解,点进去看


@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 {

...

}

  • 实际上重要的只有三个Annotation:

  • @Configuration(@SpringBootConfiguration里面还是应用了@Configuration)

  • @EnableAutoConfiguration

  • @ComponentScan

  • @Configuration的作用上面我们已经知道了,被注解的类将成为一个bean配置类。

  • @ComponentScan的作用就是自动扫描并加载符合条件的组件,比如@Component和@Repository等,最终将这些bean定义加载到spring容器中。

  • @EnableAutoConfiguration 这个注解的功能很重要,借助@Import的支持,收集和注册依赖包中相关的bean定义。


@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 {};

}

  • 如上源码,@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import这两个注解。@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。

  • 进入@AutoConfigurationPackage,发现也是引入了@Import注解


@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Import({Registrar.class})

public @interface AutoConfigurationPackage {

}


static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        Registrar() {

        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

                 AutoConfigurationPackages.register(registry, new String[]{(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()});

        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {

                return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));

        }

    }

  • new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()

  • new AutoConfigurationPackages.PackageImport(metadata)

  • 这两句代码的作用就是加载启动类所在的包下的主类与子类的所有组件注册到spring容器,这就是前文所说的springboot默认扫描启动类所在的包下的主类与子类的所有组件。

  • 那问题又来了,要搜集并注册到spring容器的那些beans来自哪里?

  • 进入 AutoConfigurationImportSelector类


public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

private static final String[] NO_IMPORTS = new String[0];

...

    public String[] selectImports(AnnotationMetadata annotationMetadata) {

                if(!this.isEnabled(annotationMetadata)) {

                        return NO_IMPORTS;

                } else {

                        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

                        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

                        List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

                        configurations = this.removeDuplicates(configurations);

                        Set exclusions = this.getExclusions(annotationMetadata, attributes);

                        this.checkExcludedClasses(configurations, exclusions);

                        configurations.removeAll(exclusions);

                        configurations = this.filter(configurations, autoConfigurationMetadata);

                        this.fireAutoConfigurationImportEvents(configurations, exclusions);

                        return StringUtils.toStringArray(configurations);

                }

    }

...

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

                List 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;

    }

...

}

  • SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

        MultiValueMap result = (MultiValueMap)cache.get(classLoader);

        if(result != null) {

                return result;

        } else {

                try {

                        Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");

                        LinkedMultiValueMap result1 = new LinkedMultiValueMap();

                        while(ex.hasMoreElements()) {

                        URL url = (URL)ex.nextElement();

                        UrlResource resource = new UrlResource(url);

                        Properties properties = PropertiesLoaderUtils.loadProperties(resource);

                        Iterator var6 = properties.entrySet().iterator();

                        while(var6.hasNext()) {

                                Entry entry = (Entry)var6.next();

                                List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));

                                result1.addAll((String)entry.getKey(), factoryClassNames);

                        }

                        }

                        cache.put(classLoader, result1);

                        return result1;

                } catch (IOException var9) {

                        throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);

            }

        }

    }

  • 下面是spring-boot-autoconfigure这个jar中spring.factories文件部分内容,其中有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定义了需要自动配置的bean,通过读取这个配置获取一组@Configuration类。

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\

org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\

org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

  • 每个xxxAutoConfiguration都是一个基于java的bean配置类。实际上,这些xxxAutoConfiguratio不是所有都会被加载,会根据xxxAutoConfiguration上的@ConditionalOnClass等条件判断是否加载。

private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {

        try {

                Class ex = ClassUtils.forName(instanceClassName, classLoader);

                if(!factoryClass.isAssignableFrom(ex)) {

                        throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");

                } else {

                        return ReflectionUtils.accessibleConstructor(ex, new Class[0]).newInstance(new Object[0]);

                }

         } catch (Throwable var4) {

                throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);

        }

}

  • 如上代码段,通过反射机制将spring.factories中@Configuration类实例化为对应的java实列。到此我们已经知道怎么发现要自动配置的bean了,最后一步就是怎么样将这些bean加载到spring容器。

bean加载

  • 如果要让一个普通类交给Spring容器管理,通常有以下方法:
  1. 使用 @Configuration与@Bean 注解

  2. 使用@Controller @Service @Repository @Component 注解标注该类,然后启用@ComponentScan自动扫描

  3. 使用@Import 方法

  • springboot中使用了@Import 方法

  • @EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector实现了DeferredImportSelector接口,

DeferredImportSelector接口继承了ImportSelector接口,ImportSelector接口只有一个selectImports方法。

总结

  • 我们可以将自动配置的关键几步以及相应的注解总结如下:
  1. @Configuration&与@Bean->基于java代码的bean配置

  2. @Conditional->设置自动配置条件依赖

  3. @EnableConfigurationProperties与@ConfigurationProperties->读取配置文件转换为bean。

  4. @EnableAutoConfiguration、@AutoConfigurationPackage 与@Import->实现bean发现与加载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容