我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。
TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。
步骤 1
创建一个抽象类,它的模板方法被设置为 final。Game.java
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
步骤 2
创建扩展了上述类的实体类。Cricket.java
public class Cricket extends Game {
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
}
Football.java
public class Football extends Game {
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
}
步骤 3
使用 Game 的模板方法 play() 来演示游戏的定义方式。TemplatePatternDemo.java
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}
步骤 4
执行程序,输出结果:
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!
Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!
The RestTemplate offers templates for common scenarios by HTTP method, in
* addition to the generalized {@code exchange} and {@code execute} methods that
* support of less frequent cases.
*
*
NOTE: As of 5.0 this class is in maintenance mode, with
* only minor requests for changes and bugs to be accepted going forward. Please,
* consider using the {@code org.springframework.web.reactive.client.WebClient}
* which has a more modern API and supports sync, async, and streaming scenarios.
*
* @author Arjen Poutsma
* @author Brian Clozel
* @author Roy Clarkson
* @author Juergen Hoeller
* @author Sam Brannen
* @author Sebastien Deleuze
* @since 3.0
* @see HttpMessageConverter
* @see RequestCallback
* @see ResponseExtractor
* @see ResponseErrorHandler
*/
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
package org.springframework.http.client.support;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Base class for {@link org.springframework.web.client.RestTemplate}
* and other HTTP accessing gateway helpers, adding interceptor-related
* properties to {@link HttpAccessor}'s common properties.
*
*
Not intended to be used directly.
* See {@link org.springframework.web.client.RestTemplate} for an entry point.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @see ClientHttpRequestInterceptor
* @see InterceptingClientHttpRequestFactory
* @see org.springframework.web.client.RestTemplate
*/
public abstract class InterceptingHttpAccessor extends HttpAccessor {
package org.springframework.web.client;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
/**
* Interface specifying a basic set of RESTful operations.
* Implemented by {@link RestTemplate}. Not often used directly, but a useful
* option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @see RestTemplate
*/
public interface RestOperations {
模板核心执行请求方法:
// General execution
/**
* {@inheritDoc}
*
To provide a {@code RequestCallback} or {@code ResponseExtractor} only,
* but not both, consider using:
*
/**
* Execute the given method on the provided URI.
*
The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
* the response with the {@link ResponseExtractor}.
* @param url the fully-expanded URL to connect to
* @param method the HTTP method to execute (GET, POST, etc.)
* @param requestCallback object that prepares the request (can be {@code null})
* @param responseExtractor object that extracts the return value from the response (can be {@code null})
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
@Nullable
protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
RequestCallback功能性接口定义:
package org.springframework.web.client;
import java.io.IOException;
import java.lang.reflect.Type;
import org.springframework.http.client.ClientHttpRequest;
/**
* Callback interface for code that operates on a {@link ClientHttpRequest}.
* Allows manipulating the request headers, and write to the request body.
*
*
Used internally by the {@link RestTemplate}, but also useful for
* application code. There several available factory methods:
*
*
* @author Arjen Poutsma
* @see RestTemplate#execute
* @since 3.0
*/
@FunctionalInterface
public interface RequestCallback {
/**
* Gets called by {@link RestTemplate#execute} with an opened {@code ClientHttpRequest}.
* Does not need to care about closing the request or about handling errors:
* this will all be handled by the {@code RestTemplate}.
* @param request the active HTTP request
* @throws IOException in case of I/O errors
*/
void doWithRequest(ClientHttpRequest request) throws IOException;
}
示例程序:
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInitializer;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.HEAD;
import static org.springframework.http.HttpMethod.OPTIONS;
import static org.springframework.http.HttpMethod.PATCH;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;
import static org.springframework.http.MediaType.parseMediaType;
/**
* Unit tests for {@link RestTemplate}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sam Brannen
*/
@SuppressWarnings("unchecked")
class RestTemplateTests {
private final ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class);
private final ClientHttpRequest request = mock(ClientHttpRequest.class);
private final ClientHttpResponse response = mock(ClientHttpResponse.class);
private final ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
@SuppressWarnings("rawtypes")
private final HttpMessageConverter converter = mock(HttpMessageConverter.class);
private final RestTemplate template = new RestTemplate(Collections.singletonList(converter));
@BeforeEach
void setup() {
template.setRequestFactory(requestFactory);
template.setErrorHandler(errorHandler);
}
@Test
void constructorPreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new RestTemplate((List>) null))
.withMessage("At least one HttpMessageConverter is required");
assertThatIllegalArgumentException()
.isThrownBy(() -> new RestTemplate(Arrays.asList(null, this.converter)))
.withMessage("The HttpMessageConverter list must not contain null elements");
}
@Test
void setMessageConvertersPreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> template.setMessageConverters((List>) null))
.withMessage("At least one HttpMessageConverter is required");
assertThatIllegalArgumentException()
.isThrownBy(() -> template.setMessageConverters(Arrays.asList(null, this.converter)))
.withMessage("The HttpMessageConverter list must not contain null elements");
}
@Test
void varArgsTemplateVariables() throws Exception {
mockSentRequest(GET, "https://example.com/hotels/42/bookings/21");
mockResponseStatus(HttpStatus.OK);
template.execute("https://example.com/hotels/{hotel}/bookings/{booking}", GET,
null, null, "42", "21");
verify(response).close();
}
@Test
void varArgsNullTemplateVariable() throws Exception {
mockSentRequest(GET, "https://example.com/-foo");
mockResponseStatus(HttpStatus.OK);
template.execute("https://example.com/{first}-{last}", GET, null, null, null, "foo");
verify(response).close();
}
@Test
void mapTemplateVariables() throws Exception {
mockSentRequest(GET, "https://example.com/hotels/42/bookings/42");
mockResponseStatus(HttpStatus.OK);
Map vars = Collections.singletonMap("hotel", "42");
template.execute("https://example.com/hotels/{hotel}/bookings/{hotel}", GET, null, null, vars);
verify(response).close();
}
@Test
void mapNullTemplateVariable() throws Exception {
mockSentRequest(GET, "https://example.com/-foo");
mockResponseStatus(HttpStatus.OK);
Map vars = new HashMap<>(2);
vars.put("first", null);
vars.put("last", "foo");
template.execute("https://example.com/{first}-{last}", GET, null, null, vars);
verify(response).close();
}
@Test // SPR-15201
void uriTemplateWithTrailingSlash() throws Exception {
String url = "https://example.com/spring/";
mockSentRequest(GET, url);
mockResponseStatus(HttpStatus.OK);
template.execute(url, GET, null, null);
verify(response).close();
}
@Test
void errorHandling() throws Exception {
String url = "https://example.com";
mockSentRequest(GET, url);
mockResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR);
willThrow(new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR))
.given(errorHandler).handleError(new URI(url), GET, response);
assertThatExceptionOfType(HttpServerErrorException.class).isThrownBy(() ->
template.execute(url, GET, null, null));
verify(response).close();
}
@Test
void getForObject() throws Exception {
String expected = "Hello World";
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(GET, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
mockTextResponseBody("Hello World");
String result = template.getForObject("https://example.com", String.class);
assertThat(result).as("Invalid GET result").isEqualTo(expected);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
verify(response).close();
}
@Test
void getUnsupportedMediaType() throws Exception {
mockSentRequest(GET, "https://example.com/resource");
mockResponseStatus(HttpStatus.OK);
given(converter.canRead(String.class, null)).willReturn(true);
MediaType supportedMediaType = new MediaType("foo", "bar");
given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(supportedMediaType));
MediaType barBaz = new MediaType("bar", "baz");
mockResponseBody("Foo", new MediaType("bar", "baz"));
given(converter.canRead(String.class, barBaz)).willReturn(false);
assertThatExceptionOfType(RestClientException.class).isThrownBy(() ->
template.getForObject("https://example.com/{p}", String.class, "resource"));
verify(response).close();
}
@Test
void requestAvoidsDuplicateAcceptHeaderValues() throws Exception {
HttpMessageConverter> firstConverter = mock(HttpMessageConverter.class);
given(firstConverter.canRead(any(), any())).willReturn(true);
given(firstConverter.getSupportedMediaTypes())
.willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
HttpMessageConverter> secondConverter = mock(HttpMessageConverter.class);
given(secondConverter.canRead(any(), any())).willReturn(true);
given(secondConverter.getSupportedMediaTypes())
.willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(GET, "https://example.com/", requestHeaders);
mockResponseStatus(HttpStatus.OK);
mockTextResponseBody("Hello World");
template.setMessageConverters(Arrays.asList(firstConverter, secondConverter));
template.getForObject("https://example.com/", String.class);
assertThat(requestHeaders.getAccept().size()).as("Sent duplicate Accept header values").isEqualTo(1);
}
@Test
void getForEntity() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(GET, "https://example.com", requestHeaders);
mockTextPlainHttpMessageConverter();
mockResponseStatus(HttpStatus.OK);
String expected = "Hello World";
mockTextResponseBody(expected);
ResponseEntity result = template.getForEntity("https://example.com", String.class);
assertThat(result.getBody()).as("Invalid GET result").isEqualTo(expected);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type header").isEqualTo(MediaType.TEXT_PLAIN);
assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
verify(response).close();
}
@Test
void getForObjectWithCustomUriTemplateHandler() throws Exception {
DefaultUriBuilderFactory uriTemplateHandler = new DefaultUriBuilderFactory();
template.setUriTemplateHandler(uriTemplateHandler);
mockSentRequest(GET, "https://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150");
mockResponseStatus(HttpStatus.OK);
given(response.getHeaders()).willReturn(new HttpHeaders());
given(response.getBody()).willReturn(StreamUtils.emptyInput());
Map uriVariables = new HashMap<>(2);
uriVariables.put("hotel", "1");
uriVariables.put("publicpath", "pics/logo.png");
uriVariables.put("scale", "150x150");
String url = "https://example.com/hotels/{hotel}/pic/{publicpath}/size/{scale}";
template.getForObject(url, String.class, uriVariables);
verify(response).close();
}
@Test
void headForHeaders() throws Exception {
mockSentRequest(HEAD, "https://example.com");
mockResponseStatus(HttpStatus.OK);
HttpHeaders responseHeaders = new HttpHeaders();
given(response.getHeaders()).willReturn(responseHeaders);
HttpHeaders result = template.headForHeaders("https://example.com");
assertThat(result).as("Invalid headers returned").isSameAs(responseHeaders);
verify(response).close();
}
@Test
void postForLocation() throws Exception {
mockSentRequest(POST, "https://example.com");
mockTextPlainHttpMessageConverter();
mockResponseStatus(HttpStatus.OK);
String helloWorld = "Hello World";
HttpHeaders responseHeaders = new HttpHeaders();
URI expected = new URI("https://example.com/hotels");
responseHeaders.setLocation(expected);
given(response.getHeaders()).willReturn(responseHeaders);
URI result = template.postForLocation("https://example.com", helloWorld);
assertThat(result).as("Invalid POST result").isEqualTo(expected);
verify(response).close();
}
@Test
void postForLocationEntityContentType() throws Exception {
mockSentRequest(POST, "https://example.com");
mockTextPlainHttpMessageConverter();
mockResponseStatus(HttpStatus.OK);
String helloWorld = "Hello World";
HttpHeaders responseHeaders = new HttpHeaders();
URI expected = new URI("https://example.com/hotels");
responseHeaders.setLocation(expected);
given(response.getHeaders()).willReturn(responseHeaders);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(MediaType.TEXT_PLAIN);
HttpEntity entity = new HttpEntity<>(helloWorld, entityHeaders);
URI result = template.postForLocation("https://example.com", entity);
assertThat(result).as("Invalid POST result").isEqualTo(expected);
verify(response).close();
}
@Test
void postForLocationEntityCustomHeader() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockTextPlainHttpMessageConverter();
mockResponseStatus(HttpStatus.OK);
HttpHeaders responseHeaders = new HttpHeaders();
URI expected = new URI("https://example.com/hotels");
responseHeaders.setLocation(expected);
given(response.getHeaders()).willReturn(responseHeaders);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.set("MyHeader", "MyValue");
HttpEntity entity = new HttpEntity<>("Hello World", entityHeaders);
URI result = template.postForLocation("https://example.com", entity);
assertThat(result).as("Invalid POST result").isEqualTo(expected);
assertThat(requestHeaders.getFirst("MyHeader")).as("No custom header set").isEqualTo("MyValue");
verify(response).close();
}
@Test
void postForLocationNoLocation() throws Exception {
mockSentRequest(POST, "https://example.com");
mockTextPlainHttpMessageConverter();
mockResponseStatus(HttpStatus.OK);
URI result = template.postForLocation("https://example.com", "Hello World");
assertThat(result).as("Invalid POST result").isNull();
verify(response).close();
}
@Test
void postForLocationNull() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
template.postForLocation("https://example.com", null);
assertThat(requestHeaders.getContentLength()).as("Invalid content length").isEqualTo(0);
verify(response).close();
}
@Test
void postForObject() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
String expected = "42";
mockResponseBody(expected, MediaType.TEXT_PLAIN);
String result = template.postForObject("https://example.com", "Hello World", String.class);
assertThat(result).as("Invalid POST result").isEqualTo(expected);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
verify(response).close();
}
@Test
void postForEntity() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
String expected = "42";
mockResponseBody(expected, MediaType.TEXT_PLAIN);
ResponseEntity result = template.postForEntity("https://example.com", "Hello World", String.class);
assertThat(result.getBody()).as("Invalid POST result").isEqualTo(expected);
assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
verify(response).close();
}
@Test
void postForObjectNull() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
responseHeaders.setContentLength(10);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(StreamUtils.emptyInput());
given(converter.read(String.class, response)).willReturn(null);
String result = template.postForObject("https://example.com", null, String.class);
assertThat(result).as("Invalid POST result").isNull();
assertThat(requestHeaders.getContentLength()).as("Invalid content length").isEqualTo(0);
verify(response).close();
}
@Test
void postForEntityNull() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
responseHeaders.setContentLength(10);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(StreamUtils.emptyInput());
given(converter.read(String.class, response)).willReturn(null);
ResponseEntity result = template.postForEntity("https://example.com", null, String.class);
assertThat(result.hasBody()).as("Invalid POST result").isFalse();
assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
assertThat(requestHeaders.getContentLength()).as("Invalid content length").isEqualTo(0);
assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
verify(response).close();
}
@Test
void put() throws Exception {
mockTextPlainHttpMessageConverter();
mockSentRequest(PUT, "https://example.com");
mockResponseStatus(HttpStatus.OK);
template.put("https://example.com", "Hello World");
verify(response).close();
}
@Test
void putNull() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(PUT, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
template.put("https://example.com", null);
assertThat(requestHeaders.getContentLength()).as("Invalid content length").isEqualTo(0);
verify(response).close();
}
@Test // gh-23740
void headerAcceptAllOnPut() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setResponseCode(500).setBody("internal server error"));
server.start();
template.setRequestFactory(new SimpleClientHttpRequestFactory());
template.put(server.url("/internal/server/error").uri(), null);
assertThat(server.takeRequest().getHeader("Accept")).isEqualTo("*/*");
}
}
@Test // gh-23740
void keepGivenAcceptHeaderOnPut() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setResponseCode(500).setBody("internal server error"));
server.start();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity entity = new HttpEntity<>(null, headers);
template.setRequestFactory(new SimpleClientHttpRequestFactory());
template.exchange(server.url("/internal/server/error").uri(), PUT, entity, Void.class);
RecordedRequest request = server.takeRequest();
final List> accepts = request.getHeaders().toMultimap().entrySet().stream()
.filter(entry -> entry.getKey().equalsIgnoreCase("accept"))
.map(Entry::getValue)
.collect(Collectors.toList());
assertThat(accepts).hasSize(1);
assertThat(accepts.get(0)).hasSize(1);
assertThat(accepts.get(0).get(0)).isEqualTo("application/json");
}
}
@Test
void patchForObject() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(PATCH, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
String expected = "42";
mockResponseBody("42", MediaType.TEXT_PLAIN);
String result = template.patchForObject("https://example.com", "Hello World", String.class);
assertThat(result).as("Invalid POST result").isEqualTo(expected);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
verify(response).close();
}
@Test
void patchForObjectNull() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(PATCH, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
responseHeaders.setContentLength(10);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(StreamUtils.emptyInput());
String result = template.patchForObject("https://example.com", null, String.class);
assertThat(result).as("Invalid POST result").isNull();
assertThat(requestHeaders.getContentLength()).as("Invalid content length").isEqualTo(0);
verify(response).close();
}
@Test
void delete() throws Exception {
mockSentRequest(DELETE, "https://example.com");
mockResponseStatus(HttpStatus.OK);
template.delete("https://example.com");
verify(response).close();
}
@Test // gh-23740
void headerAcceptAllOnDelete() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setResponseCode(500).setBody("internal server error"));
server.start();
template.setRequestFactory(new SimpleClientHttpRequestFactory());
template.delete(server.url("/internal/server/error").uri());
assertThat(server.takeRequest().getHeader("Accept")).isEqualTo("*/*");
}
}
@Test
void optionsForAllow() throws Exception {
mockSentRequest(OPTIONS, "https://example.com");
mockResponseStatus(HttpStatus.OK);
HttpHeaders responseHeaders = new HttpHeaders();
EnumSet expected = EnumSet.of(GET, POST);
responseHeaders.setAllow(expected);
given(response.getHeaders()).willReturn(responseHeaders);
Set result = template.optionsForAllow("https://example.com");
assertThat(result).as("Invalid OPTIONS result").isEqualTo(expected);
verify(response).close();
}
@Test // SPR-9325, SPR-13860
void ioException() throws Exception {
String url = "https://example.com/resource?access_token=123";
mockSentRequest(GET, url);
mockHttpMessageConverter(new MediaType("foo", "bar"), String.class);
given(request.execute()).willThrow(new IOException("Socket failure"));
assertThatExceptionOfType(ResourceAccessException.class).isThrownBy(() ->
template.getForObject(url, String.class))
.withMessage("I/O error on GET request for \"https://example.com/resource\": " +
"Socket failure; nested exception is java.io.IOException: Socket failure");
}
@Test // SPR-15900
void ioExceptionWithEmptyQueryString() throws Exception {
// https://example.com/resource?
URI uri = new URI("https", "example.com", "/resource", "", null);
given(converter.canRead(String.class, null)).willReturn(true);
given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(parseMediaType("foo/bar")));
given(requestFactory.createRequest(uri, GET)).willReturn(request);
given(request.getHeaders()).willReturn(new HttpHeaders());
given(request.execute()).willThrow(new IOException("Socket failure"));
assertThatExceptionOfType(ResourceAccessException.class).isThrownBy(() ->
template.getForObject(uri, String.class))
.withMessage("I/O error on GET request for \"https://example.com/resource\": " +
"Socket failure; nested exception is java.io.IOException: Socket failure");
}
@Test
void exchange() throws Exception {
mockTextPlainHttpMessageConverter();
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
String expected = "42";
mockResponseBody(expected, MediaType.TEXT_PLAIN);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.set("MyHeader", "MyValue");
HttpEntity entity = new HttpEntity<>("Hello World", entityHeaders);
ResponseEntity result = template.exchange("https://example.com", POST, entity, String.class);
assertThat(result.getBody()).as("Invalid POST result").isEqualTo(expected);
assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(requestHeaders.getFirst("MyHeader")).as("Invalid custom header").isEqualTo("MyValue");
assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
verify(response).close();
}
@Test
@SuppressWarnings("rawtypes")
void exchangeParameterizedType() throws Exception {
GenericHttpMessageConverter converter = mock(GenericHttpMessageConverter.class);
template.setMessageConverters(Collections.>singletonList(converter));
ParameterizedTypeReference> intList = new ParameterizedTypeReference>() {};
given(converter.canRead(intList.getType(), null, null)).willReturn(true);
given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
given(converter.canWrite(String.class, String.class, null)).willReturn(true);
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
List expected = Collections.singletonList(42);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
responseHeaders.setContentLength(10);
mockResponseStatus(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream(Integer.toString(42).getBytes()));
given(converter.canRead(intList.getType(), null, MediaType.TEXT_PLAIN)).willReturn(true);
given(converter.read(eq(intList.getType()), eq(null), any(HttpInputMessage.class))).willReturn(expected);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.set("MyHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity<>("Hello World", entityHeaders);
ResponseEntity> result = template.exchange("https://example.com", POST, requestEntity, intList);
assertThat(result.getBody()).as("Invalid POST result").isEqualTo(expected);
assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
assertThat(requestHeaders.getFirst("MyHeader")).as("Invalid custom header").isEqualTo("MyValue");
assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
verify(response).close();
}
@Test // SPR-15066
void requestInterceptorCanAddExistingHeaderValueWithoutBody() throws Exception {
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
request.getHeaders().add("MyHeader", "MyInterceptorValue");
return execution.execute(request, body);
};
template.setInterceptors(Collections.singletonList(interceptor));
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.add("MyHeader", "MyEntityValue");
HttpEntity entity = new HttpEntity<>(null, entityHeaders);
template.exchange("https://example.com", POST, entity, Void.class);
assertThat(requestHeaders.get("MyHeader")).contains("MyEntityValue", "MyInterceptorValue");
verify(response).close();
}
@Test // SPR-15066
void requestInterceptorCanAddExistingHeaderValueWithBody() throws Exception {
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
request.getHeaders().add("MyHeader", "MyInterceptorValue");
return execution.execute(request, body);
};
template.setInterceptors(Collections.singletonList(interceptor));
MediaType contentType = MediaType.TEXT_PLAIN;
given(converter.canWrite(String.class, contentType)).willReturn(true);
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(contentType);
entityHeaders.add("MyHeader", "MyEntityValue");
HttpEntity entity = new HttpEntity<>("Hello World", entityHeaders);
template.exchange("https://example.com", POST, entity, Void.class);
assertThat(requestHeaders.get("MyHeader")).contains("MyEntityValue", "MyInterceptorValue");
verify(response).close();
}
@Test
void clientHttpRequestInitializerAndRequestInterceptorAreBothApplied() throws Exception {
ClientHttpRequestInitializer initializer = request ->
request.getHeaders().add("MyHeader", "MyInitializerValue");
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
request.getHeaders().add("MyHeader", "MyInterceptorValue");
return execution.execute(request, body);
};
template.setClientHttpRequestInitializers(Collections.singletonList(initializer));
template.setInterceptors(Collections.singletonList(interceptor));
MediaType contentType = MediaType.TEXT_PLAIN;
given(converter.canWrite(String.class, contentType)).willReturn(true);
HttpHeaders requestHeaders = new HttpHeaders();
mockSentRequest(POST, "https://example.com", requestHeaders);
mockResponseStatus(HttpStatus.OK);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(contentType);
HttpEntity entity = new HttpEntity<>("Hello World", entityHeaders);
template.exchange("https://example.com", POST, entity, Void.class);
assertThat(requestHeaders.get("MyHeader")).contains("MyInterceptorValue", "MyInitializerValue");
verify(response).close();
}
private void mockSentRequest(HttpMethod method, String uri) throws Exception {
mockSentRequest(method, uri, new HttpHeaders());
}
private void mockSentRequest(HttpMethod method, String uri, HttpHeaders requestHeaders) throws Exception {
given(requestFactory.createRequest(new URI(uri), method)).willReturn(request);
given(request.getHeaders()).willReturn(requestHeaders);
}
private void mockResponseStatus(HttpStatus responseStatus) throws Exception {
given(request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(responseStatus.isError());
given(response.getStatusCode()).willReturn(responseStatus);
given(response.getRawStatusCode()).willReturn(responseStatus.value());
given(response.getStatusText()).willReturn(responseStatus.getReasonPhrase());
}
private void mockTextPlainHttpMessageConverter() {
mockHttpMessageConverter(MediaType.TEXT_PLAIN, String.class);
}
private void mockHttpMessageConverter(MediaType mediaType, Class> type) {
given(converter.canRead(type, null)).willReturn(true);
given(converter.canRead(type, mediaType)).willReturn(true);
given(converter.getSupportedMediaTypes())
.willReturn(Collections.singletonList(mediaType));
given(converter.canRead(type, mediaType)).willReturn(true);
given(converter.canWrite(type, null)).willReturn(true);
given(converter.canWrite(type, mediaType)).willReturn(true);
}
private void mockTextResponseBody(String expectedBody) throws Exception {
mockResponseBody(expectedBody, MediaType.TEXT_PLAIN);
}
private void mockResponseBody(String expectedBody, MediaType mediaType) throws Exception {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(mediaType);
responseHeaders.setContentLength(expectedBody.length());
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream(expectedBody.getBytes()));
given(converter.read(eq(String.class), any(HttpInputMessage.class))).willReturn(expectedBody);
}
}
In order for our log4j2 configuration to be used in an IDE, you must
* set the following system property before running any tests — for
* example, in Run Configurations in Eclipse.
*
*
*
* @author Arjen Poutsma
* @author Brian Clozel
* @author Sam Brannen
*/
class RestTemplateIntegrationTests extends AbstractMockWebServerTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("clientHttpRequestFactories")
@interface ParameterizedRestTemplateTest {
}
@SuppressWarnings("deprecation")
static Stream clientHttpRequestFactories() {
return Stream.of(
new SimpleClientHttpRequestFactory(),
new HttpComponentsClientHttpRequestFactory(),
new org.springframework.http.client.Netty4ClientHttpRequestFactory(),
new OkHttp3ClientHttpRequestFactory()
);
}
private RestTemplate template;
private ClientHttpRequestFactory clientHttpRequestFactory;
/**
* Custom JUnit Jupiter extension that handles exceptions thrown by test methods.
*
*
If the test method throws an {@link HttpServerErrorException}, this
* extension will throw an {@link AssertionError} that wraps the
* {@code HttpServerErrorException} using the
* {@link HttpServerErrorException#getResponseBodyAsString() response body}
* as the failure message.
*
*
The {@code AsyncRestTemplate} exposes a synchronous {@link RestTemplate} via the
* {@link #getRestOperations()} method and shares its {@linkplain #setErrorHandler error handler}
* and {@linkplain #setMessageConverters message converters} with that {@code RestTemplate}.
*
*
Note: by default {@code AsyncRestTemplate} relies on
* standard JDK facilities to establish HTTP connections. You can switch to use
* a different HTTP library such as Apache HttpComponents, Netty, and OkHttp by
* using a constructor accepting an {@link org.springframework.http.client.AsyncClientHttpRequestFactory}.
*
*
For more information, please refer to the {@link RestTemplate} API documentation.
*
* @author Arjen Poutsma
* @since 4.0
* @see RestTemplate
* @deprecated as of Spring 5.0, in favor of {@link org.springframework.web.reactive.function.client.WebClient}
*/
@Deprecated
public class AsyncRestTemplate extends org.springframework.http.client.support.InterceptingAsyncHttpAccessor
implements AsyncRestOperations {
private final RestTemplate syncTemplate;
AsyncRequestCallback功能性接口定义:
package org.springframework.web.client;
import java.io.IOException;
/**
* Callback interface for code that operates on an
* {@link org.springframework.http.client.AsyncClientHttpRequest}. Allows to
* manipulate the request headers, and write to the request body.
*
*
Used internally by the {@link AsyncRestTemplate}, but also useful for
* application code.
*
* @author Arjen Poutsma
* @see org.springframework.web.client.AsyncRestTemplate#execute
* @since 4.0
* @deprecated as of Spring 5.0, in favor of
* {@link org.springframework.web.reactive.function.client.ExchangeFilterFunction}
*/
@FunctionalInterface
@Deprecated
public interface AsyncRequestCallback {
/**
* Gets called by {@link AsyncRestTemplate#execute} with an opened {@code ClientHttpRequest}.
* Does not need to care about closing the request or about handling errors:
* this will all be handled by the {@code RestTemplate}.
* @param request the active HTTP request
* @throws java.io.IOException in case of I/O errors
*/
void doWithRequest(org.springframework.http.client.AsyncClientHttpRequest request) throws IOException;
}
AysncRestOperations异步调用定义:
package org.springframework.web.client;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Interface specifying a basic set of asynchronous RESTful operations.
* Implemented by {@link AsyncRestTemplate}. Not often used directly, but a useful
* option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Arjen Poutsma
* @since 4.0
* @see AsyncRestTemplate
* @see RestOperations
* @deprecated as of Spring 5.0, in favor of {@link org.springframework.web.reactive.function.client.WebClient}
*/
@Deprecated
public interface AsyncRestOperations {
/**
* Expose the synchronous Spring RestTemplate to allow synchronous invocation.
*/
RestOperations getRestOperations();
// GET
/**
* Asynchronously retrieve an entity by doing a GET on the specified URL.
* The response is converted and stored in an {@link ResponseEntity}.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param responseType the type of the return value
* @param uriVariables the variables to expand the template
* @return the entity wrapped in a {@link Future}
*/
ListenableFuture> getForEntity(String url, Class responseType,
Object... uriVariables) throws RestClientException;
/**
* Asynchronously retrieve a representation by doing a GET on the URI template.
* The response is converted and stored in an {@link ResponseEntity}.
*
URI Template variables are expanded using the given map.
* @param url the URL
* @param responseType the type of the return value
* @param uriVariables the map containing variables for the URI template
* @return the entity wrapped in a {@link Future}
*/
ListenableFuture> getForEntity(String url, Class responseType,
Map uriVariables) throws RestClientException;
/**
* Asynchronously retrieve a representation by doing a GET on the URL.
* The response is converted and stored in an {@link ResponseEntity}.
* @param url the URL
* @param responseType the type of the return value
* @return the entity wrapped in a {@link Future}
*/
ListenableFuture> getForEntity(URI url, Class responseType)
throws RestClientException;
// HEAD
/**
* Asynchronously retrieve all headers of the resource specified by the URI template.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param uriVariables the variables to expand the template
* @return all HTTP headers of that resource wrapped in a {@link Future}
*/
ListenableFuture headForHeaders(String url, Object... uriVariables)
throws RestClientException;
/**
* Asynchronously retrieve all headers of the resource specified by the URI template.
*
URI Template variables are expanded using the given map.
* @param url the URL
* @param uriVariables the map containing variables for the URI template
* @return all HTTP headers of that resource wrapped in a {@link Future}
*/
ListenableFuture headForHeaders(String url, Map uriVariables)
throws RestClientException;
/**
* Asynchronously retrieve all headers of the resource specified by the URL.
* @param url the URL
* @return all HTTP headers of that resource wrapped in a {@link Future}
*/
ListenableFuture headForHeaders(URI url) throws RestClientException;
// POST
/**
* Create a new resource by POSTing the given object to the URI template, and
* asynchronously returns the value of the {@code Location} header. This header
* typically indicates where the new resource is stored.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
* @return the value for the {@code Location} header wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
ListenableFuture postForLocation(String url, @Nullable HttpEntity> request, Object... uriVariables)
throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URI template, and
* asynchronously returns the value of the {@code Location} header. This header
* typically indicates where the new resource is stored.
*
URI Template variables are expanded using the given map.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
* @return the value for the {@code Location} header wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
ListenableFuture postForLocation(String url, @Nullable HttpEntity> request, Map uriVariables)
throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URL, and asynchronously
* returns the value of the {@code Location} header. This header typically indicates
* where the new resource is stored.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @return the value for the {@code Location} header wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
ListenableFuture postForLocation(URI url, @Nullable HttpEntity> request) throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URI template,
* and asynchronously returns the response as {@link ResponseEntity}.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
* @return the entity wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
ListenableFuture> postForEntity(String url, @Nullable HttpEntity> request,
Class responseType, Object... uriVariables) throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URI template,
* and asynchronously returns the response as {@link ResponseEntity}.
*
URI Template variables are expanded using the given map.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
* @return the entity wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
ListenableFuture> postForEntity(String url, @Nullable HttpEntity> request,
Class responseType, Map uriVariables) throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URL,
* and asynchronously returns the response as {@link ResponseEntity}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @return the entity wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
ListenableFuture> postForEntity(URI url, @Nullable HttpEntity> request,
Class responseType) throws RestClientException;
// PUT
/**
* Create or update a resource by PUTting the given object to the URI.
*
URI Template variables are expanded using the given URI variables, if any.
*
The Future will return a {@code null} result upon completion.
* @param url the URL
* @param request the Object to be PUT (may be {@code null})
* @param uriVariables the variables to expand the template
* @see HttpEntity
*/
ListenableFuture> put(String url, @Nullable HttpEntity> request, Object... uriVariables)
throws RestClientException;
/**
* Creates a new resource by PUTting the given object to URI template.
*
URI Template variables are expanded using the given map.
*
The Future will return a {@code null} result upon completion.
* @param url the URL
* @param request the Object to be PUT (may be {@code null})
* @param uriVariables the variables to expand the template
* @see HttpEntity
*/
ListenableFuture> put(String url, @Nullable HttpEntity> request, Map uriVariables)
throws RestClientException;
/**
* Creates a new resource by PUTting the given object to URL.
*
The Future will return a {@code null} result upon completion.
* @param url the URL
* @param request the Object to be PUT (may be {@code null})
* @see HttpEntity
*/
ListenableFuture> put(URI url, @Nullable HttpEntity> request) throws RestClientException;
// DELETE
/**
* Asynchronously delete the resources at the specified URI.
*
URI Template variables are expanded using the given URI variables, if any.
*
The Future will return a {@code null} result upon completion.
* @param url the URL
* @param uriVariables the variables to expand in the template
*/
ListenableFuture> delete(String url, Object... uriVariables) throws RestClientException;
/**
* Asynchronously delete the resources at the specified URI.
*
URI Template variables are expanded using the given URI variables, if any.
*
The Future will return a {@code null} result upon completion.
* @param url the URL
* @param uriVariables the variables to expand in the template
*/
ListenableFuture> delete(String url, Map uriVariables) throws RestClientException;
/**
* Asynchronously delete the resources at the specified URI.
*
URI Template variables are expanded using the given URI variables, if any.
*
The Future will return a {@code null} result upon completion.
* @param url the URL
*/
ListenableFuture> delete(URI url) throws RestClientException;
// OPTIONS
/**
* Asynchronously return the value of the Allow header for the given URI.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param uriVariables the variables to expand in the template
* @return the value of the allow header wrapped in a {@link Future}
*/
ListenableFuture> optionsForAllow(String url, Object... uriVariables)
throws RestClientException;
/**
* Asynchronously return the value of the Allow header for the given URI.
*
URI Template variables are expanded using the given map.
* @param url the URL
* @param uriVariables the variables to expand in the template
* @return the value of the allow header wrapped in a {@link Future}
*/
ListenableFuture> optionsForAllow(String url, Map uriVariables)
throws RestClientException;
/**
* Asynchronously return the value of the Allow header for the given URL.
* @param url the URL
* @return the value of the allow header wrapped in a {@link Future}
*/
ListenableFuture> optionsForAllow(URI url) throws RestClientException;
// exchange
/**
* Asynchronously execute the HTTP method to the given URI template, writing the
* given request entity to the request, and returns the response as
* {@link ResponseEntity}.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request
* (may be {@code null})
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
ListenableFuture> exchange(String url, HttpMethod method,
@Nullable HttpEntity> requestEntity, Class responseType, Object... uriVariables)
throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the
* given request entity to the request, and returns the response as
* {@link ResponseEntity}.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request
* (may be {@code null})
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
ListenableFuture> exchange(String url, HttpMethod method,
@Nullable HttpEntity> requestEntity, Class responseType,
Map uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the
* given request entity to the request, and returns the response as
* {@link ResponseEntity}.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request
* (may be {@code null})
* @param responseType the type of the return value
* @return the response as entity wrapped in a {@link Future}
*/
ListenableFuture> exchange(URI url, HttpMethod method,
@Nullable HttpEntity> requestEntity, Class responseType)
throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the given
* request entity to the request, and returns the response as {@link ResponseEntity}.
* The given {@link ParameterizedTypeReference} is used to pass generic type
* information:
*
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the
* request (may be {@code null})
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
ListenableFuture> exchange(String url, HttpMethod method,
@Nullable HttpEntity> requestEntity, ParameterizedTypeReference responseType,
Object... uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the given
* request entity to the request, and returns the response as {@link ResponseEntity}.
* The given {@link ParameterizedTypeReference} is used to pass generic type
* information:
*
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request
* (may be {@code null})
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
ListenableFuture> exchange(String url, HttpMethod method,
@Nullable HttpEntity> requestEntity, ParameterizedTypeReference responseType,
Map uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the given
* request entity to the request, and returns the response as {@link ResponseEntity}.
* The given {@link ParameterizedTypeReference} is used to pass generic type
* information:
*
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request
* (may be {@code null})
* @param responseType the type of the return value
* @return the response as entity wrapped in a {@link Future}
*/
ListenableFuture> exchange(URI url, HttpMethod method,
@Nullable HttpEntity> requestEntity, ParameterizedTypeReference responseType)
throws RestClientException;
// general execution
/**
* Asynchronously execute the HTTP method to the given URI template, preparing the
* request with the {@link AsyncRequestCallback}, and reading the response with a
* {@link ResponseExtractor}.
*
URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @param uriVariables the variables to expand in the template
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
ListenableFuture execute(String url, HttpMethod method,
@Nullable AsyncRequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor,
Object... uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, preparing the
* request with the {@link AsyncRequestCallback}, and reading the response with a
* {@link ResponseExtractor}.
*
URI Template variables are expanded using the given URI variables map.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @param uriVariables the variables to expand in the template
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
ListenableFuture execute(String url, HttpMethod method,
@Nullable AsyncRequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor,
Map uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URL, preparing the request
* with the {@link AsyncRequestCallback}, and reading the response with a
* {@link ResponseExtractor}.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
ListenableFuture execute(URI url, HttpMethod method,
@Nullable AsyncRequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor)
throws RestClientException;
}
In order for our log4j2 configuration to be used in an IDE, you must
* set the following system property before running any tests — for
* example, in Run Configurations in Eclipse.
*
*
package org.springframework.jdbc.core;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.BatchUpdateException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.InvalidResultSetAccessException;
import org.springframework.jdbc.SQLWarningException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.jdbc.datasource.ConnectionProxy;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcAccessor;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.StringUtils;
/**
* This is the central class in the JDBC core package.
* It simplifies the use of JDBC and helps to avoid common errors.
* It executes core JDBC workflow, leaving application code to provide SQL
* and extract results. This class executes SQL queries or updates, initiating
* iteration over ResultSets and catching JDBC exceptions and translating
* them to the generic, more informative exception hierarchy defined in the
* {@code org.springframework.dao} package.
*
*
Code using this class need only implement callback interfaces, giving
* them a clearly defined contract. The {@link PreparedStatementCreator} callback
* interface creates a prepared statement given a Connection, providing SQL and
* any necessary parameters. The {@link ResultSetExtractor} interface extracts
* values from a ResultSet. See also {@link PreparedStatementSetter} and
* {@link RowMapper} for two popular alternative callback interfaces.
*
*
Can be used within a service implementation via direct instantiation
* with a DataSource reference, or get prepared in an application context
* and given to services as bean reference. Note: The DataSource should
* always be configured as a bean in the application context, in the first case
* given to the service directly, in the second case to the prepared template.
*
*
Because this class is parameterizable by the callback interfaces and
* the {@link org.springframework.jdbc.support.SQLExceptionTranslator}
* interface, there should be no need to subclass it.
*
*
All SQL operations performed by this class are logged at debug level,
* using "org.springframework.jdbc.core.JdbcTemplate" as log category.
*
*
NOTE: An instance of this class is thread-safe once configured.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Thomas Risberg
* @since May 3, 2001
* @see PreparedStatementCreator
* @see PreparedStatementSetter
* @see CallableStatementCreator
* @see PreparedStatementCallback
* @see CallableStatementCallback
* @see ResultSetExtractor
* @see RowCallbackHandler
* @see RowMapper
* @see org.springframework.jdbc.support.SQLExceptionTranslator
*/
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
核心执行模板方法如下 :
//-------------------------------------------------------------------------
// Methods dealing with a plain java.sql.Connection
//-------------------------------------------------------------------------
@Override
@Nullable
public T execute(ConnectionCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
try {
// Create close-suppressing Connection proxy, also preparing returned Statements.
Connection conToUse = createConnectionProxy(con);
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("ConnectionCallback", sql, ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
/**
* Create a close-suppressing proxy for the given JDBC Connection.
* Called by the {@code execute} method.
*
The proxy also prepares returned JDBC Statements, applying
* statement settings such as fetch size, max rows, and query timeout.
* @param con the JDBC Connection to create a proxy for
* @return the Connection proxy
* @see java.sql.Connection#close()
* @see #execute(ConnectionCallback)
* @see #applyStatementSettings
*/
protected Connection createConnectionProxy(Connection con) {
return (Connection) Proxy.newProxyInstance(
ConnectionProxy.class.getClassLoader(),
new Class>[] {ConnectionProxy.class},
new CloseSuppressingInvocationHandler(con));
}
//-------------------------------------------------------------------------
// Methods dealing with static SQL (java.sql.Statement)
//-------------------------------------------------------------------------
@Nullable
private T execute(StatementCallback action, boolean closeResources) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
if (closeResources) {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}
ConnectionCallback和StatementCallback功能性接口定义:
package org.springframework.jdbc.core;
import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.dao.DataAccessException;
import org.springframework.lang.Nullable;
/**
* Generic callback interface for code that operates on a JDBC Connection.
* Allows to execute any number of operations on a single Connection,
* using any type and number of Statements.
*
*
This is particularly useful for delegating to existing data access code
* that expects a Connection to work on and throws SQLException. For newly
* written code, it is strongly recommended to use JdbcTemplate's more specific
* operations, for example a {@code query} or {@code update} variant.
*
* @author Juergen Hoeller
* @since 1.1.3
* @param the result type
* @see JdbcTemplate#execute(ConnectionCallback)
* @see JdbcTemplate#query
* @see JdbcTemplate#update
*/
@FunctionalInterface
public interface ConnectionCallback {
/**
* Gets called by {@code JdbcTemplate.execute} with an active JDBC
* Connection. Does not need to care about activating or closing the
* Connection, or handling transactions.
*
If called without a thread-bound JDBC transaction (initiated by
* DataSourceTransactionManager), the code will simply get executed on the
* JDBC connection with its transactional semantics. If JdbcTemplate is
* configured to use a JTA-aware DataSource, the JDBC Connection and thus
* the callback code will be transactional if a JTA transaction is active.
*
Allows for returning a result object created within the callback, i.e.
* a domain object or a collection of domain objects. Note that there's special
* support for single step actions: see {@code JdbcTemplate.queryForObject}
* etc. A thrown RuntimeException is treated as application exception:
* it gets propagated to the caller of the template.
* @param con active JDBC Connection
* @return a result object, or {@code null} if none
* @throws SQLException if thrown by a JDBC method, to be auto-converted
* to a DataAccessException by an SQLExceptionTranslator
* @throws DataAccessException in case of custom exceptions
* @see JdbcTemplate#queryForObject(String, Class)
* @see JdbcTemplate#queryForRowSet(String)
*/
@Nullable
T doInConnection(Connection con) throws SQLException, DataAccessException;
}
package org.springframework.jdbc.core;
import java.sql.SQLException;
import java.sql.Statement;
import org.springframework.dao.DataAccessException;
import org.springframework.lang.Nullable;
/**
* Generic callback interface for code that operates on a JDBC Statement.
* Allows to execute any number of operations on a single Statement,
* for example a single {@code executeUpdate} call or repeated
* {@code executeUpdate} calls with varying SQL.
*
*
Used internally by JdbcTemplate, but also useful for application code.
*
* @author Juergen Hoeller
* @since 16.03.2004
* @param the result type
* @see JdbcTemplate#execute(StatementCallback)
*/
@FunctionalInterface
public interface StatementCallback {
/**
* Gets called by {@code JdbcTemplate.execute} with an active JDBC
* Statement. Does not need to care about closing the Statement or the
* Connection, or about handling transactions: this will all be handled
* by Spring's JdbcTemplate.
*
NOTE: Any ResultSets opened should be closed in finally blocks
* within the callback implementation. Spring will close the Statement
* object after the callback returned, but this does not necessarily imply
* that the ResultSet resources will be closed: the Statement objects might
* get pooled by the connection pool, with {@code close} calls only
* returning the object to the pool but not physically closing the resources.
*
If called without a thread-bound JDBC transaction (initiated by
* DataSourceTransactionManager), the code will simply get executed on the
* JDBC connection with its transactional semantics. If JdbcTemplate is
* configured to use a JTA-aware DataSource, the JDBC connection and thus
* the callback code will be transactional if a JTA transaction is active.
*
Allows for returning a result object created within the callback, i.e.
* a domain object or a collection of domain objects. Note that there's
* special support for single step actions: see JdbcTemplate.queryForObject etc.
* A thrown RuntimeException is treated as application exception, it gets
* propagated to the caller of the template.
* @param stmt active JDBC Statement
* @return a result object, or {@code null} if none
* @throws SQLException if thrown by a JDBC method, to be auto-converted
* to a DataAccessException by an SQLExceptionTranslator
* @throws DataAccessException in case of custom exceptions
* @see JdbcTemplate#queryForObject(String, Class)
* @see JdbcTemplate#queryForRowSet(String)
*/
@Nullable
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
The central method is {@code execute}, supporting Hibernate access code
* implementing the {@link HibernateCallback} interface. It provides Hibernate Session
* handling such that neither the HibernateCallback implementation nor the calling
* code needs to explicitly care about retrieving/closing Hibernate Sessions,
* or handling Session lifecycle exceptions. For typical single step actions,
* there are various convenience methods (find, load, saveOrUpdate, delete).
*
*
Can be used within a service implementation via direct instantiation
* with a SessionFactory reference, or get prepared in an application context
* and given to services as bean reference. Note: The SessionFactory should
* always be configured as bean in the application context, in the first case
* given to the service directly, in the second case to the prepared template.
*
*
NOTE: Hibernate access code can also be coded against the native Hibernate
* {@link Session}. Hence, for newly started projects, consider adopting the standard
* Hibernate style of coding against {@link SessionFactory#getCurrentSession()}.
* Alternatively, use {@link #execute(HibernateCallback)} with Java 8 lambda code blocks
* against the callback-provided {@code Session} which results in elegant code as well,
* decoupled from the Hibernate Session lifecycle. The remaining operations on this
* HibernateTemplate are deprecated in the meantime and primarily exist as a migration
* helper for older Hibernate 3.x/4.x data access code in existing applications.
*
* @author Juergen Hoeller
* @since 4.2
* @see #setSessionFactory
* @see HibernateCallback
* @see Session
* @see LocalSessionFactoryBean
* @see HibernateTransactionManager
* @see org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
* @see org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptor
*/
public class HibernateTemplate implements HibernateOperations, InitializingBean {
核心执行方法:
@Override
@Nullable
public T execute(HibernateCallback action) throws DataAccessException {
return doExecute(action, false);
}
/**
* Execute the action specified by the given action object within a Session.
* @param action callback object that specifies the Hibernate action
* @param enforceNativeSession whether to enforce exposure of the native
* Hibernate Session to callback code
* @return a result object returned by the action, or {@code null}
* @throws DataAccessException in case of Hibernate errors
*/
@Nullable
protected T doExecute(HibernateCallback action, boolean enforceNativeSession) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Session session = null;
boolean isNew = false;
try {
session = obtainSessionFactory().getCurrentSession();
}
catch (HibernateException ex) {
logger.debug("Could not retrieve pre-bound Hibernate session", ex);
}
if (session == null) {
session = obtainSessionFactory().openSession();
session.setHibernateFlushMode(FlushMode.MANUAL);
isNew = true;
}
try {
enableFilters(session);
Session sessionToExpose =
(enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
return action.doInHibernate(sessionToExpose);
}
catch (HibernateException ex) {
throw SessionFactoryUtils.convertHibernateAccessException(ex);
}
catch (PersistenceException ex) {
if (ex.getCause() instanceof HibernateException) {
throw SessionFactoryUtils.convertHibernateAccessException((HibernateException) ex.getCause());
}
throw ex;
}
catch (RuntimeException ex) {
// Callback code threw application exception...
throw ex;
}
finally {
if (isNew) {
SessionFactoryUtils.closeSession(session);
}
else {
disableFilters(session);
}
}
}
HibernateCallback功能性接口定义:
package org.springframework.orm.hibernate5;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.lang.Nullable;
/**
* Callback interface for Hibernate code. To be used with {@link HibernateTemplate}'s
* execution methods, often as anonymous classes within a method implementation.
* A typical implementation will call {@code Session.load/find/update} to perform
* some operations on persistent objects.
*
* @author Juergen Hoeller
* @since 4.2
* @param the result type
* @see HibernateTemplate
* @see HibernateTransactionManager
*/
@FunctionalInterface
public interface HibernateCallback {
/**
* Gets called by {@code HibernateTemplate.execute} with an active
* Hibernate {@code Session}. Does not need to care about activating
* or closing the {@code Session}, or handling transactions.
*
Allows for returning a result object created within the callback,
* i.e. a domain object or a collection of domain objects.
* A thrown custom RuntimeException is treated as an application exception:
* It gets propagated to the caller of the template.
* @param session active Hibernate session
* @return a result object, or {@code null} if none
* @throws HibernateException if thrown by the Hibernate API
* @see HibernateTemplate#execute
*/
@Nullable
T doInHibernate(Session session) throws HibernateException;
}
Hibernate Dao 支持实现:
package org.springframework.orm.hibernate5.support;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.support.DaoSupport;
import org.springframework.lang.Nullable;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.util.Assert;
/**
* Convenient super class for Hibernate-based data access objects.
*
*
Requires a {@link SessionFactory} to be set, providing a
* {@link org.springframework.orm.hibernate5.HibernateTemplate} based on it to
* subclasses through the {@link #getHibernateTemplate()} method.
* Can alternatively be initialized directly with a HibernateTemplate,
* in order to reuse the latter's settings such as the SessionFactory,
* exception translator, flush mode, etc.
*
*
This class will create its own HibernateTemplate instance if a SessionFactory
* is passed in. The "allowCreate" flag on that HibernateTemplate will be "true"
* by default. A custom HibernateTemplate instance can be used through overriding
* {@link #createHibernateTemplate}.
*
*
NOTE: Hibernate access code can also be coded in plain Hibernate style.
* Hence, for newly started projects, consider adopting the standard Hibernate
* style of coding data access objects instead, based on
* {@link SessionFactory#getCurrentSession()}.
* This HibernateTemplate primarily exists as a migration helper for Hibernate 3
* based data access code, to benefit from bug fixes in Hibernate 5.x.
*
* @author Juergen Hoeller
* @since 4.2
* @see #setSessionFactory
* @see #getHibernateTemplate
* @see org.springframework.orm.hibernate5.HibernateTemplate
*/
public abstract class HibernateDaoSupport extends DaoSupport {
@Nullable
private HibernateTemplate hibernateTemplate;
/**
* Set the Hibernate SessionFactory to be used by this DAO.
* Will automatically create a HibernateTemplate for the given SessionFactory.
* @see #createHibernateTemplate
* @see #setHibernateTemplate
*/
public final void setSessionFactory(SessionFactory sessionFactory) {
if (this.hibernateTemplate == null || sessionFactory != this.hibernateTemplate.getSessionFactory()) {
this.hibernateTemplate = createHibernateTemplate(sessionFactory);
}
}
/**
* Create a HibernateTemplate for the given SessionFactory.
* Only invoked if populating the DAO with a SessionFactory reference!
*
Can be overridden in subclasses to provide a HibernateTemplate instance
* with different configuration, or a custom HibernateTemplate subclass.
* @param sessionFactory the Hibernate SessionFactory to create a HibernateTemplate for
* @return the new HibernateTemplate instance
* @see #setSessionFactory
*/
protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {
return new HibernateTemplate(sessionFactory);
}
/**
* Return the Hibernate SessionFactory used by this DAO.
*/
@Nullable
public final SessionFactory getSessionFactory() {
return (this.hibernateTemplate != null ? this.hibernateTemplate.getSessionFactory() : null);
}
/**
* Set the HibernateTemplate for this DAO explicitly,
* as an alternative to specifying a SessionFactory.
* @see #setSessionFactory
*/
public final void setHibernateTemplate(@Nullable HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
/**
* Return the HibernateTemplate for this DAO,
* pre-initialized with the SessionFactory or set explicitly.
*
Note: The returned HibernateTemplate is a shared instance.
* You may introspect its configuration, but not modify the configuration
* (other than from within an {@link #initDao} implementation).
* Consider creating a custom HibernateTemplate instance via
* {@code new HibernateTemplate(getSessionFactory())}, in which case
* you're allowed to customize the settings on the resulting instance.
*/
@Nullable
public final HibernateTemplate getHibernateTemplate() {
return this.hibernateTemplate;
}
@Override
protected final void checkDaoConfig() {
if (this.hibernateTemplate == null) {
throw new IllegalArgumentException("'sessionFactory' or 'hibernateTemplate' is required");
}
}
/**
* Conveniently obtain the current Hibernate Session.
* @return the Hibernate Session
* @throws DataAccessResourceFailureException if the Session couldn't be created
* @see SessionFactory#getCurrentSession()
*/
protected final Session currentSession() throws DataAccessResourceFailureException {
SessionFactory sessionFactory = getSessionFactory();
Assert.state(sessionFactory != null, "No SessionFactory set");
return sessionFactory.getCurrentSession();
}
}
参考文章
模板模式
深入理解 Spring 中的 ThreadPoolTaskExecutor 与 ListenableFuture 对象
原题链接:#136 Single Number
要求:
给定一个整型数组,其中除了一个元素之外,每个元素都出现两次。找出这个元素
注意:算法的时间复杂度应为O(n),最好不使用额外的内存空间
难度:中等
分析:
题目限定了线性的时间复杂度,同时不使用额外的空间,即要求只遍历数组一遍得出结果。由于异或运算 n XOR n = 0, n XOR 0 = n,故将数组中的每个元素进
java.io.EOFException
at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2281)
at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:
Velocity出现的目的用于简化基于MVC的web应用开发,用于替代JSP标签技术,那么Velocity如何访问Java代码.本篇继续以Velocity三http://bit1129.iteye.com/blog/2106142中的例子为基础,
POJO
package com.tom.servlets;
public
今天看org.jboss.netty.example.http.file.HttpStaticFileServerHandler.java
可以直接往channel里面写入一个FileRegion对象,而不需要相应的encoder:
//pipeline(没有诸如“FileRegionEncoder”的handler):
public ChannelPipeline ge
Zero Clipboard的实现原理
Zero Clipboard 利用透明的Flash让其漂浮在复制按钮之上,这样其实点击的不是按钮而是 Flash ,这样将需要的内容传入Flash,再通过Flash的复制功能把传入的内容复制到剪贴板。
Zero Clipboard的安装方法
首先需要下载 Zero Clipboard的压缩包,解压后把文件夹中两个文件:ZeroClipboard.js
4.1 @Order
Spring 4.2 利用@Order控制配置类的加载顺序
4.2 演示
两个演示bean
package com.wisely.spring4_2.order;
public class Demo1Service {
}
package com.wisely.spring4_2.order;
public class