第一章 了解微服务

1.1 什么是微服务

微服务并非什么新的概念,其实就是服务化思路的一种最佳实践方向,遵循SOA(Service-Oriented Architecture 面向服务体系结构)的思路,各个企业在服务化治理的道路上走的时间长了,踩的坑多了,整个软件交付链路上各个环节的基础设施逐渐成熟了,微服务自然而然就诞生了。
微服务实现和实施思路则更强调功能趋向单一,服务单元小型化和微型化。

1.2 微服务因何而生

当规模达到一定程度,在开发阶段如果遵循monolith的服务化理念,协同开发造成的代码冲突成了工作瓶颈。到了软件交付阶段,就必须等所有的依赖部分都完成之后才能完成交付,大大降低了交付效率。
随着服务和系统的复杂度逐渐飙升,为了能够在整个软件的交付链路上高效扩展,将独立的功能和服务单元进行拆分,从而形成一个个的微服务是自然而然的。

1.3 微服务的好处

独立:每个微服务作为一个独立的应用,可以单独进行开发和交付,大大提高灵活性和效率,带来的两个比较明显的好处:1)扩展性,我们可以快速地添加服务集群的实例,提升整个微服务集群的服务能力
2)隔离性:隔离性实际上是可扩展性的基础,当我们将每个微服务都隔离为独立的运行单元之后,任何一个或多个微服务的失败都讲只影响自己或者少量其他微服务,而不会大面积地波及整个服务运行体系。
多语言生态:微服务独立之后,给了对应的团队和组织快速迭代和交付的能力,同时,也给团队和组织带来了更多的灵活性,实际上,对应交付不同微服务的团队或者组织来说,可以采用不同的编程语言构建这些微服务。

1.4 微服务带来的挑战

微服务化之后应用数量增多,就要求我们要使用标准化的思路来开发和交付微服务:

  • 通过标准化,我们可以重复使用开发阶段打造的一系列环境和工具支持

  • 通过标准化,我们可以复用支持整个微服务交付链路的各项基础设施

  • 通过标准化,我们可以减少采购差异导致的成本上升,同时高效地利用硬件资源

  • 通标准化,我们可以用标准的协议和格式来治理和维护数量庞大的微服务

第二章 spring boot 的工作机制

spring boot 是spring框架对“约定优先于配置”理念的最佳实践的产物,一个典型的spring boot 应用本质上其实就是一个基于spring框架的应用。

2.1 spring boot 初体验

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. @SpringBootApplication
  4. public class DemoApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(DemoApplication.class, args);
  7. }
  8. }

annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)是最重要的

2.2 @SpringBootApplication背后的秘密

