Spring Boot的前世今生以及它和Spring Cloud的關係詳解。

要了解Spring Boot的發展背景,還得從2004年Spring Framework1.0版本發布開始說起,不過大家都是從開始學習Java就使用Spring Framework了,所以就不做過多展開。

隨著使用Spring Framework進行開發的企業和個人越來越多,Spring 也慢慢從一個單一簡潔的小框架編程了一個大而全的開源軟體,Spring Framework的邊界不斷進行擴張,到了現在Spring 幾乎可以做任何事情。目前市面上絕大部分的開源組件和中間件,都有Spring對應組件的支援,

你們如果去關注Spring目前的官網,你會發現他的slogan是:Spring makes Java Simple。它讓Java的開發變得更加簡單。

雖然Spring的組件程式碼是輕量級的,但是它的配置卻是重量級的,Spring 每集成一個開源軟體,就需要增加一些基礎配置,慢慢的隨著我們開發的項目越來越龐大,往往需要集成很多開源軟體,因此後期使用 Spirng 開發大型項目需要引入很多配置文件,太多的配置非常難以理解,並容易配置出錯,這個給開發人員帶來了不少的負擔。

大家想像一個場景,就是假如你需要用spring開發一個簡單的Hello World Web應用程式,應該要做哪些動作呢?

  • 創建一個項目結構,必然包含依賴Maven或者Gradle的構建文件。
  • 至少需要添加spring mvc和servlet api的依賴
  • 一個web.xml,聲明spring的DispatcherServlet
  • 一個啟用了Spring MVC的spring 配置
  • 一個控制器類,「以HelloWord」為響應的http請求
  • 一個用於部署應用程式的web應用伺服器,比如Tomcat

在整個過程中,我們發現只有一個東西和Hello Word功能相關,那就是控制器(controller),剩下的都是Spring 開發的Web應用程式必須要的通用模版,既然所有Spring Web應用程式都要用到他們,那為什麼還要你來提供這些東西呢?

所以,直到2012年10月份,一個叫Mike Youngstrom(揚斯特羅姆)在Spring Jira中創建了一個功能請求,要求在Spring Framework中支援無容器Web應用程式體系結構,他談到了在主容器引導 spring 容器內配置 Web 容器服務。

//jira.spring.io/browse/SPR-9888

I think that Spring's web application architecture can be significantly simplified if it were to provided tools and a reference architecture that leveraged the Spring component and configuration model from top to bottom. Embedding and unifying the configuration of those common web container services within a Spring Container bootstrapped from a simple main() method.
我認為,如果要提供從上到下充分利用Spring組件和配置模型的工具和參考體系結構,則可以大大簡化Spring的Web應用程式體系結構。在通過簡單main()方法引導的Spring容器中嵌入和統一那些通用Web容器服務的配置。

而且Spring 開發團隊也意識到了這些問題,急需要一套軟體來解決這個問題,而這個時候微服務的概念也慢慢的起來,快速開發微小獨立的應用也變得很急迫。

而Spring恰好處在這樣一個交叉點上,所以順勢而為在2013年初的時候,開始投入Spring Boot項目的研發,直到2014年4月,Spring Boot1.0版本發布。從那以後,Spring Boot開啟了一些列的迭代和升級的過程。

經過7年時間的發展,到目前為止,Spring Boot最新穩定版為2.6.0版本。

Spring Boot的發展

Spring Boot剛出生的時候,引起了很多開源社區的關注,並且也有個人和企業開始嘗試使用Spring Boot。 其實直到2016年,Spring Boot才真正在中國被使用起來。我之前在挖財的時候,2015年公司就開始採用Spring Boot來構建基於Dubbo的微服務架構。到現在,Spring Boot幾乎是所有公司的第一選擇。

Build Anything

Spring Boot被官方定位為「BUILD ANYTHING」,Spring Boot官方的概述是這麼描述Spring Boot的。

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".
// 通過Spring Boot可以輕鬆的創建獨立的、生產級別的基於Spring 生態下的應用,你只需要運行即可。
We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.
//對於Spring平台和第三方庫,我們提供了一個固化的視圖,這個視圖可以讓我們在構建應用是減少很多麻煩。大部分spring boot應用只需要最小的Spring 配置即可。

