SpringBoot RestTemplate 相关问题和解决思路
- 采用spring resttemplate方案,适用场景
- 被调服务端需要Bearer token验证的方案设计
- 被调服务端需要SSL验证的架构设计
- 如何实现请求失败重试,利用spring retry
- 如何快速进行模型转换,避免adapter,setter and getter.
- 如何对接口和方法进行缓存, 利用spring cache和hibernate 二级缓存技术
- 如何实现 x-trace功能,追踪整个请求链条。
- 后端需要向其他服务发起Rest请求调用,拿到结果并返回
使用Spring Rest Tempalte, https://spring.io/guides/gs/consuming-rest/
RestTemplate restTemplate = new RestTemplate();
- 被调用服务启用token机制,需要在每个请求里注入 Authorization: Beaer TOKEN****
为了使得获取和注入token机制与业务功能代码解耦,需要在resttemplate注入自定义interceptor https://www.tutorialspoint.com/spring_boot/spring_boot_interceptor.htm
interceptor file:
public class DemoInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution){
//get token here.
String tokenValue = String.format("%s %s", "Authorization", "XXXXXXXXXXX");
request.getHeaders().add("Authorization", tokenValue);
}
}
resttemplate add interceptor
DemoInterceptor ris = new DemoInterceptor();
restTemplate.setInterceptors(Arrays.asList({ris});
把cert文件生成jks证书, 生成方式参考:
openssl pkcs12 -export -in server.crt -inkey server.key -out server.pkcs12
keytool -importkeystore -srckeystore server.pkcs12 -destkeystore server-keystore.jks -srcstoretype pkcs12
新建ssl request builder
public class SslRequestFactoryBuilder {
private static Logger logger = LoggerFactory.getLogger(SslRequestFactoryBuilder.class);
public ClientHttpRequestFactory build(SslOption sslOption) {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (sslOption != null && sslOption.getEnable() != null && sslOption.getEnable()) {
logger.info("ssl connection is being enabled");
SSLContext sslContext = getSslContext(sslOption);
httpClientBuilder.setSSLContext(sslContext);
} else {
logger.info("ssl connection not active, use http instead");
}
CloseableHttpClient client = httpClientBuilder.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
ClientHttpRequestFactory bufferRequestFactory = new BufferingClientHttpRequestFactory(requestFactory);
return bufferRequestFactory;
}
private SSLContext getSslContext(SslOption sslOption) {
SSLContext sslContext;
try {
sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile(sslOption.getKeyStore()),
sslOption.getKeyStorePassword().toCharArray(),
sslOption.getKeyPass().toCharArray())
.loadTrustMaterial(ResourceUtils.getFile(sslOption.getTrustStore()),
sslOption.getTrustStorePassword().toCharArray())
.build();
} catch (Exception e) {
logger.error("ssl restTemplate initialize failed!");
throw new RuntimeException("ssl restTemplate initialize failed!", e);
}
return sslContext;
}
}
resttemplate add interceptor
ClientHttpRequestFactory requestFactory = builder.buildPool(sslOption)
restTemplate.setRequestFactory(requestFactory);
添加spring retry依赖 https://www.baeldung.com/spring-retry, enable retrying
@Retryable(
value = {RetryException.class},
maxAttemptsExpression = "2",
backoff = @Backoff(5000))
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
// if error, throw exception
throw new RetryException("retry");
return response;
}
添加 Dozer 转换框架, http://dozer.sourceforge.net/
compile('net.sf.dozer:dozer:5.4.0')
定义class A 和 B, A为数据DTO, B为领域模型
class A{
@mapping("at1")
private String attr1;
@mapping("at2")
private String attr2;
private String attr3;
...
}
class B{
private String at1;
private String at2;
...
}
源数据转为目标数据
Mapper mapper = new DozerBeanMapper();
B b = mapper.map(a, B.class)
采用 Spring Cache + ehcache方案,https://www.baeldung.com/spring-boot-ehcache
添加项目依赖,spring boot enable cache
compile('javax.cache:cache-api')
compile('org.ehcache:ehcache:3.6.1')
compile('org.springframework.boot:spring-boot-starter-cache')
新增cache配置
@Configuration
public class ProfileCacheConfig implements JCacheManagerCustomizer {
private static Logger logger = LoggerFactory.getLogger(ProfileCacheConfig.class);
@Autowired
private ProfileProperties profileProperties;
@Override
public void customize(CacheManager cacheManager) {
CacheProperty cacheProperty = profileProperties.getCache();
cacheManager.createCache("CACHE_NAME", new MutableConfiguration<>()
.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.MINUTES, cacheProperty.getProfileExpireMinutes())))
.setStoreByValue(false));
}
}
在方法上面添加cacheable注解,启用缓存
@Cacheable(value = "CACHE_NAME", key = "#param")
public void method(string param){
******
}
采用MDC方案,将生成的x-trace-Id注入到request,response的header里面
拦截每个前端发送给每个后端的请求, 继承HandlerInterceptorAdapter,生成x-trace-Id, 注入到 MDC context里面
public class XTraceInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String xTraceId = generateTraceId();
MDC.put("x-trace-Id", xTraceId);
response.setHeader("x-trace-Id", xTraceId);
return true;
}
private String generateTraceId() {
String randomId = UUID.randomUUID().toString();
String xTraceId = String.format("%s-%s", "demo", randomId);
return xTraceId;
}
}
拓展WebMvcConfigurationSupport,将XTraceInterceptor注入
public class AppConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
XTraceInterceptor xtrace = new XTraceInterceptor();
registry.addInterceptor(loggerInterceptor);
super.addInterceptors(registry);
}
}
restTemplate调用服务的时候,新建interceptor,在取出来注入到request header当中
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
String xTraceId = MDC.get(LoggerAttributeKeys.X_TRACE_ID);
if (xTraceId != null) {
request.getHeaders().add(LoggerAttributeKeys.X_TRACE_ID, xTraceId);
}
ClientHttpResponse response = execution.execute(request, body);
return response;
}
}
|