@SpringBootApplication是一个“三体”结构,实际上它是一个复合annotation

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = {
  8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  10. public @interface SpringBootApplication {
  11. ...
  12. }

重要的是这三个Annotation

  • @Configuration

  • @EnableAutoConfiguration

  • @ComponentScan

2.2.1 @Configuration 创世纪

这里的@Configuration就是javaConfig形式的Spring Ioc容器的配置类使用的@Configuration,启动类标注了@Configuration之后,它本身也是一个Ioc容器的配置类。

2.2.2 @EnableAutoConfiguration的功效

@EnableAutoConfiguration其实也没啥创意,就是借助@Import的支持,收集和注册特定场景相关的bean定义:

  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到Ioc容器

  • @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到Ioc容器

而@EnableAutoConfiguration也是借助@Import的帮助,将所有复合自动配置条件的bean定义加载到Ioc容器,仅此而已。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(AutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {
  8. ...
  9. }

其中最关键的是@Import(AutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配合都加载到当前SpringBoot创建并使用的Ioc容器
借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAUtoConfiguration可以“智能”地自动配置功效才得以大功告成
自动配置的幕后英雄:SpringFactoriesLoader详解
SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件METE-INF/spring.factories加载配置,spring.factories是一个典型的java properties文件,配置的格式为key=value形式,只不过key和value都是java类型的完整类名
对于@EnableAutoConfiguration来说,SpringFactoriesLoader的用途稍微不同一些,其本意是为了提供SPI扩展的场景,而在@EnableAutoConfiguration的场景中,它更多是提供了一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找key,所以@EnableAutoConfiguration自动配置的魔法其实就变成了:从classpath中搜寻所有META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的Ioc容器配置类,然后汇总为一个并加载到Ioc容器。

2.2.3 可有可无的@ComponentScan

为啥说@ComponentScan是可有可无的,因为原则上来说,作为Spring框架里的“老者”,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中。加载bean 定义到Spring的Ioc容器,我们可以手工单个注册,并不一定非要通过批量的自动扫描完成,所以说@ComponentScan是可有可无的。

2.3 SpringApplication:springboot程序启动的一站式解决方案

如果非说springboot微框架提供了点自己特有的东西,在核心类层面,也就是SpringApplication了。
SpringApplication将一个典型的Spring应用启动的流程“模板化”,在没有特殊需求的情况下,默认模板化后的执行流程就可以满足需求;有特殊需求也没关系,SpringApplication在适合的流程结点开放了一系列不同类型的扩展点,我们可以通过这些扩展点对SpringBoot程序的启动和关闭过程进行扩展。
最简单的扩展或者配置是SpringApplication通过一系列设置方法开放的定制方式,比如,我们之前的启动类的main方法中只有一句:SpringApplication.run(DemoApplication.class, args);

  1. SpringApplication bootstrap = new SpringApplication(DemoApplication.class);
  2. bootstrap.setBanner((environment, aClass, printStream) -> {
  3. printStream.print("ASCII 字符画");
  4. });
  5. bootstrap.setBannerMode(Banner.Mode.CONSOLE);
  6. //其他扩展
  7. bootstrap.run(args);

banner也可以直接在classpath下放一个banner.txt文件就可以替换springboot的默认的ASCII 字符画

2.3.1 深入探索SpringApplication执行流程

SpringApplication的run方法的主要执行流程可以归纳为:
1)如果使用的静态run方法,首先要创建一个SpringApplication对象,然后调用实例run方法

  • 根据classpath里面是否存在某个特征类来决定是否应该创建一个web应用使用的ApplicationContext类型,还是创建一个标准应用使用的ApplicationContext

  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer

  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener

  • 推断并设置main方法的定义类

2)SpringApplication实例初始化完成并且完成设置后,就开始执行run方法,首先遍历执行所有通过SpringFactoriesLoader可用找到并加载的SpringApplicationRunListener,调用他们的starting()方法
3)创建并配置当前SpringBoot应用将要使用的Environment
4)遍历调用所有SpringApplicationRunListener的environmentPrepared()方法
5)如果SpringApplication的bannerMode不为Banner.Mode.OFF时,会基于mode值决定打印banner到哪里
6)根据前面得到的应用类型,创建对应的ApplicationContext,根据条件决定是否使用自定义的beanNameGenerator、resourceLoader
7)ApplicationContext创建好之后,遍历ApplicationContextInitializer列表调用它们的initialize方法进行进一步的处理
8)变量所有的SpringApplicationRunListener,并调用contextPrepared()方法,通知SpringBoot应用使用的ApplicationContext准备好了。
9)最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的Ioc容器配置加载到已经准备完毕的ApplicationContext
10)遍历SpringApplicationRunListener,调用contextLoaded()方法
11)调用ApplicationContext的refresh()方法,完成Ioc容器可用的最后一道工序
12)判断是否设置了shutdownHook,如果设置了就注册
13)遍历SpringApplicationRunListener,调用started()方法
14)查找当前是否有注册ApplicationRunner和CommandLinRunner,并遍历调用
15)遍历SpringApplicationRunListener,调用running()方法
16)如果过程中出现异常,会遍历SpringApplicationRunListener,调用failed()方法

至此springboot就启动完毕了

2.3.2 SpringApplicationRunListener

这是springboot应用启动过程中接收不同执行时点时间通知的监听者,springboot提供了一个默认的实现EventPublishRunListener,如果需要,可以接收并处理,如果自己扩展按照SpringFactoriesLoader的规矩,我们在META-INF/spring.factories下进行如下配置即可。

  1. org.springframework.boot.SpringApplicationRunListener=\
  2. org.springframework.boot.context.event.EventPublishingRunListener

2.3.3 ApplicationListener

ApplicationListener是spring提供的,如果我们要为SpringBoot应用添加自定义的ApplicationListener,有两种方式:
1)通过SpringApplication.addListeners(…)或者SpringApplication.setListeners()添加一个或者多个自定义APplicationListener
2)借助SpringFactoriesLoader,在META-INF/spring.factories文件中添加配置,这是默认配置

  1. org.springframework.context.ApplicationListener=\
  2. org.springframework.boot.ClearCachesApplicationListener,\
  3. org.springframework.boot.builder.ParentContextCloserApplicationListener,\
  4. org.springframework.boot.context.FileEncodingApplicationListener,\
  5. org.springframework.boot.context.config.AnsiOutputApplicationListener,\
  6. org.springframework.boot.context.config.ConfigFileApplicationListener,\
  7. org.springframework.boot.context.config.DelegatingApplicationListener,\
  8. org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
  9. org.springframework.boot.context.logging.LoggingApplicationListener,\
  10. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

2.3.4 ApplicationContextInitializer

这个类也是spring提供的,这个类的主要目的就是在ConfigurableApplicationContext类型的ApplicationContext做refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。一般不需要自定义,springboot默认提供了

  1. org.springframework.context.ApplicationContextInitializer=\
  2. org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
  3. org.springframework.boot.context.ContextIdApplicationContextInitializer,\
  4. org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
  5. org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