如果大家不習慣看英文文檔,可能理解起來比較複雜,翻譯成人話就是:Spring Boot能夠幫助使用Spring Framework生態的開發者快速高效的構建一個基於Spring以及spring 生態體系的應用。

為了讓大家對這句話的理解更加深刻,我們來做兩個小實驗,一個是基於傳統的Spring MVC框架構建一個項目、另一種是使用Spring Boot。

Spring MVC With Spring Boot

通過Spring MVC項目搭建過程來對比Spring Boot的差異和優勢。

Spring MVC項目搭建過程

  • 創建一個maven-webapp項目

  • 添加jar包依賴

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    
    spring-context
    spring-context-support
    spring-core
    spring-expression
    spring-web
    spring-webmvc
    
  • 修改web.xml文件

    <context-param><!--配置上下文配置路徑-->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!--配置監聽器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>
    <!--配置Spring MVC的請求攔截-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dispatcher-servlet.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-patter>
    </servlet-mapping>
    
  • 在resources目錄下添加dispatcher-servlet.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
           xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
           xmlns:context="//www.springframework.org/schema/context"
           xmlns:mvc="//www.springframework.org/schema/mvc"
           xmlns:aop="//www.springframework.org/schema/aop"
           xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
           //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd
           //www.springframework.org/schema/mvc //www.springframework.org/schema/mvc/spring-mvc.xsd
           //www.springframework.org/schema/aop //www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 掃描 controller -->
    <context:component-scan base-package="com.gupaoedu.controller" />
    <!--開啟註解驅動-->
    <mvc:annotation-driven/>
    <!-- 定義視圖解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
  • 創建一個Controller

    @Controller
    public class HelloController {
    
        @RequestMapping(method = RequestMethod.GET,path = "/index")
        public String index(Model model){
            model.addAttribute("key","Hello Gupao");
            return "index";
        }
    }
    
  • 修改默認的index.jsp,設置el表達式的解析

    <%@ page language="java" contentType="text/html; charset=utf-8"
             pageEncoding="utf-8" isELIgnored="false" %>
    ${key}
    
  • 運行項目

Spring Boot搭建過程

直接基於start.spring.io這個腳手架搭建即可。

思考和總結

咱們再回到最開始Spring Boot的定義部分,Spring Boot能夠幫助使用Spring Framework生態的開發者快速高效的構建一個基於Spring以及spring 生態體系的應用。

再對比兩種構建過程,似乎也能夠理解Spring Boot的作用了吧。當然它的作用不僅於此,後續會逐步揭開它的真實面目。

通過上面這個案例我們發現,如果沒有spring boot,要去構建一個Spring MVC的web應用,需要做的事情很多

  • 引入jar包
  • 修改web.xml,添加監聽和攔截
  • 創建spring mvc核心配置文件dispatcher-servlet.xml
  • 創建controller
  • 部署到tomcat

這個過程如果不熟悉,很可能需要1~2個小時,如果是新手,可能需要更長時間。但是spring boot,不管是新手還是老手,都能夠分分鐘解決問題。

理解約定優於配置

我們知道,Spring Boot是約定由於配置理念下的產物,那麼什麼是約定由於配置呢?

約定優於配置是一種軟體設計的範式,主要是為了減少軟體開發人員需做決定的數量,獲得簡單的好處,而又不失靈活性。

簡單來說,就是你所使用的工具默認會提供一種約定,如果這個約定和你的期待相符合,就可以省略那些基礎的配置,否則,你就需要通過相關配置來達到你所期待的方式。

約定優於配置有很多地方體現,舉個例子,比如交通訊號燈,紅燈停、綠燈行,這個是一個交通規範。你可以在紅燈的時候不停,因為此時沒有一個障礙物阻礙你。但是如果大家都按照這個約定來執行,那麼不管是交通的順暢度還是安全性都比較好。

