微服務之 Spring Boot

Spring Boot 項目是 Spring 大家族的后起之秀, 它通過

  • 內(nèi)置的 Tomcat/Jetty, 可以創(chuàng)建獨立的Spring 程序, 無需依賴于 web 容器

  • 自動配置, 封裝一些 starter 依賴庫, 大大簡化了以往繁瑣的依賴配置, 無需代碼生成,無需編寫一行 xml 配置文件

  • 內(nèi)置了產(chǎn)品級的度量和健康檢查工具 actuator, 可用 yaml/properties 進行靈活的配置

Spring Boot 極大地降低了 Java Web Application 的開發(fā)成本, 提高了生產(chǎn)率, 沒那么繁瑣, 沒那么麻煩.
使用 Spring Boot 可以輕松地創(chuàng)建不依賴Web容器的, 具有產(chǎn)品級水準的基于Spring的應用程序, 它做了大量的封裝和接口的簡化, 提倡用更少的配置, 和更少代碼來創(chuàng)建Java Web 應用程序, 大大減輕了 Java 程序員的負擔.

Spring Boot 主要特點

  • 可以方便快捷地創(chuàng)建獨立的 Spring 應用程序
  • 直接嵌入Tomcat,Jetty或Undertow(無需部署WAR文件)
  • 提供預定義的初始POM以簡化您的Maven配置
  • 盡可能地自動配置 Spring, 約定優(yōu)于配置, 配置高于約定
  • 提供產(chǎn)線就緒的功能,如指標,健康檢查和外部化配置
  • 沒有代碼生成和無需XML配置

Spring Boot 還附帶了一個命令行工具,如果你想快速使用Spring原型,可以使用它來運行Groovy腳本,Groovy有著類似Java的語法,卻無需那么多的膠水代碼。

快速上手

Spring Boot 有一個用來快速生成應用骨架的頁面

https://start.spring.io/

image.png

選中所需的依賴庫, 會生成一個壓縮文件, 解開以后就是一個簡單的 Spring Boot 項目

java -jar target/hellospringboot-0.0.1-SNAPSHOT.jar --spring.profiles.active=production

或者也可以用命令行工具來生成, 例如在我的 macbook 上可以用 brew 來安裝 springboot 命令行工具

brew tap pivotal/tap
brew install springboot
spring init -n hellospringboot -a hellospringboot -g com.github.walterfan -d=web,jpa,thymeleaf,mysql hellospringboot

在其他 linux 系統(tǒng)上可通過 sdkman 來安裝, windows 上建議通過 vagrant 安裝一個 ubuntu 虛擬機
方法如下:

$ curl -s "https://get.sdkman.io" | bash

#另開一個新的終端

$ source ~/.sdkman/bin/sdkman-init.sh

$ sdk install springboot

$ spring init --java-version=1.8 --dependencies=web,data-jpa,thymeleaf,h2,security -packaging=jar --groupId=com.github.walterfan --artifactId=potato 

構建工具可以選擇 Gradle 或傳統(tǒng)的 maven

也可以使用 https://start.spring.io, 選取你所需要的子模塊, 生成一個項目骨架

假設項目命名為 potato 土豆, 保存為 'potato.zip'

解開壓縮包

$ unzip potato.zip -d server

$ cd server

$ tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── github
    │   │           └── walterfan
    │   │               └── potato
    │   │                   └── DemoApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── github
                    └── walterfan
                        └── potato
                            └── DemoApplicationTests.java

讓我們添加一個 controller

package com.github.walterfan.potato.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Date;

@Controller
public class DemoController {

    @RequestMapping(path={"/"})
    public String welcome(@RequestParam(defaultValue = "Walter") String name, Model model) {
        model.addAttribute("message", name + ",  welcome to potato application at " + new Date());
        return "welcome";
    }

再添加一個頁面 src/main/resources/template/welcome.html

<!DOCTYPE HTML>
<html>
<head>
    <title>Show me the codes, buddy</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <style>
    pre {
        color: darkblue;
        white-space: pre-wrap;
        background: lightgray;
    }

    </style>
</head>
<body>
    <div>Show me the codes,  <p th:text="'Hi ' + ${message} + '!'" /> </div>
</body>
</html>

打開瀏覽器看一下

$ ./gradlew build
$ ./gradlew bootRun

打開網(wǎng)址 http://localhost/index?name=walter

顯示:

Show me the codes,
Hi Walter, welcome to potato application at Fri Feb 01 20:17:00 CST 2019!

Spring Boot 的三大亮點

下面我們扼要說明 Spring Boot 的三大亮點