如果需要自定义,可以在META-INF/spring.factories中添加,或者通过SpringApplication.addInitializers()设置

2.3.5 CommandLineRunner

这个是springboot特有的回调扩展接口,需要关注的地方:
1)所有的执行点在SpringBoot应用的ApplicationContext完全初始化开始工作之后
2)只要存在于当前SpringBoot应用的ApplicationContext中的任何CommandLineRunner,都会被加载执行

建议CommandLineRunner的实现类使用@org.springframework.core.annotation.Order标注或者实现org.springframework.core.Ordered接口,便于对执行顺序进行调整,这是个很好的扩展接口

2.4 再谈自动配置

@EnableAutoConfiguration的自动配置功能其实有强大的调控能力,通过配合比如条件配置或者调整加载顺序,我们可以进行更加细粒度的调整和控制。

2.4.1 基于条件的自动配置

基于条件的自动配置特性是spring框架中的,使用@Conditional注解,配合@Configuration或者@Bean 来干预一个配置或者bean定义是否能够生效
要实现基于条件的配置,只要通过@Conditional指定自己的Condition实现类就可以了
@Conditional可以作为一个Meta Annotation用来标注其他Annotation实现类,从而构建符合Annotation

2.4.2 调整自动配置的顺序

在实现自动配置的过程中,除了可以提供基于条件的配置,我们还可以对当前要提供的配置或者组件的加载顺序进行相应调整,从而让这些配置或者组件之间的依赖分析和组装可以顺利完成。
我们可以使用@org.springframework.boot.autoconfigure.AutoConfigureBefore或者
@org.springframework.boot.autoconfigure.AutoConfigureAfter让当前配置或者组件在某个其他组件之前或者之后进行

第三章 spring-boot-starter

springboot微框架从两个层面影响spring社区的开发者们:
1)基于spring框架的“约定优先于配置(COC)”理念以及最佳实践之路
2)提供了针对日常企业应用研发各种场景的spring-boot-starter自动配置依赖模块,如此多“开箱即用”的依赖模块,是的开发各种场景的spring应用更加快速和高效。

springBoot提供的这些“开箱即用”的依赖模块都约定以spring-boot-starter-作为命名的前缀,并且皆位于org.springframework.boot包或者命名空间下
简单来讲,我们可以将对SpringBoot的行为可以进行干预的配置方式划分为几类:

  • 命令行参数

  • 系统环境变量

  • 位于围巾系统中的配置文件

  • 位于classpath中的配置文件

  • 固化到代码中的配置项

这里列出来的几种配置方式按照优先级从高到低排列,高优先级方式提供的配置项可以覆盖或者优先生效,比如通过命令行参数传入的配置项会覆盖通过环境变量传入的同一配置项,当然也会覆盖其他后面几种方式给出的同一配置项。
不管是位于文件系统还是classpath,springboot应用默认的配置文件名叫作application.properties,可以直接放在当前项目的根目录下或者名称为config的子目录下。

3.1 应用日志和spring-boot-starter-logging

如果引入了

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

springboot应用将自动使用logback作为应用日志框架,由org.springframework.boot.logging.LoggingApplicationListener根据情况初始化并使用。
springboot为我们提供了很多默认的日志配置,所以,只要将spring-boot-starter-logging作为依赖加入当前应用的classpath,就不需要做任何多余的配置,如果我们需要对日志做调整:

  • 遵循logback,在classpath中使用资金定制的logback.xml配置文件

  • 在围巾系统中任何一个位置提供资金的logback.xml配置文件,然后通过logging.config配置项指向这个配置文件来启用它。比如在application.properties中 logging.config=/{some.path}/any-logback.xml

如果想使用log4j或者log4j2,引入以下依赖即可

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

3.2 快速违背应用开发-spring-boot-web

为了简化搭建并开发web项目,springboot提供了spring-boot-starter-web自动配置模块

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

只要引入我们就得到了一个直接可执行的web应用,在当前项目下运行 mvn spring-boot:run 就可以启动一个使用嵌入式tomcat服务请求的web应用。

3.2.1 项目结构层面的约定

项目结构层面与传统打包为war的javaWeb应用差异在于,静态文件和页面模板的存放位置变了,原来放在src/main/webapp目录下的一系列资源,现在都统一放在src/main/resources相应子目录下。
1)src/main/resources/static 用于存放各类静态资源,比如css,js等
2)src/main/resources/templates用于存放模板文件,比如*.vm

3.2.2 springMVC框架层面的约定和定制

spring-boot-starter-web默认将为我们自动配置如下一些SpringMVC必要组件:

  • 必要的ViewResolver,比如ContentNegotiatingViewResolver和BeanNameViewResolver

  • 将必要的Converter、GenericConverter和Formtter等bean注册到Ioc容器

  • 添加一系列的HttpMessageConverter以便支持对Web请求和相应的类型转换。

  • 自动配置和注册MessageCodesResolver

  • 其他

