Spring Boot实战:逐行释义HelloWorld

一、前言

   研究 Spring boot 也有一小段时间了,最近会将研究东西整理一下给大家分享,大概会有 10~20 篇左右的博客,整个系列会以一个简单的博客系统作为基础,因为光讲理论很多东西不是特别容易理解,并且如果每次通过一个简单的小程序也无法系统的把握好一些知识点,所以就以一个简单的系统作为基础来讲,看看通过 spring boot 如何实现一个完整系统。本系列除了 Spring boot 基本的知识点之外,还会涉及到 Spring boot 与数据库、缓存(redis)、消息队列等的结合以及多实例部署等方面的内容。有兴趣的同学可以关注一下。

二、Spring boot 简介

   Spring boot 从名称上就可以看出,它是基于 Spring 的一个框架,所以不熟悉 Spring 的同学还是得先去学习一下 Spring。其次,Spring boot 帮我们集成很多常用的功能,使得整个配置更加简单。用过 Spring 的同学应该知道,虽然 Spring 一直在努力的减少配置的复杂性,但是,配置一个完全可用的(web)环境还是挺麻烦的,比如需要配置日志、数据库、缓存等,然后再配置 tomcat,最后将程序发布到 tomcat 目录下。而 Spring boot 则帮我们大大简化了这个过程,它提供了很多 starter,只要引入对应的 jar 包就可以了。例如,我们需要集成 tomcat,只需要引入 tomcat 的 starter 即可:

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

  备注:本文的例子都是基于 Maven 来实现的,所以如果不熟悉 Maven,可以先去看下怎么用,如果熟悉 gradle 的话,也可以根据情况对配置做相应调整。

  我们可以从官方文档上查看 Spring boot 提供的 starter:

  这里我只截取了一小部分,可以看到 Spring boot 支持缓存、批处理、mq、es 等等,完整的列表参考官方文档。其他就不多解释了,后续通过示例来讲解整个 Spring boot 功能,我们先看 Spring boot 来如何实现一个 web 版的 Hello World!

三、Hello World 程序

  3.1 Hello World 源码

  第一步:导入 jar 包

1
2
3
4
5
6
7
8
9
10
11
<parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.5.8.RELEASE</version>
   </parent>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
   </dependencies>

  第二步:编写控制器类  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.pandy.blog;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
import java.util.HashMap;
import java.util.Map;
 
@Controller
public class HelloWorld {
    @RequestMapping("/hello")
    @ResponseBody
    public Map<String, Object> hello() {
        Map<String, Object> map = new HashMap<>();
        map.put("hello", "world");
        return map;
    }
}

  第三步:编写启动类(入库)

1
2
3
4
5
6
7
8
9
10
11
12
package com.pandy.blog;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class Application {
 
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

 运行该类的 main 方法,然后访问 http://localhost:8080/hello,就可以看到如下结果:

  是不是感觉很开心?一行配置都没有,就可以直接运行一个 Web 应用。不过开心完以后有没有想过这是怎么实现的呢?接下来我们一行一行解析上面的代码,虽然行数不多,但是还是有很多东西值得我们去学习和理解的。

  3.2 pom 文件分析

  我们先从 pom 文件入手,pom 文件里面只引入了两个依赖项。第一个是 spring-boot-starter-parent,熟悉 Maven 的朋友应该知道,Maven 也可以跟类一样,从父 pom 文件中继承配置。我们可以看下 spring-boot-starter-parent 的 pom 文件,由于篇幅问题,这里面只看两部分,其他东西比较容易理解,大家可以自己读一下。第一个部分是:

  该文件又继承了另一个 pom 文件,即 spring-boot-dependencies,这个文件其实就是包含了一大堆的 jar,其作用是统一管理 spring boot 所依赖的 jar 包的版本,所以之后大家可以看到,各个组件里面引入 jar 的时候就不再需要再指定版本号了。另一个地方需要说明一下是配置文件的管理:

  大家可以看到,默认情况下会将 /src/main/resources 目录下的文件作为资源文件加入到 classpath 下,另外,这个地方的仅仅对 application*.yml,application*.yaml,application*.properties 三种文件进行过滤。这个过滤是指什么呢?大家配置过 spring mvc 的人应该都知道,配置数据库时,我们通常将数据库的信息配置在一个 properties 文件中,然后在 spring 的配置文件中通过 <property name="driverClass" value="${jdbc.driver}" /> 的形式引入,这个 filter 的作用就是在编译的时候将配置文件中配置的名值对替换到 spring 的配置文件中 ${xxx} 字符,但这个功能不是必要的,即使不进行替换,Spring 也能在运行时读取到配置项。

  总结一下:spring-boot-starter-parent 的作用::

  1)jar 包的版本管理。

  2)配置文件的过滤。

  3)常用插件管理。

  spring-boot-starter-parent 最核心的功能是管理了 Spring boot 所依赖的所有 jar 包。不过通 parent 的方式有一个很明显的问题,很多公司自己有自己的 parent 文件,而 maven 是没办法配置多个 parent 的。如果不使用 spring-boot-starter-parent,那应该怎么做??实际上 Spring boot 提供了另一种方式来解决这个问题,就是在自己的 pom 文件中加入 spring boot 的依赖的管理:

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
     <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>1.5.9.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

  其实从上面的分析可以看到,这个也是 spring-boot-starter-parent 的 pom 文件的 parent,而这个 pom 文件里面主要就是管理了一大堆的 jar 包版本。所以导入这个后,就不需要自己再去做版本管理,各个 starter 会自己根据需要导入对应的 jar,但版本号由 spring-boot-dependencies 统一管理。但是这样的话,spring-boot-starter-parent 中的插件就无法使用,并且默认配置文件的过滤功能也没有了。不过这没什么影响,一方面这些功能不是必须的,另一方面如果需要,自己添加也是件很容易的事情。  

  3.3 HelloWorld 类解析:

  我们再看下 HelloWorld 这个类,用过 Spring mvc 应该知道,其实这个类跟 Spring boot 没半毛钱关系,业务代码更是没任何跟 spring 相关的东西,这也是 spring 一直奉行的一个原则,侵入性极小,这也是 Spring 成功的一个主要原因。这个类里面跟 spring 相关的是三个注解,即 @Controller,@RequestMapping,@ResponseBody,但是这三个注解也都是 Spring mvc 提供的。跟 Spring boot 没有太多联系,在这我就不细讲了,如果不是很清楚,可以去看下 Spring MVC 的内容,三个注解的基本作用如下:

  • Controller:标识为一个控制器,spring 会自动实例化该类。
  • RequestMapping:url 映射。
  • ResponseBody:将返回结果自动转换为 json 串。

  3.4 Application 类解析

  最后我们看下 Application 这个类,你会发现这个类的东西更少,总共就一行有用的代码,即 SpringApplication.run(Application.class, args); 这个方法的作用是加载 Application 这个类,那 Application 这个类有什么特别之处吗?可以看一下,其实这个类的唯一特殊的地方是一个注解 @SpringBootApplication,所以 Spring boot 的运行肯定跟这个注解有着诸多的联系,我们可以看下这个注解的源码:

1
2
3
4
5
6
7
8
9
10
@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 {

  该注解的主要方法就不说了,大家看下就知道,主要是为上面这些注解提供别名。该注解上前四个注解(@Target(ElementType.TYPE),@Retention(RetentionPolicy.RUNTIME),@Documented,@Inherited)大家应该都知道,不熟悉的朋友自己去看下 JDK 如何实现自定义的注解。我们详细解释一下后面三个注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。

  先看一下 SpringBootConfiguration,这个注解比较简单,源码如下:

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

  这个注解仅仅是继承了 @Configuration,大家应该知道,Spring 提供三种方式的配置:(1)xml 文件配置(2)annotation 配置(3)Java 类配置。而 @Configuration 就是用于标识一个类为配置类的注解。Spring 4 以后比较推崇通过 Java 类的方式来进行配置,所以 Spring boot 也倾向于这种方配置式。并且从源码可以看出,SpringBootConfiguration 的作用就是标识类为配置类。

  接下来我们看一下 @EnableAutoConfiguration 注解,这个注解的源码有点复杂,在这不做细讲,后面的文章再详细解析期实现方式。这里说一下该注解的作用,它的主要功能是实现自动配置,什么叫做自动配置?就是 Spring boot 会根据你引入的 jar 包做一些自动的配置,例如,在 classpath 有 HSQLDB 的 jar,spring boot 就会自动给你配置一个内存数据库。在这个例子里面我们也可以看到,因为我们引入了 Spring-mvc、tomcat 等相关的 jar,spring boot 就会猜测你是一个 web 工程,然后就会自动做一些 spring mvc 的配置,比如对静态资源的支持、将返回结果自动转为 json 格式数据的支持等。这些都是自动配置的结果。对 Spring Enable* 注解熟悉的同学应该能够更容易理解这个注解,因为 Spring 中有很多类似的注解。

  最后我们再看下 @ComponentScan,这个注解不是 Spring boot 提供的,而是 Spring 提供的,Spring 扫描的包或类,即哪些包和类会自动纳入 Spring IoC 容器的管理,IoC 根据配置对这些类进行实例化。

   现在我们再总结一下 SpringBootConfiguration 这个注解的作用:

  1)标志该类为一个配置类。
  2)指定扫描的包,便于 Spring IoC 容器对其进行实例和生命周期的管理。
  3)自动配置,通过引入的 jar 包,猜测用户的意图进行自动化配置。

四、总结

   本文详细分析了 Spring boot 实现的一个 web 版的 Hello World,通过这个例子,我们了解了 Spring boot 的基本操作,并通过对每行的代码的分析,对 Spring boot 的原理有了一个大致的了解。总体来讲,Spring boot 统一管理了 jar 包,然后会根据我们选择的 starter 来进行自动化配置,通过这种方式来解决复杂的依赖管理,精简配置,从而使得开发者能够更加专注于自己的业务,而不需要做那些很复杂的配置工作。同时,Spring boot 这种快速、轻量级的服务也非常适合微服务架构,这个后续有机会再跟大家分享,欢迎继续关注。