深刻学习微框架:Spring Boot

Spring Boot html

是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员再也不须要定义样板化的配置。经过这种方式,Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。java

多年以来,Spring IO平台饱受非议的一点就是大量的XML配置以及复杂的依赖管理。在去年的SpringOne 2GX会议上,Pivotal的CTO Adrian Colyer回应了这些批评,而且特别提到该平台未来的目标之一就是实现免XML配置的开发体验。Boot所实现的功能超出了这个任务的描述,开发人员不只再也不须要编写XML,并且在一些场景中甚至不须要编写繁琐的import语句。在对外公开的beta版本刚刚发布之时,Boot描述了如何使用该框架在140个字符内实现可运行的web应用,从而得到了极大的关注度,该样例发表在tweet上。mysql

然而,Spring Boot并非要成为Spring IO平台里面众多“Foundation”层项目的替代者。Spring Boot的目标不在于为已解决的问题域提供新的解决方案,而是为平台带来另外一种开发体验,从而简化对这些已有技术的使用。对于已经熟悉Spring生态系统的开发人员来讲,Boot是一个很理想的选择,不过对于采用Spring技术的新人来讲,Boot提供一种更简洁的方式来使用这些技术。git

 

在追求开发体验的提高方面,Spring Boot,甚至能够说整个Spring生态系统都使用到了Groovy编程语言。Boot所提供的众多便捷功能,都是借助于Groovy强大的MetaObject协议、可插拔的AST转换过程以及内置的依赖解决方案引擎所实现的。在其核心的编译模型之中,Boot使用Groovy来构建工程文件,因此它可使用通用的导入和样板方法(如类的main方法)对类所生成的字节码进行装饰(decorate)。这样使用Boot编写的应用就能保持很是简洁,却依然能够提供众多的功能。spring

安装Boot

从最根本上来说,Spring Boot就是一些库的集合,它可以被任意项目的构建系统所使用。简便起见,该框架也提供了命令行界面,它能够用来运行和测试Boot应用。框架的发布版本,包括集成的CLI(命令行界面),能够在Spring仓库中手动下载和安装。一种更为简便的方式是使用Groovy环境管理器(Groovy enVironment Manager,GVM),它会处理Boot版本的安装和管理。Boot及其CLI能够经过GVM的命令行gvm install springboot进行安装。在OS X上安装Boot可使用Homebrew包管理器。为了完成安装,首先要使用brew tap pivotal/tap切换到Pivotal仓库中,而后执行brew install springboot命令。sql

要进行打包和分发的工程会依赖于像MavenGradle这样的构建系统。为了简化依赖图,Boot的功能是模块化的,经过导入Boot所谓的“starter”模块,能够将许多的依赖添加到工程之中。为了更容易地管理依赖版本和使用默认配置,框架提供了一个parent POM,工程能够继承它。Spring Boot工程的样例POM文件定义如程序清单1所示。mongodb

程序清单1数据库

 
<?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>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.0.0.RC1</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

为了实现更为简单的构建配置,开发人员可使用Gradle构建系统中简洁的Groovy DSL,如程序清单1.1所示。

程序清单1.1