  1. starter
  2. autoconfiguration
  3. acturator

1) Starter

以前基于 Spring 框架的Java 項目, 有個令人頭疼的依賴管理問題, 一是冗長的 pom.xml , 二是所引入的類之間可能存在臭名昭著的依賴黑洞問題, 各個依賴庫版本可不匹配, 各個庫所依賴的庫也可能有沖突, 程序員不得不用 dependency tree 細細察看, 手工排除有沖突的庫.

為解決此類問題, Spring Boot 提供了若干 spring-boot-starter 類, 大多數(shù)類也就是一個 pom.xml, 定義了一組功能相關的依賴模塊, 包含了所需依賴庫, 你導入它就行了, 而不必一個個導入并指定版本.

在 spring-boot-project 中的 spring-boot-starters 模塊包含了若干 spring-boot-starter-xxx 子模塊, 核心子模塊為 spring-boot-starter

以 spring-boot-starter-actuator 模塊為例, 主要就是一個 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starters</artifactId>
        <version>${revision}</version>
    </parent>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <name>Spring Boot Actuator Starter</name>
    <description>Starter for using Spring Boot's Actuator which provides production
        ready features to help you monitor and manage your application</description>
    <properties>
        <main.basedir>${basedir}/../../..</main.basedir>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-core</artifactId>
        </dependency>
    </dependencies>
</project>

2) 自動配置

Spring Boot 號稱開箱即用, 不用繁瑣的 XML 配置文件, 也不用 Java Config 文件, 其秘訣在于自動配置, 也就是說 JavaConfig 文件其實還是需要的, 不過它們是通過 classloader 和反射根據(jù)某些條件自動創(chuàng)建出來的.

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 {
    //...
    }

這里的關鍵是 @EnableAutoConfiguration:

  1. SpringApplication 會在 classpath 中搜索所有 META-INF/spring.factories 配置文件, 然后將其中的 org.springframework.boot.autoconfigure.EnableAuthConfiguration 的 key 對應的配置項加載到 Spring 容器中
  2. 只有 spring.boot.enableautoconfiguration 為 true(默認值) 時, 才啟用自動配置
  3. EnableAuthConfiguration 在自動配置時也可以用 exclude 來排除某些自動配置, 例如
@Configuration

@EnableAutoConfiguration(exclude={DataSourceAutoCofiguration.class})

public class WebAppConfig {

}

在配置文件中, spring.autoconfigure.exclude 屬性可以達到相同效果.

基于項目所依賴的 Jar 包進行自動配置, 例如在 classpath 中發(fā)現(xiàn)有 h2 的 jar 包,
并且也沒有手動配置任何的數(shù)據(jù)庫連接, Spring Boot 就會自動配置一個 h2 的內(nèi)存數(shù)據(jù)庫

自動配置是非侵略性的, 如果已經(jīng)有 DataSource 的手動配置, 自動配置便不會生效

在啟動應用時添加 --debug 選項, 可以看到哪些自動配置被應用了, 自動配置背后的魔法就是使用了 @ConditionalOnClass, @ConditionalOnMissingBean, ConditionalOnProperty 等這一類的 注解, 意為當某種條件成立或不成立時來應用一些配置或創(chuàng)建某些 Bean.

DataSource 自動配置剖析

在 spring-boot-configuration 項目中有一個 spring.factories 文件, 其中定義了若干自動配置類, 其中有一個 DataSourceAutoConfiguration 類, 這個類又 import 了 EmbeddedDataSourceConfiguration 類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
...

在 EmbeddedDataSourceConfiguration 配置類中定義了dataSource bean 為由 EmbeddedDatabaseBuilder 構建的 EmbeddedDatabase

@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {

    private EmbeddedDatabase database;

    private ClassLoader classLoader;

    private final DataSourceProperties properties;

    public EmbeddedDataSourceConfiguration(DataSourceProperties properties) {
        this.properties = properties;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Bean
    public EmbeddedDatabase dataSource() {
        this.database = new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseConnection.get(this.classLoader).getType())
                .setName(this.properties.determineDatabaseName()).build();
        return this.database;
    }

    @PreDestroy
    public void close() {
        if (this.database != null) {
            this.database.shutdown();
        }
    }

}