任何时候,如果我们对默认提供的SpringMVC组件设定不满意,都可以在Ioc容器中注册新的同类型的bean定义来替换,或者直接提供一个基于WebMvcConfigurerAdapter类型的bean定义来定制,甚至直接提供一个标注了@EnableWebMvc的@Configuration配置类完全接管所有SpringMVC的相关配置

3.2.3 嵌入式Web容器层面的约定

spring-boot-satrter-web默认使用嵌入式tomcat作为web容器对外提供http服务,默认将使用8080端口对外监听和提供服务:
1)假设我们不想使用默认的嵌入式tomcat,可以引入spring-boot-starter-jetty或者spring-boot-starter-undertow作为替代方案(需要排除tomcat包)
2)假设我们不想使用默认的8080端口,那么我们可以通过更改配置项server.port来指定端口,比如server.port=8000

spring-boot-starter-web提供了很多以server.为前缀的配置项用于对嵌入式web容器提供配置,比如:
server.port
server.address
server.ssl.
server.tomcat.

如果还需要进一步定制,可以继承对应的Factory并注册到Ioc容器
TomcatServletWebServerFactory
JettyServletWebServerFactory
UndertowServletWebServerFactory
一般不会走的这一步,默认的配置已经很完备了。

3.3 数据访问与spring-boot-starter-jdbc

大部分java应用都需要访问数据库,尤其是服务层,所以springboot会为我们自动配置相应的数据访问设施。
那么我们需要直接或者间接依赖spring-jdbc,一旦spring-jdbc位于我们springboot应用的classpath,就会触发数据访问相关的自动配置,最简单的就是加入spring-boot-starter-jdbc加入依赖。
默认情况下,如果我们没有任何配置,会为我们自动配置一个h2的嵌入式数据库,这个适合于测试场景,一般我们会自己配置DataSource实例,或者通过自动配置模块提供的配置参数对DataSource实例进行自定义的配置,如果我们只有一个数据库,使用DataSource自动配置提供的参数是最方便的
spring.datasource.url=jdbc:mysql://{database host}:3306/{databaseName}
spring.datasource.username={database username}
spring.datasource.password={database password}

除了DataSource会自动配置,springboot还会自动配置相应的JdbcTemplate、DataSourceTransactionManager等关联配置,使用的时候直接注入就可以了。
如果我们配置了多个DataSource实例指向多个数据库,就需要在启动类上添加排除,来禁用自动配置
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class})

我们也可以将其中一个DataSource用@Primary注解指定为主数据源,这样就不需要对启动类做修改了。
springBoot还提供了其他一些数据访问相关的自动配置模块,比如:spring-boot-starter-data-jpa、spring-boot-starter-data.mongodb,自己根据需要选择。

3.3.1 SpringBoot应用的数据库版本化管理

SpringBoot为大家提供了针对Flyway和Liquibase的自动配置功能,对单一开发和部署的应用来说还是可以考虑的。

3.4 spring-boot-starter-aop及其使用场景说明

现在Spring框架提供的AOP方案倡导了一种各取所长的方案,即使用SpringAOP的面向对象的方式来编写和组织织入逻辑,并使用AspectJ的Pointcut描述语言配合Annotation来标注和指明织入点。
原则上来说,我们只要引入Spring框架中的AOP的相应依赖就可以直接使用Spring的AOP支持了,不过为了进一步使大家使用SpringAOP提供便利,Springboot还是提供了一个spring-boot-starter-aop自动配置模块。
由两部分组成:
1)位于spring-boot-autoconfigure的org.springframework.boot.autoconfigure.aop.AopAutoConfiguration提供@Configuration配置类和相应的配置项。
2)spring-boot-starter-aop模块自身提供了针对spring-aop、aspectjrt和aspectjweaver的依赖

一般情况下,只要项目依赖中加入了spring-boot-starter-aop,其实就会自动触发AOP的关联行为,包括构建相应的AutoProxyCreator,将横切关注点织入(weave)相应的目标对象等,不过AOPAutoConfiguration依然为我们提供了两个配置项,用来干预AOP相关配置:
spring.aop.auto=true //关闭自动配置
spring.aop.proxy-target-class=false //不启用针对class而不是interface级别的aop代理

3.4.1 spring-boot-starter-aop在构建spring-boot-starter-metrics自定义模块中的应用

对于应用性能监控来说,架构逻辑很简单:

  • 应用性能数据的采集

  • 应用性能数据的存储

  • 应用性能数据的分析和展示