buildscript {
  repositories {
    maven { url "http://repo.spring.io/libs-snapshot" }
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
  }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

repositories {
  mavenCentral()
  maven { url "http://repo.spring.io/libs-snapshot"  }
}

dependencies {
  compile 'org.springframework.boot:spring-boot-starter-actuator:1.0.0.RC1'
}

为了快速地搭建和运行Boot工程,Pivotal提供了称之为“Spring Initializr” 的web界面,用于下载预先定义好的Maven或Gradle构建配置。咱们也可使用Lazybones模板实现快速起步,在执行lazybones create spring-boot-actuator my-app命令后,它会为Boot应用建立必要的工程结构以及gradle构建文件。

开发Spring Boot应用

Spring Boot在刚刚公开宣布以后就将一个样例发布到了Twitter上,它目前成为了最流行的一个应用样例。它的所有描述如程序清单1.2所示,一个很是简单的Groovy文件能够生成功能强大的以Spring为后端的web应用。

程序清单1.2

@RestController
class App {
  @RequestMapping("/")
  String home() {
    "hello"
  }
}

这个应用能够经过spring run App.groovy命令在Spring Boot CLI中运行。Boot会分析文件并根据各类“编译器自动配置(compiler auto-configuration)”标示符来肯定其意图是生成Web应用。而后,它会在一个嵌入式的Tomcat中启动Spring应用上下文,而且使用默认的8080端口。打开浏览器并导航到给定的URL,随后将会加载一个页面并展示简单的文本响应:“hello”。提供默认应用上下文以及嵌入式容器的这些过程,可以让开发人员更加关注于开发应用以及业务逻辑,从而不用再关心繁琐的样板式配置。

Boot可以自动肯定类所需的功能,这一点使其成为了强大的快速应用开发工具。当应用在Boot CLI中执行时,它们在使用内部的Groovy编译器进行构建,这个编译器能够在字节码生成的时候以编码的方式探查并修改类。经过这种方式,使用CLI的开发人员不只能够省去定义默认配置,在必定程度上甚至能够不用定义特定的导入语句,它们能够在编译的过程当中识别出来并自动进行添加。除此以外,当应用在CLI中运行时,Groovy内置的依赖管理器,“Grape”,将会解析编译期和运行时的类路径依赖,与Boot编译器的自动配置机制相似。这种方式不只使得框架更加对用户友好,并且可以让不一样版本的Spring Boot与特定版本的来自于Spring IO平台的库相匹配,这样一来开发人员就不用关心如何管理复杂的依赖图和版本结构了。另外,它还有助于快速原型的开发并生成概念原型的工程代码。

对于不是使用CLI构建的工程,Boot提供了许多的“starter”模块,它们定义了一组依赖,这些依赖可以添加到构建系统之中,从而解析框架及其父平台所需的特定类库。例如,spring-boot-starter-actuator依赖会引入一组基本的Spring项目,从而实现应用的快速配置和即时可用。关于这种依赖,值得强调的一点就是当开发Web应用,尤为是RESTful Web服务的时候,若是包含了spring-boot-starter-web依赖,它就会为你提供启动嵌入式Tomcat容器的自动化配置,而且提供对微服务应用有价值的端点信息,如服务器信息、应用指标(metrics)以及环境详情。除此以外,若是引入spring-boot-starter-security模块的话,actuator会自动配置Spring Security,从而为应用提供基本的认证以及其余高级的安全特性。它还会为应用结构引入一个内部的审计框架,这个框架能够用来生成报告或其余的用途,好比开发认证失败的锁定策略。

为了阐述在Java Maven工程中,如何快速地使Spring Web工程准备就绪,考虑一下程序清单1.3中的应用程序代码。

程序清单1.3

package com.infoq.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello";
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Application类上的@EnableAutoConfiguration注解会告知Boot要采用一种特定的方式来对应用进行配置。这种方法会将其余样板式的配置均假设为框架默认的约定,所以可以聚焦于如何尽快地使应用准备就绪以便运行起来。Application类是可运行的,所以,当咱们以Java Application的方式运行这个类时,就能启动该应用及其嵌入式的容器,这样也能实现即时地开发。

为了发布版本而构建工程时,Boot的Maven和Gradle插件能够嵌入(hook)到这些构建系统的打包过程当中,以生成可执行的“胖jar包(fat jar)”,这种jar包含了工程的全部依赖而且可以以可运行jar的方式执行。使用Maven打包Boot应用只需运行mvn package命令,与之相似,使用Gradle时,执行gradle build命令将会在构建的目标地址下生成可运行的jar。

开发微服务

Boot对Spring应用的开发进行了简化,提供了模块化方式导入依赖的能力,强调了开发RESTful Web服务的功能并提供了生成可运行jar的能力,这一切都清晰地代表在开发可部署的微服务方面Boot框架是一个强大的工具。正如前面的例子所示,借助于Boot,让一个RESTful Web工程运行起来是一件很容易的事情;不过,为了了解Boot全部潜在的功能,咱们会阐述在开发完整功能的微服务时,会遇到的全部繁琐的事情。在企业级基础设施领域,微服务是一种愈来愈流行的应用架构,由于它可以实现快速开发、更小的代码库、企业级集成以及模块化部署。有众多的框架致力于该领域的开发,该章节将会讨论使用Boot如何简化这一过程。

数据访问

咱们能够基于各类目的来构建微服务,但有一点是确定的,那就是大多数都须要读取和写入数据库的能力。Spring Boot使数据库集成变成了一项很是简单的任务,由于它具备自动配置Spring Data以访问数据库的能力。只需在你的工程中将spring-boot-starter-data-jpa包含进来,Boot的自动配置引擎就能探测到你的工程须要数据访问功能,而且会在Spring应用上下文中建立必要的Bean,这样你就可使用Repository和服务了。为了更具体地阐述这一点,请参见程序清单1.4中的Gradle构建文件,它列出了一个基于Groovy的微服务web应用的构建结构,该应用使用了Spring Data对JPA的支持来实现数据访问。

程序清单1.4

buildscript {
  repositories {
    maven { url "http://repo.spring.io/libs-snapshot" }
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
  }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

repositories {
  mavenCentral()
  maven { url "http://repo.spring.io/libs-snapshot"  }
}

ext {
  springBootVersion = "1.0.0.RC1"
}

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.2.1'
  compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

在这个配置中,Boot的actuator模块提供了对hsqldb的依赖,这会搭建全部必要的依赖——包括模式的建立——所以Spring Data可使用这个内存数据库做为数据源。这种简便的方式可以让开发人员免于在开发期建立和管理复杂的XML配置,进而可以快速地开发数据库驱动的微服务。若是在classpath中有H2或Derby数据库的话,这种自动化配置也会生效。Boot所提供的另外一个便利之处就是可以快速简便地使用相关数据启动应用的数据库模式。这在开发期是很是有用的,此时数据库多是在内存中或者是不稳定的,开发人员须要保证的是在应用启动的时候可以访问到这些特定的数据。为了阐述这一点,考虑一下程序清单1.5中的示例JPA实体,它表明了微服务所提供的“User”数据结构。

程序清单1.5

@Entity
class User {
  @Id
  @GeneratedValue
  Long id

  String username
  String firstName
  String lastName
  Date createdDate
  Date lastAccessed

  Boolean isActive = Boolean.TRUE
}

为了启用表明User对象的通用数据,咱们只需建立一个名为schema.sqldata.sql的文件,并将其包含在classpath之中。这个文件会在模式建立完成以后执行,因此基于程序清单1.5所给出的实体,咱们可使用SQL语句启用一个用户帐号,如程序清单1.6所示。

程序清单1.6

insert into user(username, first_name, last_name, created_date) values ('danveloper', 'Dan', 'Woods', now())

在启动的时候,咱们所提供的SQL代码会执行,这样就能确保有一个测试帐号可使用。微服务此时已经具备了数据访问的起始点,程序清单1.7展示了如何按照Spring Data的开发模式建立Repository接口,该接口会做为User实体的数据访问对象(Data Access Object)。

程序清单1.7

public interface UserRepository extends CrudRepository<User, Long> {
}

CrudRepository提供了一些通用的接口方法来建立、查询、更新以及删除对象和对象集合。应用所需的其余特定功能能够按照Spring Data的Repository开发约定进行定义。一旦UserRepository接口建立成功,Boot的spring-data-jpa层会在工程中探测到它,并将其添加到Spring应用上下文之中,这样对于controller和sevice对象来讲,它就成为能够进行自动注入的可选对象。这种自动化的配置只有在Boot应用要求按照这种方式初始化的时候才生效,这是经过存在@EnableAutoConfiguration注解来标识的。借助程序清单1.8中所实现的controller,微服务如今就能够定义RESTful端点了,服务的使用者能够获取到User的列表或单个User。

程序清单1.8

@RestController
@EnableAutoConfiguration
@RequestMapping("/user")
class UserController {

  @Autowired
  UserRepository repository

  @RequestMapping(method=[RequestMethod.GET])
  def get(Long id) {
    id ? repository.findOne(id) : repository.findAll()
  }

  public static void main(String[] args) {
    SpringApplication.run UserController, args
  }
}

在启动的时候,应用将会输出日志,代表Hibernate按照User实体的定义建立数据库结构,在应用初始化的最后,Boot还会从schema.sql文件中导入数据。

在开发微服务应用时,须要特别注意的一点是使用了@RequestMapping注解。这不是Boot特定的注解。不过,由于Boot安装了本身的端点以监控应用的性能、健康状况以及配置,因此须要确保应用的代码不要与这些内置的提供详情的路径解析相冲突。鉴于此,若是有从请求路径中解析属性的需求(在咱们的场景中,也就是user的id属性),那么咱们须要仔细考虑这个动态的属性解析会对微服务的其余行为产生什么影响。在本例中,只是简单地将controller映射到/user端点而不是根上下文,就能容许Boot的端点也能够进行访问。

微服务所提供的数据并不必定所有适合关系型结构,针对这一点Spring Boot也暴露了一些模块,从而让开发人员可使用Spring Data的MongoDB和Redis项目,不过依然采起特定的方式来进行配置。Spring Data用来定义数据访问对象(Data Access Object)的高层框架,这样快速切换JPA与非JPA数据源会变得很是容易。参见程序清单1.9,它展示了一个从新定义的UserRepository接口,这个接口设计为使用MongoDB取代JPA。

程序清单1.9

public interface UserRepository extends MongoRepository<User, Long> {
}

MongoRepository接口也扩展了CrudRepository,所以微服务的Controller代码,也就是程序清单1.8所示并不须要修改。为了实现与MongoDB的集成,工程惟一要作的就是在应用的classpath中包含spring-boot-starter-data-mongodb。程序清单1.4所示的Gradle构建文件须要稍微调整一下依赖的部分,如程序清单1.10所示。

程序清单1.10

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.2.1'
  compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

MongoDB依赖都置于classpath之中之后,Boot将会自动配置Spring Data链接到localhost上的数据库,而且默认的数据库名为test。在这个库中,将会自动建立User集合(按照MongoDB的标准),微服务如今就能使用MongoDB做为后端了。对非JPA的数据存储来讲,初始化数据比其余的方式更为简单,这主要是由于它不能针对MongoDB的文档存储和Redis的键值存储运行SQL文件。鉴于Spring Data会使用这些存储的持久化实例,这就意味着开发期建立的数据须要在重启后保留。为了持久化数据,咱们须要修改微服务的controller,这样服务的使用者就能建立User实例了。咱们也能够将微服务的UserController进行修改,使其符合通用的RESTful API结构,让controller以不一样的方式处理不一样的HTTP方法。程序清单1.11展示了为controller添加建立新User实例的功能。

程序清单1.11

@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {

  @Autowired
  UserRepository repository

  @RequestMapping(method=[RequestMethod.GET])
  def get(Long id) {
    id ? repository.findOne(id) : repository.findAll()
  }

  @RequestMapping(method=[RequestMethod.POST])
  def create(@RequestBody User user) {
    repository.save user
    user
  }

  public static void main(String[] args) {
    SpringApplication.run UserController, args
  }
}

当微服务的使用者往应用的端点上发送一个HTTP POST请求时,Spring将会把请求体转换为User实例。代码接下来会使用UserRepository将这个对象存储到MongoDB集合之中。使用curl建立User实例的样例如程序清单1.12所示。

程序清单1.12

curl -v -H "Content-Type: application/json" -d "{ \"username\": \"danveloper\", \"firstName\": \"Dan\", \"lastName\": \"Woods\", \"createdDate\": \"2014-02-02T00:00:00\" }" http://localhost:8080/user

按照Boot针对Mongo数据源的特定配置,新的User实例默认会持久化到本地Mongo实例的test数据库的user集合之中。若是咱们打开web浏览器并对微服务发起一个HTTP GET请求,咱们就能看到所建立的user存在于返回的列表之中。

配置

咱们能够很快地重写Spring Boot的默认配置。默认状况下,应用的配置可使用Java属性文件来进行定义,这个文件名为application.properties而且位于应用的classpath根目录下。不过,一种更好的方式是使用 YAML配置,它提供告终构化以及嵌套的配置。在应用的运行时类路径之中包含snakeyaml以后,你的工程就能够在application.yml文件中直接定义配置了。为了详述这一点,考虑程序清单1.13的示例YAML配置,这里列出了应用的嵌入式HTTP服务器(默认是Tomcat,也可选择Jetty)的各类设置项。

程序清单1.13

# Server settings (ServerProperties)
server:
  port: 8080
  address: 127.0.0.1
  sessionTimeout: 30
  contextPath: /

  # Tomcat specifics
  tomcat:
    accessLogEnabled: false
    protocolHeader: x-forwarded-proto
    remoteIpHeader: x-forwarded-for
    basedir:
    backgroundProcessorDelay: 30 # secs

容许重写Boot的自动化配置,这一点可以使你的应用从原型转化为真正的产品,Boot使用相同的application.yml文件进行配置,这样就会很是容易。自动化配置的指令被设计的尽量简短,因此当使用actuator构建微服务时,会安装一个配置属性的端点,也就是/configprops,当肯定哪些指令须要重写时能够进行参考。若是咱们的微服务要使用持久化数据源,如MySQL,那么只需将MySQL的Java驱动添加到运行时classpath中,而后在application.yml中添加必要的配置指令便可,如程序清单1.14所示。

程序清单1.14

spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/proddb
    username: root
    password

在一些场景下你可能须要更为灵活的配置,Boot容许你经过Java的系统属性(System properties)重写不少它的默认配置。例如,若是你的应用须要在部署到产品化环境中使用不一样的数据库用户,那么username配置指令能够经过标准的Java系统属性传入到应用之中,而这须要切换到命令行中执行-Dspring.datasource.username=user。关于这一点更为现实的场景是云部署环境,如Cloud Foundry或Heroku,这些平台须要应用启动特定的HTTP端口,这一点经过操做系统的环境变量能够实现。Boot可以从系统属性继承获得配置,这样你的应用就能够在命令行中使用-Dserver.port=$PORT来获得HTTP端口。在开发微服务时,这是一种至关有用的特性,由于它可让微服务应用运行在各类环境配置之中。

外部化配置

微服务必需要支持的很重要的一点就是外部化配置。这种配置能够包含任何的内容,从占位符信息到数据库配置等等,在初始规划和构建应用原型时,这是必需要考虑的架构内容。在Spring IO平台中,已经存在各类导入配置的策略,可是应用可以以多种方式使用配置所形成的后果每每是产生冗长的编码性耦合。

Boot一个很棒的特性在于它能管理外部化的配置并将其转换为对象结构,这个对象能够在整个应用上下文中使用。建立一个简单老式的Java/Groovy对象(Plain Old Java/Groovy Object),并使用@ConfigurationProperties注解,那么这个对象就能使用Boot配置结构中预先定义的name名下的配置项。更具体一点来说,考虑一下程序清单1.15中的POGO,它可以获得application.key下的配置指令。

程序清单1.15

@ConfigurationProperties(name = "application")
class ApplicationProperties {
  String name
  String version
}

ApplicationProperties对象在Spring上下文中建立完成以后,Boot将会识别出它是一个配置对象,而且会按照运行时classpath之中application.propertiesapplication.yml文件中的配置指令填充它的属性。所以,若是咱们在微服务的application.yml文件中添加application内容区的话,如程序清单1.16所示,那么咱们就能够在应用的其余部分以编程的方式访问这些配置指令。

程序清单1.16

application:
  name: sb-ms-custdepl
  version: 0.1-CUSTOMER

这些配置指令能够有各类用途,要访问这些指令的惟一要求就是表明它们的POJO/POGO必须是Spring应用上下文的成员。Boot可以将一个controller做为Spring Java配置对象,这样就能很容易地管理配置bean与应用上下文的集成,如程序清单1.17所示。

程序清单1.17

@RestController
@Configuration
@RequestMapping("/appinfo")
@EnableAutoConfiguration
class AppInfoController {

  @Autowired
  ApplicationProperties applicationProperties

  @RequestMapping(method=[RequestMethod.GET])
  def get() {
    [
      name: applicationProperties.name,
      version: applicationProperties.version
    ]
  }

  @Bean
  ApplicationProperties applicationProperties() {
    new ApplicationProperties()
  }

  public static void main(String[] args) {
    SpringApplication.run UserController, args
  }
}

程序清单1.17中的样例代码可能有些牵强,不过,即使是在更为复杂的场景下,如何使用Boot来访问应用特定配置的原则是相同的。配置类也支持嵌套式的对象图,这样来自于配置中的深层数据就能更便利地进行访问,也有了更好的语义。例如,若是咱们想要获得的配置指令是application.根下的那些metrics key,那么能够在ApplicationProperties POGO中添加一个嵌套对象来表示这些值,如程序清单1.18所示。

程序清单1.18

@ConfigurationProperties(name = "application")
class ApplicationProperties {
  String name
  String version

  final Metrics metrics = new Metrics()

  static class Metrics {
    String dbExecutionTimeKey
  }
}

如今,咱们的application.yml文件能够如程序清单1.19所示,它在application.代码块中包含了metrics配置。

程序清单1.19

application:
  name: sb-ms-custdepl
  version: 0.1-CUSTOMER
  metrics:
    dbExecutionTimeKey: user.get.db.time

当咱们须要访问application.metrics.dbExecutionTimeKey的值时,可以以编程的方式经过ApplicationProperties对象来进行访问。

为了在整个应用之中使用application.propertiesapplication.yml文件中的这些配置指令,咱们并非必需要将其转换为对象图。Boot也为Spring应用上下文提供了PropertySourcesPlaceholderConfiguration,这样的话,来自于application.propertiesapplication.yml文件的指令或者来自于Java系统的重写属性均可以做为Spring属性占位符来使用。Spring的这种机制可以让你以一种特定的语法来为属性定义占位符值,若是Spring发现了占位符配置的话,就会用这个配置来进行填充。做为示例,咱们能够在controller中使用@Value注解来直接访问application.metrics.dbExecutionTimeKey,如程序清单1.20所示。

程序清单1.20

@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {

  @Autowired
  UserRepository repository

  @Autowired
  GaugeService gaugeService

  @Value('${application.metrics.dbExecutionTimeKey}')
  String dbExecutionKey

  @RequestMapping(method=[RequestMethod.GET])
  def get(Long id) {
    def start = new Date().time
    def result = id ? repository.findOne(id) : repository.findAll()
    gaugeService.submit dbExecutionKey, new Date().time - start
    result
  }

  public static void main(String[] args) {
    SpringApplication.run UserController, args
  }
}

关于应用指标的报告,后面会有更为详细的介绍,但如今重要的一点在于,理解@Value注解如何与Spring属性占位符一块儿使用,使Boot可以自动注入值,从而知足这个微服务的特定配置需求。

安全

在微服务的开发中,对于完备安全场景的需求会持续增加。为了知足这种需求,Boot引入了强大完整的Spring Security,而且提供了自动配置的功能,以快速简便地启用安全层。只需在应用的classpath中包含spring-boot-starter-security模块就能使Boot引入一些安全特性,如跨站脚本防御(cross-site scripting protection)而且会添加头信息以防止点击劫持(click-jacking)。除此以外,添加一条简单的配置指令就能启用基本认证来保护你的应用,如程序清单1.21所示。

程序清单1.21

security:
  basic:
    enabled: true

Boot会为你提供一个默认的用户帐号user和默认角色USER,而且会在应用启动的时候在控制台上输出随机生成的密码。就像Boot的其余功能那样,对于内置的user帐号,咱们能够很容易地指定不一样的用户名和密码(分别为“secured”和“foo”),这须要经过明肯定义的配置指令来实现,如程序清单1.22所示。

程序清单1.22

security:
  basic:
    enabled: true
  user:
    name: secured
    password: foo

对于简单的内部应用或开发原型来讲,Boot内置的基础设施可以快速地在微服务中启用基本认证,这是很是有用的。随着需求的演化,你的应用毫无疑问会须要更细粒度的安全特性,如保护端点只能由特定的角色访问。从这个角度来看,咱们可能但愿具备USER角色的调用者只能读取数据(即GET请求),而对具备ADMIN角色的调用者能够读取和写入数据(即POST请求)。为了作到这一点,咱们须要在工程的application.yml文件中禁用Boot的基本认证自动配置功能,而且定义咱们本身的useradmin帐号以及对应的角色。当你的需求超过Boot所提供的默认功能时,它一般很快就能实现,这能够做为佐证这一点的又一个例子。为了更具体地阐述这一点,考虑一下程序清单1.23中的代码。这个样例能够阐述如何发挥Spring Security全部潜在的功能以及更为复杂的认证策略,如基于JDBC后端、OpenID或单点登陆(Single-Sign On)。

程序清单1.23

@RestController
@RequestMapping("/user")
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableAutoConfiguration
class UserController extends WebSecurityConfigurerAdapter {

  @Autowired
  UserRepository repository

  @RequestMapping(method = [GET])
  @Secured(['ROLE_USER'])
  def get(Long id) {
    id ? repository.findOne(id) : repository.findAll()
  }

  @RequestMapping(method = [POST])
  @Secured(['ROLE_ADMIN'])
  def create(@RequestBody User user) {
    repository.save user
    user
  }

  @Override
  void configure(AuthenticationManagerBuilder auth) {
    auth
    .inMemoryAuthentication()
    .withUser "user" password "password" roles "USER" and() withUser "admin" password "password" roles "USER", "ADMIN"
  }

  @Override
  void configure(HttpSecurity http) throws Exception {
    BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint()
    entryPoint.realmName = "Spring Boot"
    http.exceptionHandling().authenticationEntryPoint(entryPoint)
    http.requestMatchers().antMatchers("/**").anyRequest()
    .and().httpBasic().and().anonymous().disable().csrf().disable()
  }

  public static void main(String[] args) {
    SpringApplication.run UserController, args
  }
}

在程序清单1.23的样例之中,应用如今被明确地配置为要基于useradmin用户帐号进行访问,它们的密码都是password,具备的角色分别是USERADMIN。微服务的GET和POST端点分别经过USERADMIN角色进行保护,这就意味着普通用户能够访问只读的数据,而执行读取-写入操做的话,须要admin用户凭证。

对于微服务来讲,基本认证是很好的一个选择,由于它遵循了很实用且普遍使用的认证协议。换句话说,不少的API调用者,包括移动应用,可以很容易地使用这一点来访问你的微服务。当你的认证需求超过了基本认证的功能时(如OpenID或OAuth),微服务可使用Spring Security的所有功能来知足你的需求。

消息集成

在任何的应用中,消息(messaging)都是一种很强大的工具,在一点上,微服务固然也不能例外。使用消息驱动架构开发的应用可以更好地支持可重用性和扩展性。Spring Boot可以让开发人员在编写微服务时将消息做为架构的核心组成部分,它使用到了Spring IO平台的企业集成模式(Enterprise Integration Patterns)实现,即Spring Integration。Spring Integration提供了开发消息驱动架构的基本结构,以及与分布式企业平台集成的模块。这种能力使得微服务可使用来自抽象消息源的业务对象,这些消息源能够在应用内部,也多是组织机构内部的其余服务所提供的。

尽管Boot并无提供明确的Spring上下文自动化配置,可是它为Spring Integration提供了一个starter模块,它会负责引入Spring Integration项目的一系列依赖。这些依赖包括Spring Integration的核心库(Core library)、HTTP模块(用来进行面向HTTP的企业集成)、IP模块(用来进行基于Socket的集成操做)、File模块(用于进行文件系统集成)以及Stream模块(用于支持使用Stream的操做,如stdin和stdout)。这个starter模块为开发人员提供了健壮的消息功能的工具集,可使已有的基础设施适应微服务API。

除了starter模块,Boot也为经过CLI构建的应用提供了编译器自动配置的功能。对于须要快速构建微服务原型并验证可行性的开发者来讲,这种方式提供了一些捷径。使用企业级平台的应用能够快速地进行开发,在转移到正式的工程和构建系统以前,就能确认其价值。使用Spring Boot和Spring Integration使一个消息驱动的微服务运行起来很是简单,如程序清单1.24的样例代码所示。

程序清单1.24

@RestController
@EnableIntegrationPatterns
class App {

  @Bean
  def userLookupChannel() {
    new DirectChannel()
  }

  @Bean
  def userTemplate() {
    new MessagingTemplate(userLookupChannel())
  }

  @RequestMapping(method=[RequestMethod.GET])
  def get(@RequestParam(required=false) Long id) {
    userTemplate().convertSendAndReceive( id ? id : "")
  }
}

class User {
  Long id
}

@MessageEndpoint
class UserLookupObject {

  @ServiceActivator(inputChannel="userLookupChannel")
  def get(Long id) {
    id ? new User(id:id) : new User()
  }
}

使用消息驱动的方式来进行微服务的开发能提供很大的代码可重用性,而且可以与底层的服务提供者实现相解耦。在更为正式的场景之中,程序清单1.24的代码可能会负责组合数据,这些数据可能来自于数据库调用和企业组织中某个外部的服务集成。Spring Integration具备内置的设施用来进行负载路由(payload routing)和处理器链(handler chaining),这对于组合不一样的数据来讲,是一个颇有吸引力的方案,咱们的微服务能够做为一个数据的提供者(provider)。

提供度量指标

微服务最重要的一个特性可能就是为报表终端(reporting agent)提供度量指标。不像那些功能完备的Web应用,微服务是轻量级的,设计时可能就不会规划提供报表界面或完备的接口来分析服务的活动。这种类型的操做最好是留给专门进行数据聚合和分析的应用,这些数据可以用来进行稳定性、性能以及商务智能的监控。基于这样的前提,微服务应该为这些工具提供端点,从而更加容易地获取有关该服务活动的数据。而报表工具负责将数据聚合到一个视图或报告中,对于关心数据的人这才是有意义的。

微服务的一些指标如稳定性和性能,对全部的应用都是通用的,可是与业务操做相关的指标必须由应用自己来具体进行管理。针对这一点,Spring Boot的actuator模块为开发人员提供了一种机制,容许开发人员经过/metrics端点以编码的方式暴露微服务状态的细节。Boot将指标拆分为“counter”和“gauge”两种类别:counter是全部以Number类型来展示的指标,而gauge是衡量双精度计算的指标。为了让微服务的开发人员更加容易地使用指标,Boot暴露了CounterServiceGaugeService,它们能够自动织入到应用上下文之中。请参见程序清单1.25的样例,它阐述了如何经过CounterService对外暴露点击数。

程序清单1.25

@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {

  @Autowired
  UserRepository repository

  @Autowired
  CounterService counterService

  @RequestMapping(method = [GET])
  def get() {
    get(null)
  }

  @RequestMapping(value="/{id}", method = [GET])
  def get(@PathVariable Long id) {
    counterService.increment id ? "queries.by.id.$id" : "queries.without.id"
    id ? repository.findOne(id) : repository.findAll()
  }
}

在点击/user端点时,有可能提供ID也有可能不提供ID,/metrics端点都会在counter.父节点下记录新的key。例如,若是咱们只是查询/user端点而不带有ID的话,那么就会注册counter.queries.without.id指标。相似的,若是咱们带有ID的话,那么就会看到有一个counter.queries.by.id.<id>的key,它能用来标记对于给定的ID已经进行了多少次查询。这些指标可能会有助于掌握最常常访问的User对象并指导要采起的行为,如缓存或数据库索引。相似于递增指标的数值,CounterService也容许将指标的值将为零。这对于跟踪打开的链接数或其余频率分布(histographic)的测量都是颇有用处的。

gauge是稍微有所不一样的一种类型指标,它会进行探索性的计算或基于请求来肯定值。如GaugeService的JavaDocs所述,“gauge”能够测量任意的值,从方法执行的次数到会议室的温度。当须要为报表工具暴露细节时,这种类型的测量尤为适合于使用GaugeService。gauge的指标会在/metrics端点之下进行访问,而且带有gauge.前缀。它们的注册方式与counter有些差异,如程序清单1.26所示。

程序清单1.26

@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {

  @Autowired
  UserRepository repository

  @Autowired
  CounterService counterService

  @RequestMapping(method = [GET])
  def get() {
    get(null)
  }

  @RequestMapping(value="/{id}", method = [GET])
  def get(@PathVariable Long id) {
    def start = new Date().time
    def result = id ? repository.findOne(id) : repository.findAll()
    def time = new Date().time - start
    gaugeService.submit("user.get.db.time", time.doubleValue())
    result
  }
}

默认状况下,指标会存储在一个易失的内存数据库之中,但Boot同时也为应用上下文提供了MetricsRepository实现,它能支持更为持久化的行为。Boot自带了一个RedisMetricsRepository,它可以自动织入进来,从而将指标存储到Redis值存储之中,另外,能够编写自定义的实现将指标存储到任意的数据存储形式之中。

Boot还提供了对Coda Hale Metrics库的支持,它会将以特定名称开头的指标强制转换为对应的Metrics类型。例如,若是有一个指标是以histogram.开头,那么这个值将会做为Histogram对象类型。这种自动化的强制转换对于meter.timer.key也是有效的,而普通的指标将会做为Gauge类型。

一旦微服务的指标在Boot中进行了注册,那么报表工具就能够经过/metrics端点来检索它们。已命名的指标能够经过/metrics端点获取,只需将指标的key名做为查询字符串的一部分便可。例如,若是只是访问gauge指标下的“user.get.db.time”,报表工具能够针对/metrics/gauge.user.get.db.time进行查询。

打包Boot应用

正如前面所讨论的,Boot提供了Maven和Gradle插件,它为构建系统的打包阶段提供了一种钩子(hook),以产生所谓的“胖jar”,在这种jar中包含了工程的全部依赖。当这个胖jar包执行时,应用将会运行在与工程开发期相同的嵌入式容器之中。这种简便的方式可以让开发人员省去不少麻烦,由于他们的部署包在开发期和运行时环境之中具备相同的依赖结构。这也可以缓解运维团队的焦虑,他们不用担忧部署的场景,由于在部署时一个错误配置的运行时容器可能会带有某个特定的依赖,而在项目的开发期所依赖的多是另一个。

为了在Maven下执行打包,只需执行mvn package命令。Spring Boot的插件会备份工程所建立的原始jar而且在文件名上添加“.original”。在这里,可以获得可运行的jar,文件符合Maven artifact的命名约定,它能够按照工程最合适的方式进行部署。使用Gradle构建Boot工程一样很简单,只需执行标准的gradle build命令便可。相似于Maven,Boot插件在原有的打包任务以后使用Gradle安装了一个生命周期事件,而且会在build/libs目录下建立胖jar包。对所生成的胖jar包进行检查的一种方式就是全部依赖的jar都会位于归档文件的lib/目录下。

打包完成以后,胖jar包就可以像其余可运行的jar文件那样在命令行中执行了,也就是使用$JAVA_HOME/bin/java -jar path/to/myproject.jar命令。启动后,Boot应用的日志将会显示在控制台上。

对于须要部署到传统servlet容器之中的应用,Boot提供了一种方式以编码的方式初始化Web配置。为了使用这一点,Boot提供了可选的WebApplicationInitializer,它会使用servlet容器来注册应用,这会经过Servlet 3.0 API以编码的方式注册servlet而且会用到ServletContext。经过提供SpringBootServletInitializer的子类,Boot应用可以使用嵌入的Spring上下文来注册配置,这个Spring上下文是在容器初始化的时候建立的。为了阐述这个功能,考虑程序清单1.27中的示例代码。

程序清单1.27

@RestController
@EnableAutoConfiguration
class Application extends SpringBootServletInitializer {

  @RequestMapping(method = RequestMethod.GET)
  String get() {
    "home"
  }

  static void main(String[] args) {
    SpringApplication.run this, args
  }

  @Override
  SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    application.sources Application
  }
}

Application类中被重写的configure方法就是使用嵌入式的Spring上下文注册应用的地方。在更为正式的场景之中,这个方法可能会用来注册Spring Java配置类,它会定义应用中全部controller和服务的bean。

当将应用打包部署到servlet容器之中时,工程要构建为一个war文件。在Maven工程中,为了适应这一点,须要移除Boot插件,而且packaging须要明肯定义为“war”类型,如程序清单1.28所示。

程序清单1.28

<?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>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.0.0.RC1</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
        </repository>
    </repositories>
</project>

对这个工程执行mvn install命令会在target目录下生成myproject-1.0.0-SNAPSHOT.war文件。使用Gradle构建的工程可使用Gradle War Plugin,它为构建war文件暴露了一个war任务。相似于Maven的配置,Boot Gradle工程也须要移除所包含的Boot插件。产生war文件的示例Gradle构建脚本如程序清单1.29所示。

程序清单1.29

apply plugin: 'java'
apply plugin: 'war'

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "http://repo.spring.io/milestone" }
}

