源码
gitee项目地址: https://gitee.com/gxkj_admin/easyspring
如果对您有帮助,请点赞收藏。谢谢
功能说明:
1:gateway的java代码实现
2:gateway增加权限验证
3:gateway与nacos整合
4:springboot与nacos整合
5:gateway与sentinel整合
6:springboot与sentinel整合
参考文档
- [spring中文网]https://www.springcloud.cc/spring-cloud-greenwich.html#_addrequestheader_gatewayfilter_factory
- [官网]https://spring.io/guides/gs/gateway/
- [官网例子]https://github.com/spring-cloud/spring-cloud-gateway/tree/main/spring-cloud-gateway-sample
- 熔断后共享线程信息 https://www.cnblogs.com/duanxz/p/10949816.html
实现功能
- Fluent java实现
- 自定义应用拦截器
- 自定义全局拦截器
- 熔断器功能例子
- 熔断器上下文地址信息获取
- 完整代码
代码实现
– 第一版
下列代码主要是实现路由:
import com.example.demogateway.filters.MyPreGatewayFilterFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 参考网址:
* https://www.springcloud.cc/spring-cloud-greenwich.html#_addrequestheader_gatewayfilter_factory
*/
@RestController
@SpringBootApplication
public class DemogatewayApplication {
@RequestMapping("/hystrixfallback")
public String hystrixfallback() {
return "This is a fallback";
}
@Autowired
private MyPreGatewayFilterFactory myPreGatewayFilterFactory;
// path注意以“/”开头,支持and|or组合使用
// order属性的数值越低,优先级越高
// 支持host和上传测试
// path可以使用通配符,例如:/foo/**
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
MyPreGatewayFilterFactory.Config conifg = new MyPreGatewayFilterFactory.Config();
RouteLocator locator = builder.routes()
// 支持路径测试
.route("path_route", r -> r.path("/get")
.filters(f -> f.filter(myPreGatewayFilterFactory.apply(conifg)))
.uri("http://httpbin.org:80"))
.route("host_form_route",
r -> r.order(2).host("**.myhost.org").and().path("/uploadimg3")
.filters(f -> f.filter(myPreGatewayFilterFactory.apply(conifg)))
.uri("http://localhost:81/uploadImg2"))
.route("path_route_complex", r -> r.path("/uploadimg4")
.filters(f -> f.filter(myPreGatewayFilterFactory.apply(conifg)))
.uri("http://localhost:81"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.build();
return locator;
}
@Bean
RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(1, 2);
}
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http.httpBasic().and()
.csrf().disable()
.authorizeExchange()
.pathMatchers("/anything/**").authenticated()
.anyExchange().permitAll()
.and()
.build();
}
@Bean
public MapReactiveUserDetailsService reactiveUserDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build();
return new MapReactiveUserDetailsService(user);
}
public static void main(String[] args) {
SpringApplication.run(DemogatewayApplication.class, args);
}
}
下列代码主要实现过滤器
package com.example.demogateway.filters;
import com.example.demogateway.utils.ResponseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata;
@Component
public class MyPreGatewayFilterFactory extends AbstractGatewayFilterFactory<MyPreGatewayFilterFactory.Config> {
private Logger logger = LoggerFactory.getLogger(MyPreGatewayFilterFactory.class);
public MyPreGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
if(path.equals("dologin") ||"dologout".equals(path)){
return chain.filter(exchange.mutate().request(request).build());
}else{
MultiValueMap<String, String> queryMap = request.getQueryParams();
String userName = queryMap.getFirst("userName");
// String userName = config.getName();
if(!StringUtils.hasText(userName)){
Mono<Void> info = ResponseUtils.getResponseTextError(exchange,"非法用户,请在参数中使用:userName=xxx来尝试验证");
return info;
}else if(!userName.equals("测试")){
Mono<Void> info = ResponseUtils.getResponseTextError(exchange,"请使用用户名:'测试'进行登录");
return info;
}
//If you want to build a "pre" filter you need to manipulate the
//request before calling chain.filter
// ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
//use builder to manipulate the request
return chain.filter(exchange.mutate().request(request).build());
}
};
}
public static class Config {
private String name;
public Config() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
下面代码主要是个工具:
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class ResponseUtils {
public static Mono<Void> getResponseTextError(ServerWebExchange exchange, String msg) {
ServerHttpResponse response = exchange.getResponse();
String repString = String.format("{\"code\":-1,\"message\",%s}",msg);
byte[] bits = repString.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
补充知识:
1: 本地跳转的方法。具体参考HystrixGatewayFilterFactory.java类。参考下面的代码,希望对forward,等有参考实现价值。
protected Observable<Void> resumeWithFallback() {
if (this.fallbackUri == null) {
return super.resumeWithFallback();
} else {
URI uri = this.exchange.getRequest().getURI();
boolean encoded = ServerWebExchangeUtils.containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host((String)null).port((String)null).uri(this.fallbackUri).build(encoded).toUri();
this.exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
ServerHttpRequest request = this.exchange.getRequest().mutate().uri(requestUrl).build();
ServerWebExchange mutated = this.exchange.mutate().request(request).build();
return RxReactiveStreams.toObservable(HystrixGatewayFilterFactory.this.dispatcherHandler.handle(mutated));
}
}
使用方式在官网的例子里:
.route("hystrix_fallback_route",
r -> r.host("*.hystrixfallback.org")
.filters(
f -> f.hystrix(c -> c.setName("slowcmd")
.setFallbackUri("forward:/hystrixfallback"))
)
.uri("http://httpbin.org"))
2:更多实现功能请参考网址:
[spring中文网]https://www.springcloud.cc/spring-cloud-greenwich.html#_addrequestheader_gatewayfilter_factory。里面有介绍负载均衡、限制次数等业务需求场景,大家可以自己实现。
3: 过滤器: SetPath、RequestSize(控制参数大小)、ModifyRequestBody(修改请求体,只能用java代码实现)、ModifyResponseBody(修改响应主体),GlobalFilter (全局过滤器)、ForwardRoutingFilter(转发过滤器)、LoadBalancerClientFilter(均衡过滤器)
4: 参看网址[spring中文网]中有前置过滤器PreGatewayFilterFactory 、后置过滤器PostGatewayFilterFactory、全局前置过滤器GlobalFilter customGlobalFilter和全局后置过滤器GlobalFilter customGlobalPostFilter 的例子
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: https://example.org
predicates:
- Path=/foo/{segment}
filters:
- SetPath=/{segment}
– -- ----- 版本2-- – -----优化点如下:
- 使用predicate
- 使用断路器
- 断路器传递MDC等上下文
参考文档增加:
https://blog.csdn.net/zhanglh046/article/details/78652133
代码如下:
启动类
import com.example.demogateway.filters.MyPreGatewayFilterFactory;
import com.example.demogateway.plugins.HystrixCallableWrapper;
import com.example.demogateway.plugins.MdcAwareCallableWrapper;
import com.example.demogateway.plugins.RequestAttributeAwareCallableWrapper;
import com.example.demogateway.plugins.RequestContextHystrixConcurrencyStrategy;
import com.example.demogateway.predicates.MyRoutePredicateFactory;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.strategy.HystrixPlugins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ServerWebExchange;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
/**
* 参考网址:
* https://www.springcloud.cc/spring-cloud-greenwich.html#_addrequestheader_gatewayfilter_factory
*/
@RestController
@SpringBootApplication
public class DemogatewayApplication {
private Logger logger = LoggerFactory.getLogger(DemogatewayApplication.class);
@Bean(name = "requestAttributeAwareCallableWrapper")
public HystrixCallableWrapper requestAttributeAwareCallableWrapper() {
return new RequestAttributeAwareCallableWrapper();
}
@Bean(name = "mdcAwareCallableWrapper")
public HystrixCallableWrapper mdcAwareCallableWrapper(){
return new MdcAwareCallableWrapper();
}
@Autowired(required = false)
private List<HystrixCallableWrapper> wrappers = new ArrayList<>();
@Autowired
@Qualifier(value = "requestAttributeAwareCallableWrapper" )
private HystrixCallableWrapper requestAttributeAwareCallableWrapper;
@Autowired
@Qualifier(value = "mdcAwareCallableWrapper" )
private HystrixCallableWrapper mdcAwareCallableWrapper;
@PostConstruct
public void init() {
wrappers.add(requestAttributeAwareCallableWrapper);
wrappers.add(mdcAwareCallableWrapper);
HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestContextHystrixConcurrencyStrategy(wrappers));
}
@RequestMapping("/hystrixfallback")
public String hystrixfallback() {
SecurityContext s = SecurityContext.NONE ;
String traceId = MDC.get("traceId");
logger.info("hystrixfallback-traceId={}",traceId);
return "This is a fallback";
}
@Autowired
private MyPreGatewayFilterFactory myPreGatewayFilterFactory;
// 熔断器
@Bean
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(DispatcherHandler dispatcherHandler) {
return new HystrixGatewayFilterFactory(dispatcherHandler);
}
// path注意以“/”开头,支持and|or组合使用
// order属性的数值越低,优先级越高
// 支持host和上传测试
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
MyPreGatewayFilterFactory.Config conifg = new MyPreGatewayFilterFactory.Config();
Consumer<HystrixGatewayFilterFactory.Config> configConsumer = new Consumer<HystrixGatewayFilterFactory.Config>() {
@Override
public void accept(HystrixGatewayFilterFactory.Config config) {
config.setFallbackUri("forward:/hystrixfallback");
config.setName("test3000");
}
};
RouteLocator locator = builder.routes()
.route("predicate_route", r -> r.order(1)
.predicate(exchange ->{
//自定义匹配器,如果匹配"/httpbin"开头,则跳转到对应的网站
ServerHttpRequest request = exchange.getRequest();
RequestPath requestPath = request.getPath();
String requestPathString = requestPath.value().toString();
boolean isMatch = requestPathString.startsWith("/httpbin");
return isMatch;
})
.filters(f->
{
return f.removeRequestHeader("Origin")
.rewritePath("/httpbin","")
.hystrix(configConsumer)
;
}
)
.uri("http://localhost:81"))
// 支持路径测试
.route("path_route", r -> r.order(2).path("/get")
.filters(f -> f.filter(myPreGatewayFilterFactory.apply(conifg)))
.uri("http://httpbin.org:80"))
.route("host_form_route",
r -> r.order(3).host("**.myhost.org").and().path("/uploadimg3")
.filters(f -> f.filter(myPreGatewayFilterFactory.apply(conifg)))
.uri("http://localhost:81/uploadImg2"))
.route("path_route_complex", r -> r.order(4).path("/uploadimg4")
.filters(f -> f.filter(myPreGatewayFilterFactory.apply(conifg)))
.uri("http://localhost:81"))
.route("hystrix_fallback_route", r -> r.order(5).host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.build();
return locator;
}
public static void main(String[] args) {
SpringApplication.run(DemogatewayApplication.class, args);
}
}
HystrixCallableWrapper类
import java.util.concurrent.Callable;
public interface HystrixCallableWrapper {
/**
* 包装Callable实例
*
* @param callable 待包装实例
* @param <T> 返回类型
* @return 包装后的实例
*/
<T> Callable<T> wrap(Callable<T> callable);
}
MdcAwareCallableWrapper类
import org.slf4j.MDC;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
public class MdcAwareCallableWrapper implements HystrixCallableWrapper {
@Override
public <T> Callable<T> wrap(Callable<T> callable) {
return new MdcAwareCallable<>(callable, MDC.getCopyOfContextMap());
}
private class MdcAwareCallable<T> implements Callable<T> {
private final Callable<T> delegate;
private final Map<String, String> contextMap;
public MdcAwareCallable(Callable<T> callable, Map<String, String> contextMap) {
this.delegate = callable;
this.contextMap = contextMap != null ? contextMap : new HashMap();
}
@Override
public T call() throws Exception {
try {
MDC.setContextMap(contextMap);
return delegate.call();
} finally {
MDC.clear();
}
}
}
}
RequestAttributeAwareCallableWrapper 类
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Callable;
public final class RequestAttributeAwareCallableWrapper implements HystrixCallableWrapper {
@Override
public <T> Callable<T> wrap(Callable<T> callable) {
return new RequestAttributeAwareCallable(callable, RequestContextHolder.getRequestAttributes());
}
static class RequestAttributeAwareCallable<T> implements Callable<T> {
private final Callable<T> delegate;
private final RequestAttributes requestAttributes;
RequestAttributeAwareCallable(Callable<T> callable, RequestAttributes requestAttributes) {
this.delegate = callable;
this.requestAttributes = requestAttributes;
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return delegate.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
}
RequestContextHystrixConcurrencyStrategy 类
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.Callable;
public class RequestContextHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private final Collection<HystrixCallableWrapper> wrappers;
public RequestContextHystrixConcurrencyStrategy(Collection<HystrixCallableWrapper> wrappers) {
this.wrappers = wrappers;
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return new CallableWrapperChain(callable, wrappers.iterator()).wrapCallable();
}
private static class CallableWrapperChain<T> {
private final Callable<T> callable;
private final Iterator<HystrixCallableWrapper> wrappers;
CallableWrapperChain(Callable<T> callable, Iterator<HystrixCallableWrapper> wrappers) {
this.callable = callable;
this.wrappers = wrappers;
}
Callable<T> wrapCallable() {
Callable<T> delegate = callable;
while (wrappers.hasNext()) {
delegate = wrappers.next().wrap(delegate);
}
return delegate;
}
}
}
MyRoutePredicateFactory 类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import java.util.function.Predicate;
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
private Logger logger = LoggerFactory.getLogger(MyRoutePredicateFactory.class);
public MyRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// grab configuration from Config object
return exchange -> {
//grab the request
ServerHttpRequest request = exchange.getRequest();
//take information from the request to see if it
//matches configuration.
return matches(config, request);
};
}
private boolean matches(Config config, ServerHttpRequest request) {
logger.info("请求到达");
return true;
}
public static class Config {
//Put the configuration properties for your filter here
}
}
这里做个全局过滤器,测试当熔断时traceId是否传到回调方法里
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;
import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
@Component
public class AccessLogGlobalFilter implements GlobalFilter {
private Logger log = LoggerFactory.getLogger(AccessLogGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
String traceId = getTraceId();
log.info("traceId={}",traceId);
MDC.put("traceId",traceId);
log.info("请求路径:{}", path);
RequestContextHolder.currentRequestAttributes().setAttribute("traceId", traceId, SCOPE_REQUEST);
long start = System.currentTimeMillis();
Mono<Void> rep = null;
try{
rep = chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
long end = System.currentTimeMillis();
log.info("请求路径:{},响应码:{},耗时:{},traceId="+traceId, path, statusCode,(end-start));
}));
}catch (Exception e){
throw e;
}finally {
// 这行不能要,否则熔断跳转后这个就丢失了 MDC.remove("traceId");
}
return rep;
}
public String getTraceId(){
String traceId = MDC.get("traceId");
if(StringUtils.hasLength(traceId)){
return traceId;
}else{
traceId = UUID.randomUUID().toString().replace("-", "");
return traceId;
}
}
}
配置不同熔断器的响应时间,在application.yml里如下配置
hystrix:
shareSecurityContext: true
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
test3000:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
熔断后的上线文传递主要参考的是下列网址:
https://www.cnblogs.com/duanxz/p/10949816.html
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>
<groupId>com.gxkj.easy-spring</groupId>
<artifactId>easy-spring-gateway</artifactId>
<version>1.0.5</version>
<packaging>jar</packaging>
<name>spring-cloud-gateway-sample</name>
<description>Demo project for Spring Cloud Gateway</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>