这里演示构建一个spring-boot-starter-metrics自定义的自动配置模块用来解决“应用性能数据采集”问题。

  • 采集应用性能数据可以帮助我们更好的分析和改进应用,但是不应该对应用的核心职能形成侵害,应该控制数据采集模块对性能的损耗在10%或更少。

  • SpringAOP其实提供了多种横切逻辑织入机制,性能损耗上也是各有差别

  • 针对应用性能数据采集,应该对应用开发者透明,通过配置外部化的形式,可以最大限度地剥离这部分对应用开发者来说并非核心的关注点,只在部署和运行之前确定采集点并配置上线

Dropwizard Metrics为我们提供了多种不同类型的应用数据度量方案,通过相应的数据处理算法在性能和批量状态的管理上做了很多很优秀的工作,如果直接用它的API来做,写起来代码太多,并且会跟应用代码有更多的耦合,所以使用AOP的方式进行维护,使用spring-boot-starter-metrics自定义的自动配置模块就会自动地收集这些位置上指定的性能度量数据。

  • 继承spring-boot-starter-parent ,用于加入springboot的相关依赖

  • 添加spring-boot-starter-aop,用来使用AOP的

  • io.dropwizard.metrics的相应依赖,用来引入dropwizard metrics类库和必要的Annotations

  • spring-boot-starter-actuator,这个自动配置模块主要用来对dropwizard metrics和JMX的一部分自动配置逻辑

  1. package com.keevol.springboot.metrics.autocfg;
  2. import com.codahale.metrics.JmxReporter;
  3. import com.codahale.metrics.MetricRegistry;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  7. import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.ComponentScan;
  10. import org.springframework.context.annotation.Configuration;
  11. import javax.management.MBeanServer;
  12. @Configuration
  13. @ComponentScan({"com.keevol.springboot.metrics.lifecycle","com.keevol.springboot.metrics.aop"})
  14. @AutoConfigureAfter(AopAutoConfiguration.class)
  15. public class DropwizardMetricsMBeansAutoConfiguration {
  16. @Value("${metrics.mbeans.domain.name:com.keevol.meraics}")
  17. String metricsMBeansDomainName;
  18. @Autowired
  19. MBeanServer mBeanServer;
  20. @Autowired
  21. MetricRegistry metricRegistry;
  22. @Bean
  23. public MetricRegistry metricRegistry() {
  24. return new MetricRegistry();
  25. }
  26. @Bean
  27. public JmxReporter jmxReporter() {
  28. JmxReporter reporter = JmxReporter.forRegistry(metricRegistry)
  29. .inDomain(metricsMBeansDomainName)
  30. .registerWith(mBeanServer)
  31. .build();
  32. return reporter;
  33. }
  34. }
  • @Configuration,指定当前类为一个JavaConfig配置类

  • @ComponentScan,为了简便,让@ComponentScan把这两个java package下的所有组件都加载到Ioc容器中

  • @AutoConfigureAfter,是希望DropwizardMetricsMBeansAutoConfiguration在AOPAutoConfiguration完成之后进行配置。

io.dropwizard.metrics:metrics-annotation 提供了几个有趣的annotation:

  • Timed

  • Gauge

  • Counted

  • Metered

  • ExceptionMetered

这些语义良好的Annotation定义可以用来标注相应的AOP逻辑扩展点,针对同一个MockService,我们可以将性能数据的度量和采集简化为只标注几个Annotation就可以了,这也只是一些标记信息,还是需要AutoMetricsAspect,通过拦截每个public方法并检查方法上是否存在某个metrics annotation,为匹配的方法注入相应性能数据采集代码逻辑,从而完成基于AOP和dropwizard metrics的应用性能数据采集方案的实现。

3.5 应用安全与spring-boot-starter-security

应用安全属于安全防护体系中的重要一环,但也是最薄弱的一环:

  • 应用的核心职责是完成业务和产品的功能需求,而安全确实非功能性需求,在资源有限的情况下,对安全防护会不足够重视

  • 大部分应用开发者对应用安全知之甚少

  • 在做的比较小的时候,通常也不会遇到麻烦

springSecurity提供了丰富的开箱即用的安全特性支持,springboot也提供了spring-boot-starte-security,主要面向web应用安全,默认提供一个基于httpBasic认证的安全防护策略,默认用户名是user,密码会打印在控制台,如果需要定制,通过配置项修改
security.user.name={name}
security.user.password={password}

除此之外,还会默认启用一些必要的web安全防护,比如针对xss、csrf等常见针对web应用的攻击,同时,也会将常见的静态资源路径排除在安全防护之外。但是对于生产环境还是太弱了,需要通过扩展进行加固。

3.5.1 了解SpringSecurity基本设计

SpringSecurity框架不但囊括了基本的认证和授权功能,而且提供了加密、统一登录等一系列相关支持

  • 访问者(Accessor)需要访问某个资源(Resources)是这个场景中最原始的需求,中间要加入一些检查和防护。

  • 在访问资源过程中,需要经过一系列的关卡,就是FilterInvocation、MethodInvocation和Joinpoint,这些在SpringSecurity框架中统称Secured Object(s)

  • 设置关卡,类似FilterSecurityInterceptor、MethodSecurityInterceptor和AspectJSecurityInterceptor,都是AbstractSecurityInterceptor的实现类

  • 还需要一个进行过滤的类,在SpringSecurity框架中就是AccessDecisionManager,来决定谁可以访问哪些资源

  • 还需要一个识别访问者身份的类,也就是AuthenticationManager