而上述 EmbeddedDatabaseConnection 類的靜態(tài) get 方法中遍歷其定義的類型 H2, DERBY, HSQL, 使用 ClassUtils.isPresent(driverClass) 在 classpath 中尋找相關 class, 如果發(fā)現(xiàn) org.h2.Driver, 則返回 H2 這個 EmbeddedDatabaseConnection, 從而創(chuàng)建 H2 這個EmbeddedDatabase 為 DataSource

public enum EmbeddedDatabaseConnection {

        /**
     * No Connection.
     */
    NONE(null, null, null),

    /**
     * H2 Database Connection.
     */
    H2(EmbeddedDatabaseType.H2, "org.h2.Driver",
            "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"),

    /**
     * Derby Database Connection.
     */
    DERBY(EmbeddedDatabaseType.DERBY, "org.apache.derby.jdbc.EmbeddedDriver",
            "jdbc:derby:memory:%s;create=true"),

    /**
     * HSQL Database Connection.
     */
    HSQL(EmbeddedDatabaseType.HSQL, "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s");

    private final EmbeddedDatabaseType type;

    private final String driverClass;

    private final String url;

    EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass,
            String url) {
        this.type = type;
        this.driverClass = driverClass;
        this.url = url;
    }

        //...
       public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
        for (EmbeddedDatabaseConnection candidate : 
                     EmbeddedDatabaseConnection.values()) {
            if (candidate != NONE && 
                             ClassUtils.isPresent(candidate.getDriverClassName(),
                    classLoader)) {
                return candidate;
            }
        }
        return NONE;
    }

總結(jié)一下:

    1. SpringApplication 的 run 方法會調(diào)用 SpringFactoriesLoader 的 loadSpringFactories 方法
    1. SpringFactoriesLoader.loadSpringFactories 方法會讀取 "META-INF/spring.factories"
    1. 在 spring.factories 中定義了由 org.springframework.boot.autoconfigure.EnableAutoConfiguration 為鍵值對應的若干 Configuration 類, 其中就有 DataSourceAutoConfiguration
    1. DataSourceAutoConfiguration 導入了 EmbeddedDataSourceConfiguration
    1. EmbeddedDataSourceConfiguration 中在classpath 中尋找相關 driver(org.hsqldb.jdbcDriver) 類并創(chuàng)建對應的 EmbeddedDatabaseConnection

3) Actuator

寫一個例子, 做一個原型, 與開發(fā)一個真正的產(chǎn)品區(qū)別不亞于搭帳篷與蓋房子, 一個帳篷可以暫且棲身, 可是不耐風寒, 一幢房子才可以安家, 一個真正的產(chǎn)品需要產(chǎn)品級的監(jiān)控, Spring Boot Actutor 是Spring Boot 的一個重要的子模塊, 它可以提供用于生產(chǎn)環(huán)境的監(jiān)視和管理功能, 可選擇使用HTTP端點或JMX來管理和監(jiān)視你的服務。 還可將審核,運行狀況和指標收集功能應用于你的服務。

Actuator 是一個制造業(yè)的術語, 可翻譯為驅(qū)動器, 一個可以驅(qū)動設備自動運行某些操作的裝置

把 spring-boot-starter-actuator 加入你的依賴庫即可開箱即用, 由于有些端點的數(shù)據(jù)比較敏感, 所以我們也加入 spring-boot-starter-security

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
</dependencies>
  1. JMX 端點
    啟動你的 Spring Boot 應用程序, 打開 jconsole 通過JMX 連接, 如下所示

通過 actuator 端點,可以監(jiān)控應用程序并與之交互。 Spring Boot包含許多內(nèi)置端點,你也可以添加自己的端點, 或通過配置啟用或禁用每個端點, 以 JMX或HTTP 來公開你的端點。

  1. HTTP 端點
    http://localhost:8080/actuator, HTTP 的默認端點只有 health 和 info

調(diào)整 application.properties 配置如下

logging.level.org.springframework: DEBUG

spring.security.user.name=admin
spring.security.user.password=pass1234
spring.security.user.roles=USER

management.endpoints.web.exposure.include=*
management.endpont.shutdown.enabled=true
management.endpont.health.show-details=when_authorized

則可以看到所有的 HTTP Actuator 端點