而相對於技術層面來說,約定有很多地方體現,比如一個公司,會有專門的文檔格式、程式碼提交規範、介面命名規範、資料庫規範等等。這些規定的意義都是讓整個項目的可讀性和可維護性更強。

Spring Boot Web應用中約定優於配置的體現

那麼在前面的案例中,我們可以思考一下,Spring Boot為什麼能夠把原本繁瑣又麻煩的工作省略掉呢? 實際上這些工作並不是真正意義上省略了,只是Spring Boot幫我們默認實現了。

而這個時候我們反過來思考一下,Spring Boot Web應用中,相對Spring MVC框架的構建而言,它的約定由於配置體現在哪些方面呢?

  • Spring Boot的項目結構約定,Spring Boot默認採用Maven的目錄結構,其中

    src.main.java 存放源程式碼文件

    src.main.resource 存放資源文件

    src.test.java 測試程式碼

    src.test.resource 測試資源文件

    target 編譯後的class文件和jar文件

  • 內置了嵌入式的Web容器,在Spring 2.2.6版本的官方文檔中3.9章節中,有說明Spring Boot支援四種嵌入式的Web容器

    Tomcat

    Jetty

    Undertow

    Reactor

  • Spring Boot默認提供了兩種配置文件,一種是application.properties、另一種是application.yml。Spring Boot默認會從該配置文件中去解析配置進行載入。

  • Spring Boot通過starter依賴,來減少第三方jar的依賴。

這些就是Spring Boot能夠方便快捷的構建一個Web應用的秘密。當然Spring Boot的約定優於配置還不僅體現在這些地方,在後續的分析中還會看到Spring Boot中約定優於配置的體現。

Spring Boot整合Mybatis

實際上Spring Boot的本質就是Spring,如果一定要從技術發展的過程中找到一些相似的對比的話,你們可以對比一下Jsp/Servlet和Spring MVC, 兩者都可以用來開發Web項目,但是在使用上,Spring MVC的使用會更加簡單。

而Spring Boot和Spring 就相當於當年的JSP/Servlet和Spring MVC的關係。所以它本身並沒有所謂新的技術,接下來,我帶著大家來通過Spring Boot整合Mybatis實現數據的基本操作的案例,來繼續認識一下Spring Boot。

創建Spring Boot 應用

創建一個Web項目

引入項目中需要的starter依賴

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

創建資料庫表

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `address` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

配置資料庫連接

spring:
  datasource:
    url: jdbc:mysql://192.168.13.106:3306/test_springboot
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

開發資料庫訪問層

1587536145803

創建實體對象

public class User {
    private int id;
    private String name;
    private String address;
}

創建Mapper

//@Repository可以支援在你的持久層作為一個標記,可以去自動處理資料庫操作產生的異常
@Repository
@Mapper
public interface UserMapper {

    User findById(int id);
    List<User> list();
    int insert(User user);
    int delete(int id);
    int update(User user);
}

編寫mapper文件

在resource文件目錄下創建UserMapper.xml文件,內容如下

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC
        "-//mybatis.org//DTD com.example.Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <resultMap id="resultMap" type="com.example.demo.entity.User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="address" column="address"/>
    </resultMap>

    <select id="findById" resultMap="resultMap" parameterType="java.lang.Integer">
        select * from t_user where id=#{id}
    </select>
    <select id="list" resultMap="resultMap">
        select * from t_user
    </select>
    <insert id="insert" parameterType="com.example.demo.entity.User" keyProperty="id" useGeneratedKeys="true">
        insert into t_user(name,address) values(#{name,jdbcType=VARCHAR},#{address,jdbcType=VARCHAR})
    </insert>
    <delete id="delete" parameterType="java.lang.Integer">
        delete from t_user where id=#{id}
    </delete>
    <update id="update" parameterType="com.example.demo.entity.User">
        update t_user set name=#{name,jdbcType=VARCHAR},address=#{address,jdbcType=VARCHAR} where id=#{id,jdbcType=INTEGER}
    </update>
</mapper>

定義service及實現

public interface IUserService {

    User findById(int id);
    List<User> list();
    int insert(User user);
    int delete(int id);
    int update(User user);
}

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;
}