SpringSecurity的web安全方案基于Java的Servlet API规范构建,org.springframework.security.web.SecurityFilterChain接口,filter可以简单划分为几类,可以根据需要选用并添加:

  • 第一类可以认为是信道与状态管理,比如ChannelProcessingFilter用于处理http或者https之间的切换,而SecurityContextPersistenceFilter用于重建或者销毁必要的SecurityContext状态

  • 第二类是常见的web安全防护类,比如CsrfFilter

  • 第三类是认证和授权,比如BasicAUthenticationFilter,CasAuthenticationFilter等

最需要重点关注的是FilterSecurityInterceptor,就属于放在web访问路径上的那道“关卡”
ExceptionTranslationFlter属于另一个需要关注的核心类,它负责接待或送客
每个filter序列的步长设置成了100,我们就可以在其中添加自己的filter

3.5.2 进一步定制spring-boot-starter-security

除了使用SecurityProperties暴露的配置项(以security.*开头)对spring-boot-starter-security进行简单的配置,还可以通过继承WebSecurityConfigurerAdapter:

  • 使用其他的AuthenticationManager实例

  • 对默认HTTPSecurity定义的资源访问的规则进行重新定义

  • 对默认提供的webSecurity行为进行调整。

这种方式只能修改有限的扩展,可以实现一个用@EnableWebSecurity的javaCOnfig配置类到Ioc容器,但是这个需要对spring security框架本身和web安全本身有很深的理解,轻易不会采用。

3.6 应用监控与spring-boot-starter-actuator

为了保障应用的持续稳定服务,我们通常需要对其进行监控,从而可以了解应用的运行状态,并根据需要对运行状态进行调整,springboot提供了spring-boot-starter-actuator自动配置模块用于支持springboot应用的监控
为了能够感知应用的运行状态,我们通常会设置一些监控指标并采集分析,这些监控指标的采集需要在应用内部设置相应的监控点,一般是读取状态数据,我们称为Sensor应用的运行状态数据通过Sensors采集上来之后,我们通常会有专门的系统对这些数据进行分析和判断,一旦某个指标数据超出阈值,就需要对指标进行调整,为了能够执行调整,我们需要预先在应用内部设置对应的执行调整逻辑的控制器(比如,直接关闭的开关,或者可以执行微调甚至像刹车一样快速拉低某个指标值的装置),这些控制器就称为Actuator。
spring-boot-starter-actuator自动配置模块默认提供了很多endpoint,这些endpoint可以按照“监”和“孔”划分为两类:
1.Sensor类endpoints

  • autoconfig:这个endpoint会为我们提供一份springboot的自动配置报告,告诉我们哪些自动配置模块生效了,以及哪些没有生效,原因是什么

  • beans:给出当前应用容器中所有bean的信息

  • configprops:对现有容器中的configurationProperties提供的信息进行“消毒”处理后给出汇总信息

  • info:提供当前springboot应用的任意信息,我们可以通过environment或者application.properties等形式提供以ifno.为前缀的任何配置项,然后info这个endpoint就会将这些配置项的支作为信息的一部分展示出来。

  • health:针对当前springBoot应用的健康检查用的endpoint

  • env:关于当前springboot应用对应的environment信息

  • metrics:当前springboot应用的metrics信息

  • trace:当前springboot应用的trace信息

  • mapping:如果是基于springmvc的web应用,mapping这个endpoint将给出@RequestMapping相关信息

2.Actuator类endpoints

  • shutdown:用于关闭当前springboot应用的endpoint

  • dump:用于执行线程的dump操作

3.6.1 自定义应用的健康状态检查

应用的健康状态检查是很普遍的监控需求,springboot也预先通过org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration为我们提供了一些常见的监控检查支持,比如:

  • DataSourceHealthIndicator

  • DiskSpaceHealthIndicator

  • RedisHealthIndicator

  • SolrHealthIndicator

  • MongoHealthIndicator

也可以自己实现HealthIndicator并注册,通过继承AbstractHealthIndicator来实现。

spring框架支持依赖注入key的类型为String的Map,遇到这种类型的Map声明,spring框架将扫描容器中所有类型为T的bean,然后以该bean的name作为Map的key,以bean实例作为对应的value,从而构建一个Map并注入到依赖处。

3.6.2 开放的endpoints才真正“有用”