ext {
  springBootVersion = '1.0.0.BUILD-SNAPSHOT'
}

dependencies {
    compile "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
    compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
}

对于Boot工程,使用这个构建脚本运行Gradle的war任务将会在build/libs目录下产生war文件。

无论是Maven仍是Gradle的配置,一旦war文件产生,它就能够部署到任意兼容Servlet 3.0的应用服务器之中。部分兼容的容器包括Tomcat 7+、Jetty 八、Glassfish 3.x、JBoss AS 6.x/7.x以及Websphere 8.0。

延伸阅读

Spring Boot团队已经编写了完整的指导和样例来阐述框架的功能。Blog文章、参考资料以及API文档均可以在Spring.IO网站上找到。项目的GitHub页面上能够找到示例的工程,更为具体的细节能够阅读Spring Boot的参考手册。SpringSourceDev YouTube频道有一个关于Spring Boot的webinar,它概述了这个项目的目标和功能。在去年在伦敦举行的Groovy & Grails Exchange上,David Dawson作了一个使用Spring Boot开发微服务的演讲

关于做者

Daniel Woods是Netflix的高级软件工程师,负责开发持续交付和云部署工具。他擅长JVM栈相关的技术,活跃在Groovy、Grails和Spring社区。能够经过电子邮件地址danielpwoods@gmail.com或Twitter @danveloper联系到Daniel。

http://www.infoq.com/cn/articles/microframeworks1-spring-boot#

相关文章
相关标签/搜索