創建Controller

@RestController
public class Controller {

    @Autowired
    private IUserService userService;

    @GetMapping("/user/{id}")
    public User user(@PathVariable("id") int id){
        return userService.findById(id);
    }

    @GetMapping("/users")
    public List<User> users(){
        return userService.list();
    }

    @PostMapping("/user")
    public String insertUser(User user){
        int row=userService.insert(user);
        return row>0?"SUCCESS":"FAILED";
    }

    @PutMapping("/user")
    public String updateUser(User user){
        int row=userService.update(user);
        return row>0?"SUCCESS":"FAILED";
    }
    @DeleteMapping("/user/{id}")
    public String deleteUser(@PathVariable("id") int id){
        return userService.delete(id)>0?"SUCCESS":"FAILED";
    }

}

修改配置

  • 在Spring 的Main方法上增加以下注解,用來掃描Mybatis的Mapper文件

    @MapperScan("com.example.demo.mapper")
    
  • 配置Mapper配置文件的地址,在application.yml中

    mybatis:
      mapper-locations: classpath:*Mapper.xml
    

    id int,

    name varchar(20),

    address varchar(20)

)

項目打包

  • mvn -Dmaven.test.skip -U clean install
  • java -jar xxx.jar

簡單總結

這個程式碼,我想,大家應該寫過無數遍了,而在基於Spring Boot集成Mybatis這個案例中,核心的業務邏輯並沒有減少,它只減少了一些繁瑣的配置,使得我們更聚焦在業務開發層面。

簡單來說,基於Spring Boot的項目中,我們只需要寫Controlelr、Service、Dao即可。甚至很多情況下我們dao都不需要管,比如使用mybatis-plus這個插件,就可以省去很多固定的dao層邏輯。

所以實際上,Spring Boot並沒有新鮮的東西,因此你看到市面上大部分講spring boot的書,這些書我幾乎都看過,基本上都是講解Spring Boot的應用,以及Spring Boot的一些特性分析。因為一旦你想講Spring Boot的原理,就必然會回歸到Spring這塊的內容上。比如小馬哥的Spring Boot編程思想著本書,大篇幅的都是在講Spring Framework。因為Spring Boot的內核還是Spring Framework。

Spring Boot與微服務

接下來,給大家講講spring boot與微服務這塊的內容。

什麼是Spring Cloud

首先,我們要簡單了解一下什麼是微服務,按照我的理解來說,微服務就是微粒度的服務,它是面向服務架構(SOA)的進一步優化。如果大家不是很好理解,翻譯成白話就是

一個業務系統,原本是在一個獨立的war包中。現在為了更好的維護和提高性能,把這個war包按照業務緯度拆分成了一個個獨立的業務子系統,每個子系統提供該業務領域相關的功能,並暴露API介面。

這些服務彼此之間進行數據交換和通訊來實現整個產品的功能。

而這些業務子系統,實際上代表的就是一個服務,那麼所謂的微服務,說的是這個服務的粒度。至於服務的粒度什麼樣才叫微,其實沒有一個固定的衡量標準。更多的還是在每個公司具體的業務粒度的把控上。

微服務化遇到的問題

在為服務化之後,會面臨很多的問題,比如服務註冊、服務路由、負載均衡、服務監控等等。這些問題都需要有相應的技術來解決,這個時候,Spring Cloud就出現了。

簡單來說,Spring Cloud 提供了一些可以讓開發者快速構建微服務應用的工具,比如配置管理、服務發現、熔斷、智慧路由等,這些服務可以在任何分散式環境下很好地工作。Spring Cloud 主要
致力於解決如下問題:

  • Distributed/versioned configuration,分散式及版本化配置。
  • Service registration and discovery,服務註冊與發現。
  • Routing,服務路由。
  • Service-to-service calls,服務調用。
  • Load balancing,負載均衡。
  • Circuit Breakers,斷路器。
  • Global locks,全局鎖。
  • Leadership election and cluster state,Leader 選舉及集群狀態。
  • Distributed messaging,分散式消息。

