001SpringBoot HelloWorld运行原理

作者: 编程界的小学生 | 来源:发表于2018-05-18 12:27 被阅读181次

一、完整代码

1、包结构

image.png

2、pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>

3、启动类

/**
 * 启动类的Springboot注解
 */
@SpringBootApplication
public class HelloWorldMainApplication {
    public static void main(String[] args) {
        // 启动Spring应用
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
}

4、HelloController

/**
 * @author chentongwei@bshf360.com 2018-05-16 13:57
 */
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "hello world!";
    }
}

二、启动测试

1、启动

直接运行HelloWorldMainApplicationmain方法即可。

2、测试

http://www.localhost:8080/hello,即可看到返回hello world!字样。

三、原理分析

1、pom分析

1.1、首先看到依赖了一个parent节点。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.9.RELEASE</version>
</parent>

1.2、点进去看下spring-boot-starter-parent

spring-boot-starter-parent-1.5.9.RELEASE.pom

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-dependencies</artifactId>
   <version>1.5.9.RELEASE</version>
   <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

1.3、在继续点进去看下spring-boot-dependencies

spring-boot-dependencies-1.5.9.RELEASE.pom

<properties>
        <!-- Dependency versions -->
        <activemq.version>5.14.5</activemq.version>
        <antlr2.version>2.7.7</antlr2.version>
        <appengine-sdk.version>1.9.59</appengine-sdk.version>
        <artemis.version>1.5.5</artemis.version>
        <aspectj.version>1.8.13</aspectj.version>
        <assertj.version>2.6.0</assertj.version>
        <atomikos.version>3.9.3</atomikos.version>
        <bitronix.version>2.1.4</bitronix.version>
        <caffeine.version>2.3.5</caffeine.version>
        <cassandra-driver.version>3.1.4</cassandra-driver.version>
        ......
</properties>

1.4、答疑阶段

看到这,大概明白了一个问题:

疑问1:为什么用SpringBoot有的依赖需要写版本号,有的不需要写?

答案:因为我们项目的pom里引入了parent节点,所以将最终的spring-boot-dependencies-1.5.9.RELEASE.pom也继承了来,而spring-boot-dependencies-1.5.9.RELEASE.pom里面管理了大部分的jar包的版本,所以我们依赖jar包的时候无需写版本号,比如:我们上面所依赖的

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

这里没指定版本号是因为spring-boot-dependencies-1.5.9.RELEASE.pom这里指定了,如下:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.5.9.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>

而那些在parent中没有被依赖的组件就需要手动指定版本号了。因为无法从父pom继承来。

疑问2:为什么启动main函数就能访问url?

答案:因为SpringBoot内嵌了一个tomcat。

追问2:那内嵌的tomcat版本号是多少呢?

答案:spring-boot-dependencies-1.5.9.RELEASE.pom这里也有指定,如下:

<tomcat.version>8.5.23</tomcat.version>

PS:证明:1.5.9版本的SpringBoot默认采取的是8.5.23版本的tomcat。

2、注解分析

2.1、@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 {
}

PS:拥有三个关键注解:SpringBootConfiguration、EnableAutoConfiguration、ComponentScan,下面我们逐个讲解。

2.2、@SpringBootConfiguration

SpringBoot配置类。标注在某个类上,表示这是一个SpringBoot的配置类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

PS:可以看到,底层运用了Spring的注解@Configuration。这个注解就是标明一个配置类的,配置类也就是以前的配置文件,也是一个组件(不信的话可以继续看@Configuration的源码,他上面标注了@Component注解)。

2.3、@EnableAutoConfiguration

开启自动配置功能(就是不用写一堆繁琐的配置文件了,自动帮我们配置完了)。

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

PS:拥有两个关键注解:AutoConfigurationPackage、Import我们逐个讲解。

2.3.1、@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

自动配置包

@Import(AutoConfigurationPackages.Registrar.class)

给Spring容器中导入了Registrar组件。

将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器。(这个很关键!!!!就是由@Import(AutoConfigurationPackages.Registrar.class)这句话完成的。)

@Order(Ordered.HIGHEST_PRECEDENCE)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata,
         BeanDefinitionRegistry registry) {
      register(registry, new PackageImport(metadata).getPackageName());
   }

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

}

PS:我们可以在register(registry, new PackageImport(metadata).getPackageName());这一行打断点,并重启服务,debug追踪。

new PackageImport(metadata).getPackageName()这句话返回了我们@SpringBootApplication注解所修饰类的所在包。然后他会加载这个包下所有子目录的所有组件到Spring容器中。

2.3.2、@Import

Spring的一个注解,作用就是快速给容器中导入一些组件。

@Import(EnableAutoConfigurationImportSelector.class)

PS:给容器中导入由EnableAutoConfigurationImportSelector所选择的这些组件。

EnableAutoConfigurationImportSelector:导入哪些组件的选择器;将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中。

public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {}

PS:子类就一个Boolean类型的方法,没重要信息。我们看父类。

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

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            configurations = sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }
}

PS:我们给selectImports这个方法打断点进行追踪。

getCandidateConfigurations()=》loadFactoryNames()

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
      getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

我们针对configurations进行查看,如下图:

image.png

PS:有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;

我们可以看SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader);方法

Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration 指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;以前我们需要自己配置的东西,自动配置类都帮我们;

J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar

2.4、@ComponentScan

Spring的注解,相当于配置文件的

<context:component-scan base-package="xxx">
    <context:exclude-filter type="annotation"  expression="xxxx"/>
</context:component-scan>

四、活学活用

通过上面的分析,我发现直接在启动类上写上三个注解,而不是SpringBootApplication也可以。如下

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class HelloWorldMainApplication {
    public static void main(String[] args) {
        // 启动Spring应用
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
}

所以@SpringBootApplication注解是上面三个注解的结合版,我猜开发团队想的是:写一个会更省事。

五、广告

  • QQ群【Java初学者学习交流群】:458430385

  • 微信公众号【Java码农社区】

img
  • 今日头条号:编程界的小学生

相关文章

网友评论

本文标题:001SpringBoot HelloWorld运行原理

本文链接:https://www.haomeiwen.com/subject/lxhgdftx.html