{
  "self": {
    "href": "http://localhost:8080/actuator",
    "templated": false
  },
  "auditevents": {
    "href": "http://localhost:8080/actuator/auditevents",
    "templated": false
  },
  "beans": {
    "href": "http://localhost:8080/actuator/beans",
    "templated": false
  },
  "caches-cache": {
    "href": "http://localhost:8080/actuator/caches/{cache}",
    "templated": true
  },
  "caches": {
    "href": "http://localhost:8080/actuator/caches",
    "templated": false
  },
  "health-component": {
    "href": "http://localhost:8080/actuator/health/{component}",
    "templated": true
  },
  "health": {
    "href": "http://localhost:8080/actuator/health",
    "templated": false
  },
  "health-component-instance": {
    "href": "http://localhost:8080/actuator/health/{component}/{instance}",
    "templated": true
  },
  "conditions": {
    "href": "http://localhost:8080/actuator/conditions",
    "templated": false
  },
  "configprops": {
    "href": "http://localhost:8080/actuator/configprops",
    "templated": false
  },
  "env": {
    "href": "http://localhost:8080/actuator/env",
    "templated": false
  },
  "env-toMatch": {
    "href": "http://localhost:8080/actuator/env/{toMatch}",
    "templated": true
  },
  "info": {
    "href": "http://localhost:8080/actuator/info",
    "templated": false
  },
  "loggers-name": {
    "href": "http://localhost:8080/actuator/loggers/{name}",
    "templated": true
  },
  "loggers": {
    "href": "http://localhost:8080/actuator/loggers",
    "templated": false
  },
  "heapdump": {
    "href": "http://localhost:8080/actuator/heapdump",
    "templated": false
  },
  "threaddump": {
    "href": "http://localhost:8080/actuator/threaddump",
    "templated": false
  },
  "metrics-requiredMetricName": {
    "href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
    "templated": true
  },
  "metrics": {
    "href": "http://localhost:8080/actuator/metrics",
    "templated": false
  },
  "scheduledtasks": {
    "href": "http://localhost:8080/actuator/scheduledtasks",
    "templated": false
  },
  "httptrace": {
    "href": "http://localhost:8080/actuator/httptrace",
    "templated": false
  },
  "mappings": {
    "href": "http://localhost:8080/actuator/mappings",
    "templated": false
  }
}

看看 http://localhost:8080/actuator/metrics, 如下所示, 有這么多內(nèi)置的 metrics 條目

{
  "names": [
    "jvm.memory.max",
    "jvm.threads.states",
    "http.server.requests",
    "jdbc.connections.active",
    "process.files.max",
    "jvm.gc.memory.promoted",
    "system.load.average.1m",
    "jvm.memory.used",
    "jvm.gc.max.data.size",
    "jdbc.connections.max",
    "jdbc.connections.min",
    "jvm.gc.pause",
    "jvm.memory.committed",
    "system.cpu.count",
    "logback.events",
    "tomcat.global.sent",
    "jvm.buffer.memory.used",
    "tomcat.sessions.created",
    "jvm.threads.daemon",
    "system.cpu.usage",
    "jvm.gc.memory.allocated",
    "tomcat.global.request.max",
    "hikaricp.connections.idle",
    "hikaricp.connections.pending",
    "tomcat.global.request",
    "tomcat.sessions.expired",
    "hikaricp.connections",
    "jvm.threads.live",
    "jvm.threads.peak",
    "tomcat.global.received",
    "hikaricp.connections.active",
    "hikaricp.connections.creation",
    "process.uptime",
    "tomcat.sessions.rejected",
    "process.cpu.usage",
    "tomcat.threads.config.max",
    "jvm.classes.loaded",
    "hikaricp.connections.max",
    "hikaricp.connections.min",
    "jvm.classes.unloaded",
    "tomcat.global.error",
    "tomcat.sessions.active.current",
    "tomcat.sessions.alive.max",
    "jvm.gc.live.data.size",
    "hikaricp.connections.usage",
    "tomcat.threads.current",
    "hikaricp.connections.timeout",
    "process.files.open",
    "jvm.buffer.count",
    "jvm.buffer.total.capacity",
    "tomcat.sessions.active.max",
    "hikaricp.connections.acquire",
    "tomcat.threads.busy",
    "process.start.time"
  ]
}

打開 http://localhost:8080/actuator/metrics/jvm.memory.used 可以看到 jvm 所使用的內(nèi)存如下所示:


{
  "name": "jvm.memory.used",
  "description": "The amount of used memory",
  "baseUnit": "bytes",
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 301460880
    }
  ],
  "availableTags": [
    {
      "tag": "area",
      "values": [
        "heap",
        "nonheap"
      ]
    },
    {
      "tag": "id",
      "values": [
        "Compressed Class Space",
        "PS Survivor Space",
        "PS Old Gen",
        "Metaspace",
        "PS Eden Space",
        "Code Cache"
      ]
    }
  ]
}

參考資料

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容