需要注意的是,Spring Cloud 並不是 Spring 團隊全新研發的框架,它只是把一些比較優秀的解決微服務架構中常見問題的開源框架基於 Spring Cloud 規範進行了整合,通過 Spring Boot 這個
框架進行再次封裝後屏蔽掉了複雜的配置,給開發者提供良好的開箱即用的微服務開發體驗。不難看出,Spring Cloud 其實就是一套規範,而 Spring Cloud Netflix、Spring Cloud Consul、Spring CloudAlibaba 才是 Spring Cloud 規範的實現。

為什麼Spring Cloud是基於Spring Boot

那為什麼Spring Cloud會採用Spring Boot來作為基礎框架呢?原因很簡單

  1. Spring Cloud它是關注服務治理領域的解決方案,而服務治理是依託於服務架構之上,所以它仍然需要一個承載框架
  2. Spring Boot 可以簡單認為它是一套快速配置Spring應用的腳手架,它可以快速開發單個微服務

在微服務架構下,微服務節點越來越多,需要一套成熟高效的腳手架,而Spring Boot正好可以滿足這樣的需求,如下圖所示。

image-20211124135348046

Spring Boot的四大核心機制

如果一定要基於Spring Boot的特性去說,那麼只能去說Spring Boot的四大核心機制,分別是@EnableAutoConfiguration 、 Starter開箱即用組件、Actuator應用監控、Spring Boot CLI 命令行工具。

EnableAutoConfiguration

Starter

告訴Spring Boot需要什麼功能,它就能引入需要的庫。

Actuator

讓你能夠深入運行中的Spring Boot應用程式

Spring Boot CLI

Spring Boot CLI 為Spring Cloud 提供了Spring Boot 命令行功能。我們可以通過編寫groovy腳本來運行Spring Cloud 組件應用程式。步驟如下、

  • 下載spring-boot-cli

    Spring Boot CLI://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.2.6.RELEASE/spring-boot-cli-2.2.6.RELEASE-bin.zip

  • 配置環境變數

  • 在控制台spring --version查看CLI版本

  • 使用CLI運行應用。我們可以使用run命令編譯和運行Groovy源程式碼。Spring Boot CLI中包含所有運行Groovy所需要的依賴。

  • 創建一個hello.groovy文件

    @RestController
    class HelloController {
    
    	@GetMapping("/hello")
    	String hello(){
    		return "Hello World";
    	}
    }
    
  • 在控制台執行spring run hello.groovy,如果需要傳遞參數,比如埠,和JVM參數類似

    spring run hello.groovy -- --server.port=9000
    

Spring Boot的四大核心特性

  • EnableAutoConfiguration

  • Starter

  • Actuator

  • Spring Boot CLI

    Spring Boot CLI 為Spring Cloud 提供了Spring Boot 命令行功能。我們可以通過編寫groovy腳本來運行Spring Cloud 組件應用程式。步驟如下、

    • 下載spring-boot-cli

      Spring Boot CLI://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.2.6.RELEASE/spring-boot-cli-2.2.6.RELEASE-bin.zip

    • 配置環境變數

    • 在控制台spring --version查看CLI版本

    • 使用CLI運行應用。我們可以使用run命令編譯和運行Groovy源程式碼。Spring Boot CLI中包含所有運行Groovy所需要的依賴。

    • 創建一個hello.groovy文件

      @RestController
      class HelloController {
      
      	@GetMapping("/hello")
      	String hello(){
      		return "Hello World";
      	}
      }
      
    • 在控制台執行spring run hello.groovy,如果需要傳遞參數,比如埠,和JVM參數類似

      spring run hello.groovy -- --server.port=9000
      

版權聲明:本部落格所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 Mic帶你學架構
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術乾貨!