RESTful 介绍 REST:表现层状态转化(Representational State Transfer)
RESTful是一种软件设计风格,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便。
Spring 对 RESTful 风格的接口有着天然的支持。
什么是REST REST(RepresentationalState Transfer)是Roy Fielding 提出的一个描述互联系统架构风格的名词。REST定义了一组体系架构原则,您可以根据这些原则设计以系统资源为中心的Web 服务,包括使用不同语言编写的客户端如何通过 HTTP处理和传输资源状态。
为什么称为 REST?Web本质上由各种各样的资源组成,资源由URI 唯一标识。浏览器(或者任何其它类似于浏览器的应用程序)将展示出该资源的一种表现方式,或者一种表现状态。如果用户在该页面中定向到指向其它资源的链接,则将访问该资源,并表现出它的状态。这意味着客户端应用程序随着每个资源表现状态的不同而发生状态转移,也即所谓REST。
附:REST 定义 、REST 与 SOAP 的比较
REST成熟度的四个层次 第一个层次(Level0)的Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形 式。SOAP和 XML-RPC都属于此类。
第二个层次(Level1)的Web 服务引入了资源的概念。每个资源有对应的标识符和表达。
第三个层次(Level2)的Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用HTTP 状态码来表示不同的结果。如 HTTPGET 方法来获取资源,HTTPDELETE 方法来删除资源。
第四个层次(Level3)的Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。
其中第三个层次建立了创建、读取、更新和删除(create,read, update, and delete,CRUD)操作与 HTTP方法之间的一对一映射。根据此映射:
(1)若要在服务器上创建资源,应该使用POST 方法。
(2)若要检索某个资源,应该使用GET 方法。
(3)若要更改资源状态或对其进行更新,应该使用PUT 方法。
(4)若要删除某个资源,应该使用DELETE 方法。
HTTP请求的方法 (1)GET:通过请求URI得到资源 (2)POST:用于添加新的内容 (3)PUT:用于修改某个内容,若不存在则添加 (4)DELETE:删除某个内容 (5)OPTIONS :询问可以执行哪些方法 (6)HEAD :类似于GET, 但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据 (7)CONNECT :用于代理进行传输,如使用SSL (8)TRACE:用于远程诊断服务器
HTTP请求的状态码 (1)成功Successful2xx:此类状态码标识客户端的请求被成功接收、理解并接受。常见如200(OK)、204(NoContent)。 (2)重定向Redirection3xx:这个类别的状态码标识用户代理要做出进一步的动作来完成请求。常见如301(MovedPermanently)、302(MovedTemprarily)。 (3)客户端错误Client Error 4xx:4xx类别的状态码是当客户端象是出错的时使用的。常见如400(BadRequest)、401(Unauthorized)、403(Forbidden)、404(NotFound)。 (4)服务器错误Server Error 5xx:响应状态码以5开头表示服务器知道自己出错或者没有能力执行请求。常见如500(InternalServer Error)、502(BadGateway)、504(GatewayTimeout)。
附:HTTP1.1的标准简介
接口中的注解
@Controller:修饰class,用来创建处理http请求的对象
@RestController:Spring4之后加入的注解,原来在@Controller中返回实体需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回实体格式。
@RequestMapping:配置 url 映射
@PostMapping: 这个是@RequestMapping+POST方法的简写
@RequestHeader: 请求Header参数
@PathVariable: URL路径参数,比如/{id}中的id参数
@RequestParam: URL请求参数
@RequestBody: 请求Body参数
RESTful架构有一些典型的设计误区,就是URI包含动词。因为”资源”表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。 上面设计的API的URI中都是名词。(此句引用)
RESTful 测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 @AutoConfigureMockMvc @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ApplicationTests { @Autowired private MockMvc mvc; @Test public void testUserController () throws Exception { RequestBuilder request; request = get("/users/" ); MvcResult result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); BaseResponse<List<User>> response = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<List<User>>>() {}); assertThat(response.isSuccess(), is(true )); assertThat(response.getMsg(), is("查询列表成功" )); assertThat(((List) response.getData()).size(), is(0 )); request = post("/users/" ) .param("id" , "1" ) .param("name" , "测试大师" ) .param("age" , "20" ); result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); BaseResponse<String> response1 = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<String>>() {}); assertThat(response1.isSuccess(), is(true )); assertThat(response1.getMsg(), is("新增成功" )); request = get("/users/" ); result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); BaseResponse<List<User>> response2 = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<List<User>>>() {}); assertThat(response2.isSuccess(), is(true )); assertThat(response2.getMsg(), is("查询列表成功" )); assertThat((response2.getData()).size(), is(1 )); request = put("/users/1" ) .param("name" , "测试终极大师" ) .param("age" , "30" ); result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); BaseResponse<String> response3 = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<String>>() {}); assertThat(response3.isSuccess(), is(true )); assertThat(response3.getMsg(), is("更新成功" )); request = get("/users/1" ); result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); BaseResponse<User> response4 = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<User>>() {}); assertThat(response4.isSuccess(), is(true )); assertThat(response4.getMsg(), is("查询成功" )); User user = response4.getData(); assertThat(user.getId(), is(1L )); assertThat(user.getName(), is("测试终极大师" )); request = delete("/users/1" ); result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); BaseResponse<String> response5 = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<String>>() {}); assertThat(response5.isSuccess(), is(true )); assertThat(response5.getMsg(), is("删除成功" )); request = get("/users/" ); result = mvc.perform(request) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); BaseResponse<List<User>> response6 = JacksonUtil.json2Bean(content, new TypeReference <BaseResponse<List<User>>>() {}); assertThat(response6.isSuccess(), is(true )); assertThat(response6.getMsg(), is("查询列表成功" )); assertThat((response6.getData()).size(), is(0 )); } }
RestTemplate 介绍 用于同步客户端HTTP访问的Spring scentral类。它简化了与httpserver的通信,并实施了RESTful原则。它处理HTTP连接,让应用程序代码提供url(带有可能的模板变量)并提取结果。(简化了发起HTTP请求以及处理响应的过程,并且支持REST。)
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
实现逻辑 参考https://www.xncoding.com/2017/07/06/spring/sb-restclient.html
API地址
不同的 HTTP 请求实现方式 RestTemplate 默认依赖 JDK 提供 http 连接的能力(HttpURLConnection),如果有需要的话也可以通过 setRequestFactory 方法替换为例如 Apache HttpComponents、Netty 或 OkHttp 等其它HTTP library。
需要引入依赖 1 2 3 4 5 <dependency > <groupId > org.apache.httpcomponents</groupId > <artifactId > httpclient</artifactId > </dependency >
URLConnection 方式的请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 String result= "" ; BufferedReaderin = null ; try { String urlNameString= url +"?" + param; URL realUrl= new URL (urlNameString); URLConnectionconnection = realUrl.openConnection(); connection.setRequestProperty("accept" ,"*/*" ); connection.setRequestProperty("connection" ,"Keep-Alive" ); connection.setRequestProperty("user-agent" , "Mozilla/4.0(compatible; MSIE 6.0; Windows NT 5.1;SV1)" ); connection.connect(); Map<String,List<String>> map = connection.getHeaderFields(); for (String key : map.keySet()) { System.out.println(key+ "--->" + map.get(key)); } in =new BufferedReader (newInputStreamReader( connection.getInputStream())); String line; while ((line = in.readLine())!= null ) { result += line; } } catch (Exception e) { … } finally { }
RestTemplate 方式的请求 1 ResponseEntity<SsoUrlPrm>result = restTemplate.getForEntity(requestPathUrl,SsoUrlPrm.class);
参考1https://www.cnblogs.com/zhaoyan001/p/8442602.html
参考2https://blog.csdn.net/weixin_39986856/article/details/83018655
使用 RestTemplate 直接使用方式很简单: 1 2 3 4 5 6 7 public class RestTemplateTest { public static void main (String[] args) { RestTemplate restT = new RestTemplate (); Quote quote = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random" , Quote.class); String quoteString = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random" , String.class); System.out.println(quoteString); }}
发送GET请求 1 2 3 4 5 6 User user1 = this .restTemplate.getForObject(uri, User.class);ResponseEntity<User> responseEntity1 = this .restTemplate.getForEntity(uri, User.class);HttpStatus statusCode = responseEntity1.getStatusCode();HttpHeaders header = responseEntity1.getHeaders();User user2 = responseEntity1.getBody(); RequestEntity requestEntity = RequestEntity.get(new URI (uri)).build();ResponseEntity<User> responseEntity2 = this .restTemplate.exchange(requestEntity, User.class);User user3 = responseEntity2.getBody();
发送POST请求 1 2 3 4 5 6 7 8 9 User user1 = this .restTemplate.postForObject(uri, user, User.class);ResponseEntity<User> responseEntity1 = this .restTemplate.postForEntity(uri, user, User.class); RequestEntity<User> requestEntity = RequestEntity .post(new URI (uri)) .body(user); ResponseEntity<User> responseEntity2 = this .restTemplate.exchange(requestEntity, User.class);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Entity<User> requestEntity = RequestEntity .post(new URI (uri)) .contentType(MediaType.APPLICATION_JSON) .body(user); Entity<User> requestEntity = RequestEntity .post(new URI (uri)) .accept(MediaType.APPLICATION_JSON) .body(user); Entity<User> requestEntity = RequestEntity .post(new URI (uri)) .header("Authorization" , "Basic " + base64Credentials) .body(user);
捕获异常 捕获HttpServerErrorException
1 2 3 4 try { responseEntity = restTemplate.exchange(requestEntity, String.class);} catch (HttpServerErrorException e) { }
自定义异常处理器 1 2 3 4 5 public class CustomErrorHandler extends DefaultResponseErrorHandler { @Override public void handleError (ClientHttpResponse response) throws IOException { }}
然后设置下异常处理器: 1 2 3 4 5 6 7 8 @Configurationpublic class RestClientConfig { @Bean public RestTemplate restTemplate () { RestTemplate restTemplate = new RestTemplate (); restTemplate.setErrorHandler(new CustomErrorHandler ()); return restTemplate; } }
配置类 创建HttpClientConfig
类,设置连接池大小、超时时间、重试机制等。配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 @Configuration @EnableScheduling public class HttpClientConfig { private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfig.class); @Resource private HttpClientProperties p; @Bean public PoolingHttpClientConnectionManager poolingConnectionManager () { SSLContextBuilder builder = new SSLContextBuilder (); try { builder.loadTrustMaterial(null , new TrustStrategy () { public boolean isTrusted (X509Certificate[] arg0, String arg1) { return true ; } }); } catch (NoSuchAlgorithmException | KeyStoreException e) { LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e); } SSLConnectionSocketFactory sslsf = null ; try { sslsf = new SSLConnectionSocketFactory (builder.build()); } catch (KeyManagementException | NoSuchAlgorithmException e) { LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e); } Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder .<ConnectionSocketFactory>create() .register("https" , sslsf) .register("http" , new PlainConnectionSocketFactory ()) .build(); PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager (socketFactoryRegistry); poolingConnectionManager.setMaxTotal(p.getMaxTotalConnections()); poolingConnectionManager.setDefaultMaxPerRoute(p.getDefaultMaxPerRoute()); return poolingConnectionManager; } @Bean public ConnectionKeepAliveStrategy connectionKeepAliveStrategy () { return new ConnectionKeepAliveStrategy () { @Override public long getKeepAliveDuration (HttpResponse response, HttpContext httpContext) { HeaderElementIterator it = new BasicHeaderElementIterator (response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase("timeout" )) { return Long.parseLong(value) * 1000 ; } } return p.getDefaultKeepAliveTimeMillis(); } }; } @Bean public CloseableHttpClient httpClient () { RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(p.getRequestTimeout()) .setConnectTimeout(p.getConnectTimeout()) .setSocketTimeout(p.getSocketTimeout()).build(); return HttpClients.custom() .setDefaultRequestConfig(requestConfig) .setConnectionManager(poolingConnectionManager()) .setKeepAliveStrategy(connectionKeepAliveStrategy()) .setRetryHandler(new DefaultHttpRequestRetryHandler (3 , true )) .build(); } @Bean public Runnable idleConnectionMonitor (final PoolingHttpClientConnectionManager connectionManager) { return new Runnable () { @Override @Scheduled(fixedDelay = 10000) public void run () { try { if (connectionManager != null ) { LOGGER.trace("run IdleConnectionMonitor - Closing expired and idle connections..." ); connectionManager.closeExpiredConnections(); connectionManager.closeIdleConnections(p.getCloseIdleConnectionWaitTimeSecs(), TimeUnit.SECONDS); } else { LOGGER.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised" ); } } catch (Exception e) { LOGGER.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}" , e.getMessage(), e); } } }; } @Bean public TaskScheduler taskScheduler () { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler (); scheduler.setThreadNamePrefix("poolScheduler" ); scheduler.setPoolSize(50 ); return scheduler; } }
然后再配置RestTemplateConfig类,使用之前配置好的CloseableHttpClient类注入,同时配置一些默认的消息转换器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class RestTemplateConfig { @Resource private CloseableHttpClient httpClient; @Bean public RestTemplate restTemplate (MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { RestTemplate restTemplate = new RestTemplate (clientHttpRequestFactory()); List<HttpMessageConverter<?>> messageConverters = new ArrayList <>(); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter (Charset.forName("utf-8" )); messageConverters.add(stringHttpMessageConverter); messageConverters.add(jackson2HttpMessageConverter); restTemplate.setMessageConverters(messageConverters); return restTemplate; } @Bean public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory () { HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory (); rf.setHttpClient(httpClient); return rf; } }
发送文件 1 2 3 4 5 6 MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap <>(); multiPartBody.add("file" , new ClassPathResource ("/tmp/user.txt" )); RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity .post(uri) .contentType(MediaType.MULTIPART_FORM_DATA) .body(multiPartBody);
下载文件 1 2 3 4 5 6 7 8 9 10 11 12 RequestEntity requestEntity = RequestEntity.get(uri).build();ResponseEntity<byte []> responseEntity = restTemplate.exchange(requestEntity, byte [].class);byte [] downloadContent = responseEntity.getBody(); ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor <ResponseEntity<File>>() { @Override public ResponseEntity<File> extractData (ClientHttpResponse response) throws IOException { File rcvFile = File.createTempFile("rcvFile" , "zip" ); FileCopyUtils.copy(response.getBody(), new FileOutputStream (rcvFile)); return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile); } }; File getFile = this .restTemplate.execute(targetUri, HttpMethod.GET, null , responseExtractor);
Service注入 1 2 3 4 5 @Servicepublic class DeviceService { @Resource private RestTemplate restTemplate; }
实际使用例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 LoginParam loginParam = new LoginParam ();loginParam.setUsername(managerInfo.getUsername()); loginParam.setPassword(managerInfo.getPassword());HttpBaseResponse r = restTemplate.postForObject( p.getPosapiUrlPrefix() + "/notifyLogin" , loginParam, HttpBaseResponse.class); if (r.isSuccess()) { logger.info("推送消息登录认证成功" ); String token = (String) r.getData(); UnbindParam unbindParam = new UnbindParam (); unbindParam.setImei(pos.getImei()); unbindParam.setLocation(location); URI uri; try { uri = new URI (p.getPosapiUrlPrefix() + "/notify/unbind" ); } catch (URISyntaxException e) { logger.error("URI构建失败" , e); return 1 ; } RequestEntity<UnbindParam> requestEntity = RequestEntity .post(uri) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header("Authorization" , token) .body(unbindParam); ResponseEntity<HttpBaseResponse> responseEntity = restTemplate.exchange(requestEntity, HttpBaseResponse.class); HttpBaseResponse r2 = responseEntity.getBody(); if (r2.isSuccess()) { logger.info("推送消息解绑网点成功" ); } else { logger.error("推送消息解绑网点失败,errmsg = " + r2.getMsg()); } } else { logger.error("推送消息登录认证失败" ); }
本文地址: SpringBoot实现RESTful与RestTemplate
推荐
JAVA自定义注解
本文地址: https://github.com/maxzhao-it/blog/post/40132/