微服务之 Spring Boot

Spring Boot 项目是 Spring 大家族的后起之秀, 它通过

  • 内置的 Tomcat/Jetty, 可以创建独立的Spring 程序, 无需依赖于 web 容器

  • 自动配置, 封装一些 starter 依赖库, 大大简化了以往繁琐的依赖配置, 无需代码生成,无需编写一行 xml 配置文件

  • 内置了产品级的度量和健康检查工具 actuator, 可用 yaml/properties 进行灵活的配置

Spring Boot 极大地降低了 Java Web Application 的开发成本, 提高了生产率, 没那么繁琐, 没那么麻烦.
使用 Spring Boot 可以轻松地创建不依赖Web容器的, 具有产品级水准的基于Spring的应用程序, 它做了大量的封装和接口的简化, 提倡用更少的配置, 和更少代码来创建Java Web 应用程序, 大大减轻了 Java 程序员的负担.

Spring Boot 主要特点

  • 可以方便快捷地创建独立的 Spring 应用程序
  • 直接嵌入Tomcat,Jetty或Undertow(无需部署WAR文件)
  • 提供预定义的初始POM以简化您的Maven配置
  • 尽可能地自动配置 Spring, 约定优于配置, 配置高于约定
  • 提供产线就绪的功能,如指标,健康检查和外部化配置
  • 没有代码生成和无需XML配置

Spring Boot 还附带了一个命令行工具,如果你想快速使用Spring原型,可以使用它来运行Groovy脚本,Groovy有着类似Java的语法,却无需那么多的胶水代码。

快速上手

Spring Boot 有一个用来快速生成应用骨架的页面

https://start.spring.io/

1598924-a9001999dd6b1236.png
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 系统上可通过 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 或传统的 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

打开网址 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 类, 大多数类也就是一个 pom.xml, 定义了一组功能相关的依赖模块, 包含了所需依赖库, 你导入它就行了, 而不必一个个导入并指定版本.

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

1598924-e40019fdc35abec5.png

以 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 和反射根据某些条件自动创建出来的.

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 中发现有 h2 的 jar 包,
并且也没有手动配置任何的数据库连接, Spring Boot 就会自动配置一个 h2 的内存数据库

自动配置是非侵略性的, 如果已经有 DataSource 的手动配置, 自动配置便不会生效

在启动应用时添加 --debug 选项, 可以看到哪些自动配置被应用了, 自动配置背后的魔法就是使用了 @ConditionalOnClass, @ConditionalOnMissingBean, ConditionalOnProperty 等这一类的 注解, 意为当某种条件成立或不成立时来应用一些配置或创建某些 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 类的静态 get 方法中遍历其定义的类型 H2, DERBY, HSQL, 使用 ClassUtils.isPresent(driverClass) 在 classpath 中寻找相关 class, 如果发现 org.h2.Driver, 则返回 H2 这个 EmbeddedDatabaseConnection, 从而创建 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;
    }

总结一下:

    1. SpringApplication 的 run 方法会调用 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) 类并创建对应的 EmbeddedDatabaseConnection

3) Actuator

写一个例子, 做一个原型, 与开发一个真正的产品区别不亚于搭帐篷与盖房子, 一个帐篷可以暂且栖身, 可是不耐风寒, 一幢房子才可以安家, 一个真正的产品需要产品级的监控, Spring Boot Actutor 是Spring Boot 的一个重要的子模块, 它可以提供用于生产环境的监视和管理功能, 可选择使用HTTP端点或JMX来管理和监视你的服务。 还可将审核,运行状况和指标收集功能应用于你的服务。

Actuator 是一个制造业的术语, 可翻译为驱动器, 一个可以驱动设备自动运行某些操作的装置

把 spring-boot-starter-actuator 加入你的依赖库即可开箱即用, 由于有些端点的数据比较敏感, 所以我们也加入 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 连接, 如下所示
1598924-910c9e2800a0e5c4.png

通过 actuator 端点,可以监控应用程序并与之交互。 Spring Boot包含许多内置端点,你也可以添加自己的端点, 或通过配置启用或禁用每个端点, 以 JMX或HTTP 来公开你的端点。

  1. HTTP 端点
    http://localhost:8080/actuator, HTTP 的默认端点只有 health 和 info
1598924-ab823ba2c836a2b5.png

调整 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, 如下所示, 有这么多内置的 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 所使用的内存如下所示:


{
  "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"
      ]
    }
  ]
}

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值