不管是spring-boot-starter-actuator默认提供的endpoint实现,还是我们自己实现的endpoint实现,我们需要将采集的信息暴露开放给外部监控者,或者允许外部监控者访问它们,这些endpoints才会真正发挥出它们的最大“功效”
JMX和http是endpoints对外开放访问最常见的方式,鉴于java的序列号漏洞以及JMX的远程访问防火墙问题,建议用http并配合安全防护将springboot应用的endpoints开放给外部监控者使用。

第四章 微服务实践

微服务就是在SOA的思想和原则指导下的一种实践方式,这种实践方式可以概括为以下几个特点:

  • 在开发阶段,通过相应的微框架提供标准化的统一的开发体验和支持

  • 在发布阶段,通过标准化的形式统一发布和管理。

  • 在运维阶段,通过标准化的方式统一维护数量庞大的微服务

4.1 使用springBoot构建微服务

使用开源的Dubbo开始。

4.1.1 创建基于Dubbo框架的springboot微服务

为什么要用springboot开发基于dubbo框架的微服务呢?原因就是我们将Dubbo微服务以springboot形式标准化了。如此以来,我们既可以享受springboot框架和周边的一系列研发支持,还可以用统一的形式发布、部署和运维,微服务的一个特点就是数量多。
不管是否使用springboot,基于dubbo框架的服务从其服务的定义,到服务的实现,都是常规而无法省略的工作,但是:
1)服务完成后,启动main函数里的逻辑貌似每次都要编写一样的?
2)服务完成后,以什么样的形式发布并部署?zip?jar?war?
一旦将dubbo服务以springboot的形式封装,dubbo服务就可以既享受springboot的开发便捷性,又能以统一的形式发布和部署(比如可执行的jar形式),虽然我们无法省略和简化服务的定义和实现这些步骤,但dubbo服务的main函数逻辑实际上是可以固化下来并且复用的,否则有可能实现的启动类不一样,导致运维操作不一样。
在main函数实现中,为了阻止dubbo服务的进程退出,使用的IO操作来达成这一目的(System.in.read()),但是不是一个好做法。

4.1.2 使用springboot快速构建web api

使用spring boot构建web api有几种选择,要么使用spring-boot-starte-jersey构建restFul风格的web api,要么选择spring-boot-starter-hateoas构建更加有关联性和相对“智能”的web api,对于大部分开发人员来说,http协议的get和post是直觉上最自然的选择,所以我们选择这个方式构建web api。
web api强调统一和互通,首选我们需要定义一套内外认知一直的web api开发和访问规范,在json盛行社群庞大的背景下,我们的web api方案采用json作为数据交互格式并定义同意的协议格式,然后通过http以及周边支持完成微服务的对外服务和开放访问。
1.定义web api规范
首先从服务访问交互上来说,我们可以选择较为纯粹的JSON RPC Over HTTP的方式image.png
也可以选择约束相对松一些的RPC Over HTTP方式
image.png
相对于纯粹的JSON PRC Over HTTP方案,后者对请求格式不做任何限制(所以同意支持纯粹JSON形式的请求格式),只对相应(Response)做JOSN格式上的统一规定,好处是,客户端各种工具都能很好的支持,服务端springmvc也可以少做HttpMessage转换,给服务的开发者和访问者都提供了比较灵活的操作余地,至于请求的类型差异,我们可以通过配套生成的api文档进行不足。
不管怎样,我们基于后一种方案进行说明,现在剩下的主要工作就是定义服务响应格式,只有规范和统一了服务的相应格式,才能让内部和外部的服务访问者形成统一的认知,以统一的方式“复制”对我们提供的任何web API的访问行为,减少用户的接入成本,所以,姑且我们简单规定一个服务的响应格式如下:

  1. {
  2. "code":1,
  3. "error":"msg",
  4. "data":{...}
  5. }

其中,code表示调用结果的状态,失败情况下error字段将提供对应的错误信息描述,data字段用于规范定义特定于web api的响应内容。
有了这样的规范定义,不同的开发者就可恶意根据情况选择打造对应的工具或者SDK了。而web api的服务提供者就可以根据情况选择打造对应的工具或者SDK了。而web api的服务提供者也同样可以根据规范考虑如何简化web api的开发,或者同约束减少规范认知不足可能导致的问题。
既然是使用spring boot构建web api,那么显然我们更加关注后者。
2.根据规范构建web api
使用spring initializr方式创建一个web项目,然后构建对应的controller对外提供web api的访问
3.web api的短板和补足
相对于dubbo这种强类型的服务框架,web api没有强类型的支持,在开发过程中,自然也无法享受到像ide自动提示之类的功能,所以对应web api的使用者来说,需要于web api的提供者沟通之后才能知道如何访问web api的详细信息,比如需要传那些参数,返回的响应结果又应该是什么格式的。
为了缓解这个问题,我们可以使用自动根据代码元信息生成api文档的方式来补足这块短板,像swagger已经是比较成熟的api文档方案了
不过,让每个webapi项目都自己去初始化api文档相关的设置,显然并不是很好的用户体验,为了服务到位,我们可以遵循springboot的行事风格,新建一个spring-boot-starter-webapi这样的自动配置模块,其提供的主要特性包括但不限于:

  • 提供针对我们web api规范的功能支持,即提供显式的强类型封装方式或者隐士的自动转换方式的功能实现。
  • 提供api文档相关功能的配置和设置
  • 提供统一的web api访问错误处理逻辑。

