本文意在把工作中经常涉及到的技术要点整理出来,形成一个知识体系,结构化、系统化地概括Reactive响应式开发、Spring/Spring Boot/Spring Cloud、分布式知识及涉及到的其他常用的附加知识。
Spring Cloud 官网:https://spring.io/projects/spring-cloud/
Spring Cloud Alibaba 官网:https://spring.io/projects/spring-cloud-alibaba
Spring Cloud Github: https://github.com/spring-cloud
Spring Cloud Alibaba Github: https://github.com/alibaba/spring-cloud-alibaba
Spring 5 引入了Reactor,特点是event-based、非阻塞、异步。基于JDK 8以上,比如:CompletableFuture,Stream和Duration等,为 Java 、Groovy 和其他 JVM 语言提供了构建基于事件和数据驱动应用的抽象库。Reactor 性能相当高,在最新的硬件平台上,使用无堵塞分发器每秒钟可处理 1500 万事件。
Reactor 官网:https://projectreactor.io/ (reference doc: https://projectreactor.io/docs/core/release/reference/)
Publisher
Subscriber
Subscription
Processor
1. Publisher
Publisher 代表的就是一种可以生产无限数据的发布者,接口如下:
可以看到,Publisher 里的 subscribe 方法传入的是 Subscriber 接口,这里用的是回调,Publisher 根据收到的请求向当前订阅者 Subscriber 发送元素。
2. Subscriber
Subscriber 代表的是一种可以从发布者那里订阅并接收元素的订阅者,接口如下:
Subscriber 接口定义的这组方法构成了数据流请求和处理的基本流程,其中,onSubscribe() 从命名上看就是一个回调方法,当发布者的 subscribe() 方法被调用时就会触发这个回调。而在该方法中有一个参数 Subscription,可以把这个 Subscription 看作是一种用于订阅的上下文对象。Subscription 对象中包含了这次回调中订阅者想要向发布者请求的数据个数。
当订阅关系已经建立,那么发布者就可以调用订阅者的 onNext() 方法向订阅者发送一个数据。这个过程是持续不断的,直到所发送的数据已经达到 Subscription 对象中所请求的数据个数。这时候 onComplete() 方法就会被触发,代表这个数据流已经全部发送结束。而一旦在这个过程中出现了异常,那么就会触发 onError() 方法,我们可以通过这个方法捕获到具体的异常信息进行处理,而数据流也就自动终止了。
3. Subscription
Subscription 代表的就是一种订阅上下文对象,它在订阅者和发布者之间进行传输,从而在两者之间形成一种契约关系,接口如下:
这里的 request() 方法用于请求 n 个元素,订阅者可以通过不断调用该方法来向发布者请求数据;而 cancel() 方法显然是用来取消这次订阅。请注意,Subscription 对象是确保生产者和消费者针对数据处理速度达成一种动态平衡的基础,也是流量控制中实现背压机制的关键所在。
4. Processor
Processor 代表的就是订阅者和发布者的处理阶段,Processor 接口继承了 Publisher 和 Subscriber 接口。它用于转换发布者——订阅者管道中的元素。Processor 订阅类型 T 的数据元素,接收并转换为类型 R 的数据,并发布变换后的数据。可以拥有多个处理者。
发布订阅模式都有推和拉两种,需要在“推”模式和“拉”模式之间考虑一定的平衡性,从而优雅地实现流量控制。这就需要引出响应式系统中非常重要的一个概念——背压机制(Backpressure)。
什么是背压?简单来说就是下游能够向上游反馈流量请求的机制。通过前面的分析,我们知道如果消费者消费数据的速度赶不上生产者生产数据的速度时,它就会持续消耗系统的资源,直到这些资源被消耗殆尽。
这个时候,就需要有一种机制使得消费者可以根据自身当前的处理能力通知生产者来调整生产数据的速度,这种机制就是背压。采用背压机制,消费者会根据自身的处理能力来请求数据,而生产者也会根据消费者的能力来生产数据,从而在两者之间达成一种动态的平衡,确保系统的即时响应性。
Reactor的核心是Flux /Mono类型,它代表了数据或事件的流。
Reactor 异步序列:
Flux:
Flux代表0~n个元素的异步序列:
Flux类源码截图:
代码片段示例:
Mono:
Mono代表0个或1个元素的异步序列:
Mono
Mono类源码截图:
代码片段示例:
相较 Mono, Flux 是更通用的一种响应式组件,所以针对 Flux 的操作要比 Mono 更丰富。
另一方面,Flux 和Mono 之间可以相互转换。例如,把两个Mono 序列合并起来就得到一个Flux 序列,而对一个Flux 序列进行计数操作,得到的就是 Mono 对象。
创建 Flux
1. 静态创建:
(1)just()
just() 方法可以指定序列中包含的全部元素,创建出来的 Flux 序列在发布这些元素之后会自动结束。一般情况下,在已知元素数量和内容时,使用just() 方法是创建 Flux 的最简单的做法。
(2)fromArray()、fromIterable()、fromStream()
(3)range()
使用 range(int start, int count) 方法可以创建包含从start起始的count个对象的序列,序列中的所有对象类型都是 Integer,这在有些场景下非常有用。
(4)interval()
interval(Duration period) 方法用来创建一个包含从0开始递增的 Long 对象的序列,序列中的元素按照指定的时间间隔来发布。而 interval(Duration delay, Duration period)方法除了可以指定时间间隔,还可以指定起始元素发布之前的延迟时间。另外,intervalMillis(long period)和intervalMillis(long delay, long period)与前面两个方法的作用相同,只不过这两个方法通过亳秒数来指定时间间隔和延迟时间。使用 interval() 方法创建 Flux 对象的示意图:
(5)empty()、error()、never()
empty() 方法创建一个不包含任何元素而只发布结束消息的序列;
error() 方法创建一个只包含错误消息的序列;
never() 方法创建一个不包含任何消息通知的序列。
使用 empty()方法创建 Flux 对象的示意图:
2. 动态创建:
(1)generate()
generate() 产生Flux 序列依赖于 Reactor 所提供的 SynchronousSink 组件。 SynchronousSink 组件包括 next()、complete() 和 error(Throwable) 这三个核心方法。在具体的元素生成逻辑中,next() 方法最多只能被调用一次。
Flux.generate (sink -> {
sink.next ("Hello");
sink.complete();
]).subscribe (System.out::println);
(2)create()
create() 使用的是 FluxSink 组件。FluxSink 支持同步和异步的消息产生方式,并且可以在一次调用中产生多个元素。
Flux.create (sink -> {
for (int i = 0; i < 10; i++) {
sink.next(i);
}
sink.complete();
}).subscribe (System.out::printin);
创建 Mono
1. 静态创建:
包含了一些与Flux 中相同的静态方法,如 just()、empty()、error()和 never()等。除了这些方法,Mono 还有一些特有的静态方法,比较常见的包括 delay()、justOrEmpty()等。
(1)delay()
delay(Duration duration)和 delayMillis(long duration)方法可以用于创建 Mono。它们的特点是,在指定的延退时间之后会产生数宇 0 作为唯一值。
(2)justOrEmpty()
justOrEmpty(Optional<? extends T> data) 方法从一个Optional 对象创建 Mono,只有当 Optional 对象中包含值时,Mono 序列才产生对应的元素。而 justOrEmpty(T data)方法从一个可能为 null 的对象中创建 Mono,只有对象不为 null 时,Mono 序列才产生对应的元素。
2. 动态创建:
同样可以通过 create() 方法并使用 Monosink 组件。
Mono.create (sink ->
sink.success("Hello")).subscribe(System.out::println);
示例:
常见的有map、flatMap、buffer、window等。
映射操作,对流中每一个元素进行变换(transform)。
《Hands-On Reactive Programming in Spring 5》(Oleh Dokuka、Igor Lozynskyi著,即英文版《Spring响应式编程》)中,有一段我看过的解释flatMap最好的文字:
(1)可用指定元素个数,都是把流中的元素收集到Flux序列中。所不同的是,window 操作符是把当前流中的元素收集到另外的 Flux 序列中。因此,返回值类型是Flux
(2)buffer还可以指定收集的时间间隔,为一个Duration对象或毫秒。比如bufferUntil,bufferWhile,bufferTimeout,bufferMillis,bufferTimeoutMillis。
常见的有filter、first、last、skip/skipLast、take/takeLast等。
常见的有then/when、merge、startWith、zip等。
常见的有defaultEmpty、skipUntil、skipWhile、takeUntil、takeWhile等。
常见的有concat、count、reduce等。
reduce 操作符对流中包含的所有元素进行累积操作,得到一个包含计算结果的 Mono 序列。
常见的有delay、subcribe、timeout、block等。
通过 subscribe() 方法来添加相应的订阅逻辑。在调用 subscribe() 方法时可以指定需要处理的消息类型。
block 操作符常用来把响应式数据流转换为传统的数据流。例如,使用如下方法时,我们分别将 Flux 数据流和 Mono 数据流转变成了普通的 List
public List
return orderservice.getAllOrders ()
.block (Duration.ofSecond (5));
}
public Order getOrderById (Long orderId) {
return orderservice.getOrderById(orderId)
.block (Duration.ofSecond (2));
常见的有log、debug(Hooks.onOperator(providedHook ->
providedHook.operatorStacktrace()) 和 checkpoint)等。
<待整理>
官网:https://swagger.io/
Swagger 3:
访问地址:http://
通常情况下swagger只能在开发环境或测试环境下开启,生产环境下需要进行关闭的。而swagger的开启与关闭可在application.properties中进行配置:
# 生产环境需设置为false
springfox.documentation.swagger-ui.enabled=true
@EnableOpenApi
<待整理>
/**
* 鉴权认证
*/
@Slf4j
@Component
@AllArgsConstructor
public class AuthFilter implements GlobalFilter, Ordered {
private final ObjectMapper objectMapper;
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath();
log.info("Request path=[{}]", path);
// skip
if (StringUtils.contains(path, "/v3/api-docs") || StringUtils.contains(path, "/swagger-") || StringUtils.contains(path, "/xxx/generateJWT")) {
return chain.filter(exchange);
}
ServerHttpResponse resp = exchange.getResponse();
String headerToken = exchange.getRequest().getHeaders().getFirst(“X-xxx-xxx”);
if (StringUtils.isBlank(headerToken)) {
return unAuth(resp, "缺失令牌,鉴权失败");
}
String token = JwtUtil.getToken(headerToken);
// also can get expiry
Claims claims = JwtUtil.parseJWT(token);
if (claims == null) {
return unAuth(resp, "请求未授权");
}
/* // 如果向请求里加header,记得build
ServerHttpRequest host = exchange.getRequest().mutate().header("X-YYY-ZZZ", "test-header").build();
//将现在的request变成exchange对象
ServerWebExchange build = exchange.mutate().request(host).build();
return chain.filter(build);*/
// ServerHttpRequest serverHttpRequest = exchange.getRequest();
// String method = serverHttpRequest.getMethodValue();
// if("GET".equals(method)){
// MultiValueMap queryParams = serverHttpRequest.getQueryParams();
// log.info("Details: Path=[{}], Method=GET,params=[{}]", path, JSONObject.toJSONString(queryParams));
// }
return chain.filter(exchange);
}
private Mono unAuth(ServerHttpResponse resp, String msg) {
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String result = "";
try {
// set GatewayResponse
GatewayResponse gatewayResponse = new GatewayResponse();
gatewayResponse.setMsgCode(403);
gatewayResponse.setMsg(msg;
result = objectMapper.writeValueAsString(gatewayResponse);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
}
DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}
Spring Security 详情可参考:
https://blog.csdn.net/Beth_Chan/article/details/125080402
架构:
项目 parent
依赖管理:
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR4
pom
import
服务注册中心,服务发现,特性有失效剔除、服务保护。
1. pom.xml
org.springframework.cloud
spring-cloud-starter-eureka-server
2. 给 Spring Boot main application类加上 @EnableEurekaServer。 3. 配置文件 application.properties spring.application.name=servicediscovery server.port=8260 eureka.instance.hostname=localhost eureka.server.wait-time-in-ms-when-sync-empty=5 eureka.client.register-with-eureka=true eureka.client.fetch-registry=false eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka logging.level.org.springframework=INFO logging.level.com.cxf=DEBUG
application-eureka1.properties:
server.port=8260 eureka.instance.hostname=sdpeer1 eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://:8262/eureka,http:// :8263/eureka
application-eureka2.properties:
spring.application.name=servicediscovery server.port=8262 eureka.instance.hostname=sdpeer2 eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://:8260/eureka,http:// :8263/eureka
application-eureka3.properties:
spring.application.name=servicediscovery server.port=8263 eureka.instance.hostname=sdpeer3 eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://:8260/eureka,http:// :8262/eureka
示例
启动程序:(或者使用IDE:Intellij IDEA/STS都可以)
java -jar "/Users/cxf/IT/code/springcloud-demo/service-discovery/target/service-discovery-0.0.1-SNAPSHOT.jar"
java -jar "/Users/cxf/IT/code/springcloud-demo/hello-provider/target/hello-provider-0.0.1-SNAPSHOT.jar"
java -jar "/Users/cxf/IT/code/springcloud-demo/hello-consumer/target/hello-consumer-0.0.1-SNAPSHOT.jar"
访问 http://localhost:8260/,可看到:
consumer依赖:org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-feign
配置文件:
spring.application.name=hc-service eureka.instance.prefer-ip-address=true eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://localhost:8260/eureka #feign.hystrix.enabled=true logging.level.org.springframework=INFO logging.level.com.cxf=DEBUG
main application:
@EnableDiscoveryClient @EnableFeignClients
controller endpoint:
@RestController @RequestMapping("/hello") public class HelloConsumerEndpoint { @Autowired private HelloService helloService; /** * Hello * @return */ @RequestMapping(value = "/{name}", method = RequestMethod.GET) public String hello(@PathVariable String name){ return this.helloService.hello(name); } }
service:
FeignCilent:
@FeignClient(value = "HP-SERVICE", fallback = HelloServiceFallback.class)
public interface HelloService {
@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
String hello(@PathVariable("name") String name);
}
Fallback:
@Component
public class HelloServiceFallback implements HelloService {
public String hello(String name) {
return "Hello, " + name + ", I'm fallback!";
}
}
http://localhost:8260/eureka/apps/hc-service,可查看服务详细信息
hp-service 服务:
Provider:
pom.xml:
org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-eureka
Main application:
@EnableDiscoveryClient
Controller API:
@RestController @RequestMapping("/hello") public class HelloProviderEndpoint { @RequestMapping(value = "/{name}", method = RequestMethod.GET) public String hello(@PathVariable String name){ return "Hello, " + name + "!"; } }
provider 配置:
server.port=2100
spring.application.name=hp-service
eureka.instance.prefer-ip-address=true
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://localhost:8260/eureka
logging.level.org.springframework=INFO
logging.level.com.cxf=DEBUG
Postman 里可看到:
即:
在程序里也做了容错机制:
可作为依赖中心、配置中心。
依赖:
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery fastjson com.alibaba com.google.guava guava com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config
<待整理>
功能有路由分发和过滤。
在Zuul中,服务路由信息的设置包括基于服务发现映射服务路由和基于动态配置映射服务路由。
pom.xml:
org.springframework.cloud spring-cloud-starter-zuul
配置文件:
# omit server port, application name & eureka config
zuul.prefix=/api # 请求地址的前缀 zuul.routes.userservice = /user/** # 自定义服务路由的映射 zuul.ignored-services=userservice # 将系统自动映射的路由从服务路由中删除
Main application:
@EnableZuulProxy
可通过 http:
比如:
{
"/api/user/**": "userservice"
}
旧网关Netflix Zuul 1.x只是性能一般的网关,加上Netflix Zuul 2.x版本经常不能如期发布,所以新版的Spring Cloud不打算捆绑Zuul了。新版的Spring Cloud提供了新的网关给开发者使用,这便是Spring Cloud Gateway。为了简便,下文在没有特别指明的情况下,将简称它为Gateway。Gateway并非是使用传统的Jakarta EE的Servlet容器,它是采用响应式编程的方式进行开发的。在Gateway中,需要Spring Boot和Spring WebFlux提供的基于Netty的运行环境。那么为什么Zuul 1.x是一个性能一般的网关,而Gateway又与它有什么不同呢?Zuul 1.x是基于传统的Jakarta EE的Servlet容器方式的,而Gateway是基于响应式方式的,其内部执行方式的不同,决定了二者的性能完全不一样。
从执行的原理来说,Zuul会为一个请求分配一条线程,然后通过执行不同类型的过滤器来完成路由的功能。但是请注意,这条线程会等route类型的过滤器去调用源服务器,显然这是线程执行过程中最为缓慢的一步,因为源服务器可能会因执行比较复杂的业务而响应特别慢,这样Zuul中的线程就需要执行比较长的时间,容易造成线程积压,导致性能变慢。从图中可以看到,请求达到Gateway后,便由Gateway的组件进行处理。Gateway的组件是这样处理的:
●创建一条线程,通过类似Zuul的过滤器拦截请求;
●对源服务器转发请求,但注意,Gateway并不会等待请求调用源服务器的过程,而是将处理线程挂起,这样便不再占用资源了;
●等源服务器返回消息后,再通过寻址的方式来响应之前客户端发送的请求。
从上述过程描述中大家可以看到,Gateway线程在处理请求的时候,仅仅是负责转发请求到源服务器,并不会等待源服务器执行完成,要知道源服务器执行是最缓慢的一步。因此Gateway的线程活动的时间会更短,线程积压的概率更低,性能相对Zuul来说也更好。事实上,Gateway除了性能更好之外,它还顺应了响应式、函数式和Lambda表达式的潮流,允许开发者通过它们灵活地构建微服务网关。
<待整理>
客户端负载均衡,特性有区域亲和、重试机制。(类似nginx)
Resilience4j 容错包括:断路器(Circuit Breaker)、隔板(BulkHead)、重试(Retry)、限时器(Time Limiter)、限流器(Rate Limiter)。
依赖引入:
io.github.resilience4j
resilience4j-spring-boot2
org.springframework.boot
spring-boot-starter-aop
CircuitBreaker
示例:
resilience4j.circuitbreaker:
instances:
xxx:
register-health-indicator: true
sliding-window-type: TIME_ BASED
sliding-window-size: 100
minimum-number-of-calls: 50
failure-rate-threshold: 50
automatic-transition-from-open-to-half-open-enabled: true
permitted-number-of-cal1s-in-half-open-state: 5
wait-duration-in-open-state: 5s
record-exceptions:
- java.lang.Throwable
- org.springframework.web.client.HttpServerErrorException
CircuitBreakerConfig
CircuitBreakerRegistry
Bulkhead
什么是bulkhead?
bulkhead是舱壁,隔板的意思,术语舱壁来自它在船舶中的使用,其中船舶的底部被分成彼此分开的部分。如果有裂缝,并且有水流入,则只有该部分会充满水。这可以防止整艘船沉没。
为什么要使用bulkhead?
故障隔离背后的思想是控制并发调用的数量,将不同调用视为不同的、隔离的池,可以避免某类调用异常或占用过多资源,危及系统整体。
如何使用bulkhead?
Resilience4j提供了两种类型的隔板:信号量Semaphore或线程池ThreadPool(使用了一个有界队列ArrayBlockingQueue和一个固定数量的线程池)。
在Resilience4j中使用舱壁,如果采用代码的形式,则需要创建舱壁配置(BulkheadConfig)、注册机(BulkheadRegistry)和单个舱壁 (Bulkhead)。
采用 resilience4j-spring-boot2 包则可采用下列简单的例子。配置示例:
#resilience4j.bulkhead:
# instance:
# xxx:
# max-wait-duration: 1000ms
# max-concurrent-calls: 20
resilience4j.thread-pool-bulkhead:
instance:
xxx:
max-thread-pool-size: 5
core-thread-pool-size: 5
queue-capacity: 1
配置参数说明:
信号量
属性 | 默认值 | 描述 |
max-wait-duration | 0 | 获取信号量的最长排队等待时间 |
max-concurrent-calls | 25 | 最大并发 |
线程池
属性 | 默认值 | 描述 |
max-thread-pool-size | Runtime.getRuntime().avaliableProcess() | 最大线程数 |
core-thread-pool-size | Runtime.getRuntime().avaliableProcess() - 1 | 最少线程数 |
queue-capacity | 100 | 等待队列大小 |
keep-alive-duration | 20[ms] | 线程空闲时,销毁前的等待时间 |
代码示例:
@Bulkhead(name = "xxx", fallbackMethod = "fallback")
public String method(String param1) {
return "test";
}
private String fallback(String param1, IllegalArgumentException e) {
return "test illegal argument fallback";
}
private String fallback(String param1, Exception e) {
return "test fallback";
}
重要的是要记住,回退方法应该放在同一个类中,并且必须具有相同的方法签名,只有一个额外的目标异常参数。
如果有多个 fallbackMethod 方法,将调用最接近匹配的方法。上面配置了20个并发限制,如果超过20个并发,会进入第二个fallback方法。如果方法抛出IllegalArgumentException,则会进入第一个fallback方法。
Retry
示例:
resilience4j.retry:
instances:
xxx:
max-attempts: 3
wait-duration: 10s
enable-exponential-backoff: true
exponential-backoff-multiplier: 2
retry-exceptions:
- org.spring{ramework.web.client.HttpServerErrorException
- java.io.I0Exception
RetryConfig
RetryRegistry
RateLimiter
示例:
resilience4j.ratelimiter:
instances:
xxx:
limit-for-period: 1
limit-refresh-period: 1s
timeout-duration: 0
register-health-indicator: true
event-consumer-buffer-size: 100
RateLimiterConfig
RateLimiterRegistry
TimeLimiter
示例:
resilience4j.timelimiter:
instances:
xxx:
timeout-duration: 2s
TimeLimiterConfig
Cache
<待整理>
可参考阅读:
https://www.cnblogs.com/crazymakercircle/p/14285001.html
客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。(怕访问过于频繁服务挂了,进行限流,太频繁的请求就直接拒绝)。当某个服务发生故障后,触发熔断机制向服务调用方返回结果标识错误,而不是一直等待服务提供方返回结果,这样就不会使得线程因调用故障服务而被长时间占用不释放,避免故障在分布式系统中的蔓延。
分布式配置中心,支持本地仓库、SVN、Git、Jar包内配置等模式,避免分布式架构下多微服务的多配置文件出错。
分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。(服务多了,调用的线路就会很复杂,需要跟踪来知道你到底是怎么走的)
<待整理>
推荐参考阅读:
基于Maven提供Spring-boot-devtools来监控应用中各文件,当发生变动后自动触发重启应用。
Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。
Bus,消息总线,配合Config仓库修改的一种Stream实现,用于广播消息。
Java实时处理 - Spring Integration - MQ Message_channeladapter_Beth_Chan的博客-CSDN博客
<待整理>
<待整理>
<待整理>
分表、分库、分区
Sharding 分片
Alibaba Seata;
Spring Cloud: 支持XA协议, Java实现JTA, Java Transaction API规范的有Atomikos, Biytonix和Narayana, 选用Atomikos
Redis
<待整理>
<待整理>
<待整理>
<待整理>
Jasypt
<待整理>
log4j2:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
Spring默认是Jackson。
<待整理>
<待整理>
<待整理>
<待整理>
<待整理>