gpt4 book ai didi

java - 当通过异步执行程序使用 restTemplate 时,MockRestService 出现 ConcurrentModificationException

转载 作者:塔克拉玛干 更新时间:2023-11-02 19:53:35 26 4
gpt4 key购买 nike

尝试使用 spring 的 MockRestService 模拟 rest 响应以进行集成测试,但是当实际代码使用 rest 模板时 AbstractRequestExpectationManager 一直运行到 ConcurrentModificationException异步。

测试伪代码片段:

@Autowired
RestTemplate restTemplate;
MockRestServiceServer mockRestServiceServer;

@Test
public test() {
// given
mockRestServiceServer = MockRestServiceServer
.bindTo( restTemplate )
.ignoreExpectOrder() // supported from spring 4.3
.build();
prepareRestResponse( "/resource/url", "mock json content".getBytes() );
// when
myservice.refreshPricesForProductGroup( 2 );

// then
// assertions
}

private void prepareRestResponse( final String urlTail, final byte[] responseContent ) {
mockRestServiceServer
.expect( requestTo( endsWith( urlTail ) ) )
.andExpect( method( HttpMethod.GET ) )
.andRespond( withSuccess()
.body( responseContent )
.contentType( APPLICATION_JSON_UTF8 ) );
}

访问其余模板的实际代码:

@Autowired
Executor executor
@Autowired
PriceRestClient priceClient
@Autowired
ProductRestClient productClient

/../

private void refreshPricesForProductGroup( final int groupId ) {

List<Product> products = productClient.findAllProductsForGroup( groupId );

products.forEach( p ->
executor.execute( () -> {
final Price price = priceClient.getPrice( p.getId() );
priceRepository.updatePrice( price );
} )
);
}

PriceRestClient.getPrice() 执行简单的休息调用:

Price getPrice( String productId ) {

try {
ResponseEntity<byte[]> entity = restTemplate.exchange(
restUtil.getProductPriceDataUrl(),
HttpMethod.GET,
restUtil.createGzipEncodingRequestEntity(),
byte[].class,
productId );

if ( entity.getStatusCode() == HttpStatus.OK ) {
String body = restUtil.unmarshalGzipBody( entity.getBody() );
return priceEntityParser.parse( body );
}

} catch ( HttpClientErrorException e ) {
// TODO
} catch ( ResourceAccessException e ) {
// TODO
} catch ( IOException e ) {
// TODO
}

return null;
}

抛出的异常:

Exception in thread "AsyncExecutor-2" java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:711)
at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:734)
at org.springframework.test.web.client.AbstractRequestExpectationManager$RequestExpectationGroup.findExpectation(AbstractRequestExpectationManager.java:167)
at org.springframework.test.web.client.UnorderedRequestExpectationManager.validateRequestInternal(UnorderedRequestExpectationManager.java:42)
at org.springframework.test.web.client.AbstractRequestExpectationManager.validateRequest(AbstractRequestExpectationManager.java:71)
at org.springframework.test.web.client.MockRestServiceServer$MockClientHttpRequestFactory$1.executeInternal(MockRestServiceServer.java:286)
at org.springframework.mock.http.client.MockClientHttpRequest.execute(MockClientHttpRequest.java:93)
at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:93)
at com.mycompany.myproduct.web.client.HttpRequestInterceptorLoggingClient.interceptReq(HttpRequestInterceptorLoggingClient.java:32)
at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:85)
at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:69)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:596)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:475)
at com.mycompany.myproduct.rest.PriceRestClient.getPrice(PriceRestClient.java:48)
at com.mycompany.myproduct.service.ProductPriceSourcingService.lambda$null$29(ProductPriceSourcingService.java:132)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

我是不是做错了什么,或者可能是 MockRestService 的错误?

最佳答案

我只需要线程安全,所以我重构了 laur's answer略微删除了另一个功能(即时修改期望)。留在这里以供引用。

import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.test.web.client.AbstractRequestExpectationManager;
import org.springframework.test.web.client.RequestExpectation;
import org.springframework.test.web.client.UnorderedRequestExpectationManager;

import java.io.IOException;
import java.util.*;

public class SynchronizedUnorderedRequestExpectationManager extends AbstractRequestExpectationManager {
private final List<RequestExpectation> expectations = Collections.synchronizedList(new LinkedList());
private final List<ClientHttpRequest> requests = Collections.synchronizedList(new LinkedList());
private final SynchronizedRequestExpectationGroup remainingExpectations = new SynchronizedRequestExpectationGroup();

@Override
protected List<RequestExpectation> getExpectations() {
return this.expectations;
}

@Override
protected List<ClientHttpRequest> getRequests() {
return this.requests;
}

protected void afterExpectationsDeclared() {
this.remainingExpectations.updateAll(this.getExpectations());
}

public ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException {
RequestExpectation expectation = this.remainingExpectations.findExpectation(request);

if (expectation != null) {
ClientHttpResponse response = expectation.createResponse(request);
this.remainingExpectations.update(expectation);
return response;
}

throw this.createUnexpectedRequestError(request);
}

public void reset() {
super.reset();
this.remainingExpectations.reset();
}

protected static class SynchronizedRequestExpectationGroup extends RequestExpectationGroup {

private final Set<RequestExpectation> expectations = Collections.synchronizedSet(new LinkedHashSet<>());

@Override
public Set<RequestExpectation> getExpectations() {
return this.expectations;
}

@Override
public void updateAll(Collection<RequestExpectation> expectations) {
synchronized (expectations) {
super.updateAll(expectations);
}
}

@Override
public RequestExpectation findExpectation(ClientHttpRequest request) throws IOException {
synchronized (expectations) {
return super.findExpectation(request);
}
}
}
}

背景

代码基本复制自UnorderedRequestExpectationManager .不幸的是,这仍然是必要的,因为原始类与 AbstractRequestExpectationManager.RequestExpectationGroup 紧密耦合。这不是线程安全的。为了替换依赖这个类需要被重写。其他线程不安全集合依赖项(来自 AbstractRequestExpectationManager )被替换为覆盖 getExpectationsgetRequests方法。集合迭代由关键部分保护。

用法

RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate)
.build(new SynchronizedUnorderedRequestExpectationManager());

谢谢劳尔! (点赞)

关于java - 当通过异步执行程序使用 restTemplate 时,MockRestService 出现 ConcurrentModificationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37615183/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com