这样,任何web api的开发者和提供者只要新建springboot应用,然后依赖spring-boot-starter-webapi就可以自动享有以上所有特性支持了。

4.1.3 使用spring boot构建其他形式的微服务

目前为止,我们介绍了使用spring boot开发基于dubbo框架的微服务,以及使用spring boot开发web api形式的微服务,貌似两种都是PRC形式的微服务形式,但并非所有微服务都应该是PRC形式的,而且spring boot也没有对微服务的具体服务形式进行严格规定,正如我们之前所说的那样,spring boot只是提供的了一种微服务标准化实践方式而已
区别于PRC形式对外提供服务的微服务,这次我们尝试开发令一众微服务,这种微服务只响应外部事件并做处理,比如在外汇交易系统或者股票交易系统中,我们可以设置一个个独立的微服务,在接收到市场行情事件或者数据的时候,构建和归档蜡烛图数据,架构设计如下:
image.png
不考虑泛型以及服务的通用性,我们尝试使用akka框架actor模型来开发一个生成日线蜡烛图的spring boot微服务。
为了简化原型实现,我们将日线蜡烛图微服务抽象为两个部分。第一部分是针对市场数据来源的抽象,根据不同的供应商以及使用的不同交互协议类型,提供不同的实现类

4.2 spring boot微服务的发布与部署

基于spring boot的微服务开发完之后,现在是时候开始部署了。
spring boot框架只提供了一套基于可执行jar包格式的标准发布形式,但没有对部署做过多的界定,而且为了简化可执行jar包的生成,spring boot提供了相应的maven项目插件:

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.springframewor.boot</groupId>
  5. <artifiactId>spring-boot-maven-plugin</artifiactId>
  6. </plugin>
  7. </plugins>
  8. </build>

然后只要我们运行mvn package,当前spring boot项目就会被打包成一个包含了其所有项目依赖以及该项目本身的可执行jar包,通过scp或者rsync等方式将这个可执行jar包部署到目标环境服务器之后,就可以通过Java -jar xxx.jar 启动应用了。
整个流程看起来很简单,很符合开发人员的认知,但是,相当于一套较为严谨的软件交付流程来说,以上流程难免过于粗糙了。
发布并不等于部署,如图所示
image.png
发布一般是将项目以指定的格式打包成某种可以直接交付的形式,然后放置到预先指定的交付地点,比如对于java类库来说,我们一般将其打包成jar包,然后mvn deploy到公司内部maven仓库中,像nexus Repository Manager或者JFrog Artifactory以及Apache Archiva;而对于可独立运行的程序,比如spring boot微服务或者一边的java standalone程序,我们捷克语将他们打成RPM,DEB等面向特定目标系统的发布形式,也可以将他们制作成一个个的docker images,然后将制作完成的发布成品存储到相应的仓库中去。
部署一般紧接着发布完成之后进行,它的主要职能就是将已经发布好的成品从仓库中拿出来,然后分发到目标环境的指定资源池,并最终启动服务。

4.2.1 spring-boot-starter 的发布于部署方式

spring-boot-starter属于java类库性质的组件,只被其他可独立运行的程序依赖使用,不可独立运行,对应这种性质的软件实体,我们一般将其发布到公司内部的软件仓库或者以开源形式发布到maven的中央仓库,下面我们就以spring-boot-starter-metrics为例,如何发布。
我们需要在~/.m2/settings.xml配置nexus服务器访问和认证信息

  1. <server>
  2. <id>depolyment</id>
  3. <username>depolyment</username>
  4. <password>******</password>
  5. </server>
  6. <profile>
  7. <id>nexus</id>
  8. <repositories>
  9. <repository>
  10. <id>snapshots</id>
  11. <name>nexus</name>
  12. <url>http://xxx.xxx.com/nexus/content/repositories/snapshots</url>
  13. <release><enabled>false</enabled></release>
  14. <snapshots><enabled>false</enabled></snapshots>
  15. </repository>
  16. </repositories>
  17. </profile>

这些基本配置完成之后,使用mvn deploy就可以将包发布到内部仓库了。

4.2.2 基于RPM的发布与部署方式

部署的目标服务器从硬件到系统软件,一般情况下都应该尽量相同,这与软件的便准和目的相同,一个是可以减少应对不同类型实体的复杂度,另一个就是便准和硬件和软件之后,就可以通过工具批量化以“边际成本递减”近乎为0的做法来提升效率,减少成本。