001SpringBoot HelloWorld运行原理
一、完整代码
1、包结构
![](https://img.haomeiwen.com/i4582242/7e7b4c7b4c18febe.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、启动
直接运行HelloWorldMainApplication
的main
方法即可。
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进行查看,如下图:
![](https://img.haomeiwen.com/i4582242/05ccb9eca9f94c69.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码农社区】
![](https://img.haomeiwen.com/i4582242/ca4a357ae859b1aa.jpg)
- 今日头条号:编程界的小学生