1,概念
1)REST API规范
2)Spring MVC常用注解
2,java对象
1)Request对象
1>类图
javax.servlet.ServletRequest -- 父接口
| 继承
javax.servlet.http.HttpServletRequest -- 接口 表示请求
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat)
2>工作机制及生命周期
HttpServletRequest 实例对象是什么时候创建和销毁的呢?
- client请求server;
- server根据http协议格式解析请求内容;创建请求对象: HttpServletRequest 的实现类 RequestFacade 的对象
- 通过set方法,将解析出的数据封装到请求对象中, HttpServletRequest 实例初始化完毕。
- 建立响应对象,server向client发送响应。
- 销毁HttpServletRequest 实例对象。
3>使用
//方法参数声明HttpServletRequest对象,会自动接收到HttpServletRequest请求数据。
@GetMapping
public String get(HttpServletRequest request){
//获取客户端的IP地址
//如果使用了反向代理那么拿到的数据是127.0.0.1或 192.168.1.110;
request.getRemoteAddr();
//如果使用了路由转发那么拿到的是转发服务的ip(服务器ip),此时可以使用HuTool的工具类拿到真实客户端ip
ServletUtil.getClientIP(request);
}
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.5</version>
</dependency>
4>常用api
举例:当前请求 GET /day14/demo1?name=zhangsan HTTP/1.1
1.属性获取
方法 | 说明 | 备注 |
String getMethod() | 获取请求方式 :GET | |
String getContextPath() | 获取虚拟目录:/day14 | |
String getServletPath() | 获取Servlet路径: /demo1 | |
String getQueryString() | 获取get方式请求参数:name=zhangsan | |
String getProtocol() | 获取协议及版本:HTTP/1.1 | |
| 获取客户机的IP地址 | 注意如果反向代理或者路由转发等无法获取到客户机ip地址,见上文 |
String getParameter(String name) | 根据参数名称获取参数值 | |
String[] getParameterValues(String name) | 根据参数名称获取参数值的数组 | |
Enumeration getParameterNames() | 获取所有请求的参数名称 | |
Map<String,String[]> getParameterMap() | 获取所有参数的map集合 |
- header内容获取
方法 | 说明 | 备注 |
String getHeader(String name) | 通过请求头的名称获取请求头的值 | |
Enumeration getHeaderNames() | 获取所有的请求头名称 |
- body请求体获取,只有post方法支持
方法 | 说明 | 备注 |
BufferedReader getReader() | 获取字符输入流,只能操作字符数据 | |
ServletInputStream getInputStream() | 获取字节输入流,可以操作所有类型数据 |
- 请求转发(路由转发)
一种在服务器内部的资源跳转方式(路由转发,区别于网址重定向)
特点:
a. 浏览器地址栏路径不发生变化
b. 转发只能访问当前服务器下的资源
c. 转发是一次请求,可以使用request对象来共享数据
方法 | 说明 | 备注 |
RequestDispatcher getRequestDispatcher(String path) | 通过request对象获取请求转发器对象 | 路由跳转、请求转发 |
forward(ServletRequest request, ServletResponse response) | 使用RequestDispatcher对象来进行转发 | 路由跳转、请求转发 |
- 共享数据:
域对象:
一个有作用范围的对象,可以在范围内共享数据request域
代表一次请求的范围,一般用于请求转发的多个资源中共享数据
方法 | 说明 | 备注 |
void setAttribute(String name,Object obj) | 存储数据 | |
Object getAttitude(String name) | 通过键获取值 | |
void removeAttribute(String name) | 通过键移除键值对 |
5>中文乱码问题
- get方式:tomcat 8 已经将get方式乱码问题解决了
- post方式:会乱码
解决:在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");
2)Response对象
1>使用
//方法参数直接声明HttpServletResponse 即可以操作响应对象
public FreeLoginUrlVo freeLogin(HttpServletResponse response) {
String url = "https://xxx";
try {
//url重定向 传递302消息给浏览器 请求参数将失效
response.sendRedirect(url);
} catch (IOException e) {
log.error("免登录发生错误,请检查当前免登录配置和第三方设备配置", e);
}
return freeLoginUrlVo;
}
2>常见API
- 设置响应消息
方法 | 说明 | 备注 |
setStatus(int sc) | 设置状态码 | |
setHeader(String name, String value) | 设置响应头 |
- 设置响应体
方法 | 说明 | 备注 |
PrintWriter getWriter() | 获取字符输出流 | |
ServletOutputStream getOutputStream() | 获取字节输出流 |
- 重定向
特点: - 传递302消息给浏览器,地址栏发生变化
- 请求参数将失效
- 重定向是两次请求,不能使用request对象来共享数据
- 重定向可以访问其他站点(服务器)的资源
方法 | 说明 | 备注 |
response.sendRedirect(url) | url重定向 |
3>中文乱码问题
- PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1
- 设置该流的默认编码
- 告诉浏览器响应体使用的编码
//简单的形式,设置编码,是在获取流之前设置
response.setContentType("text/html;charset=utf-8");
3)
3,Httpclient(推荐)
HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传统 JDK 自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。
1)使用
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
1>GET
//创建 HttpClient 的实例 记得传输完成后要close
try(CloseableHttpClient client = getClient()){
HttpGet httpGet = new HttpGet(url);
byte[] bytes = null;
try{
HttpResponse response = client.execute(httpGet);
//下载文件的字节流 response.getEntity()才是真实的返回数据 响应内容长度:responseEntity.getContentLength()
bytes = EntityUtils.toByteArray(response.getEntity());
}catch (IOException e) {
}finally {
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
}
//将文件保存到指定路径
filePath = filePath + File.separator + eventName + ".docx";
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(bytes);
fos.flush();
}
}catch(){
}
getClient()中设置了忽略对服务端的SSL证书校验
private CloseableHttpClient getClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (TrustStrategy) (arg0, arg1) -> true).build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
return client;
}
2>POST
//创建 HttpClient 的实例
HttpClient client = getClient();
HttpPost httpPost = new HttpPost(url);
//设置Header
httpPost.addHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Accept", "application/json");
//设置body参数
HttpEntity httpEntity = new StringEntity(JSONObject.toJSONString(userVo), Charset.forName("UTF-8"));
httpPost.setEntity(httpEntity);
//请求
HttpResponse response = client.execute(httpPost);
//获取请求结果
JSONObject result = JSONObject.parseObject(EntityUtils.toString(response.getEntity()), JSONObject.class);
4,HttpURLConnection
HttpURLConnection 是 Java 的标准类,它继承自 URLConnection,可用于向指定网站发送 GET 请求、POST 请求。HttpURLConnection 使用比较复杂,不像 HttpClient 那样容易使用。
5,RestTemplate
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端。
1)使用
- url:请求地址。
- method:HTTP方法。
- requestEntity:请求体的实体对象,可包括请求头和请求参数等信息。
- responseType:响应体的类型,可以是简单类型,也可以是复合类型,如List、Map等。
- uriVariables:可选参数,表示URL中的占位符,例如/user/{userId}中的userId。
HTTP method | RestTemplate类方法 | 说明 |
DELETE | delete(String url, Object… uriVariables) | |
delete(String url, Class responseType, Object… uriVariables) | 将响应体转换为指定的Java对象。 | |
deleteForObject(String url, Object… uriVariables) | 将响应体转换为指定的Java对象。 | |
GET | getForObject(String url, Class responseType, Object… uriVariables) | 将响应体转换为指定的Java对象。 |
getForEntity(String url, Class responseType, Object… uriVariables) | 将响应体封装到ResponseEntity对象中。 | |
POST | postForEntity(String url, Object request, Class responseType, Object… uriVariables) | 将请求体和响应体封装到ResponseEntity对象中。 |
postForObject(String url, Object request, Class responseType, Object… uriVariables) | 将响应体转换为指定的Java对象。 | |
PUT | put(String url, Object request, Object… uriVariables) | |
PATCH | atchForObject(String url, Object request, Class responseType, Object… uriVariables) | 发送PATCH请求,并将响应体转换为指定的Java对象。 |
HEAD | headForHeaders(String url, Object… uriVariables) | 发送HEAD请求,并获取响应头信息 |
OPTIONS | optionsForAllow(String url, Object… uriVariables) | 发送OPTIONS请求,并获取允许的HTTP请求方法。 |
any(通用请求方法) | exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class responseType, Object… uriVariables) | 发送任意HTTP请求,并将响应体转换为指定的Java对象。 |
exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference responseType, Object… uriVariables) | 发送任意HTTP请求,并将响应体封装到ResponseEntity对象中。 |
1>无参数请求,将返回提转换为ResponseEntity对象
//也可以@Autowired
RestTemplate restTemplate = new RestTemplate();
//getForObject, 将返回的请求体 映射为一个对象
ResponseEntity<String> entity = restTemplate.getForEntity(uri, String.class);
2>RequestParam + 路径参数:
String uri = "https://sandbox-openapi.chanjet.com/accounting/gl/statistics/pay/{bookid}?period={period}";
// params
Map<String, String> params = new HashMap<>();
params.put("bookid", bookid);
params.put("period", period);
//直接给值也可以 restTemplate.getForEntity(uri, String.class, bookid, period);
ResponseEntity<String> entity = restTemplate.getForEntity(uri, String.class, params);
3>body参数
// body post的请求体
JSONObject json = new JSONObject();
json.put("XXX", XXX);
json.put("XXX", XXX);
HttpEntity<JSONObject> request = new HttpEntity<>(json, httpHeaders);
//如果还有路径或者request参数:param,直接加后面 client.postForEntity(uri, request, String.class, param);
ResponseEntity<String> entity = restTemplate.postForEntity(uri, request, String.class);
4>header
// 添加headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers .add("openToken", openToken);
HttpEntity<Object> objectHttpEntity = new HttpEntity<>(headers);
ResponseEntity<String> entity = restTemplate.postForEntity(uri, request, String.class);
5>添加超时时间
//设置超时时间
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(60 * 1000);
requestFactory.setReadTimeout(60 * 1000);
restTemplate.setRequestFactory(requestFactory);
6,spring-cloud-starter-openfeign(推荐)
1)OpenFeign概念
通过java接口注解的方式调用Http请求。更加直观。
spring-cloud-starter-feign
Feign是Netflix开源的轻量级RESTful的HTTP服务客户端。
Feign可以(通过客户端负载均衡的方式)作为客户端,去调用服务注册中心的服务。
Feign和OpenFeign作用一样,都是进行远程调用的组件,里面都内置了Ribbon。Feign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡。
OpenFeign在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
2)使用
首先,对于服务提供者,不需要进行任何配置,feign的访问对于服务提供者其实就是一次http访问。
需要对服务调用者进行feign的相关配置:
1>创建spring boot项目
使用feign调用,通信的两者之间必须都在同一个注册中心(Eureka或nacos)。
在Eureka上注册服务: 服务的发现:Eureka + 服务的消费:Ribbon
2>openfeign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3>启用Feign功能
添加注解:@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableDiscoveryClient
public class ConsumerServiceApplication {
}
4>配置yml文件
Eureka相关配置好即可。
5>声明一个Client接口
在client包下创建接口:
//name不可缺省,其它可以不填。configuration 是对Feign调用的一些配置;url是访问链接,比如指向本地进行联调
@FeignClient(name="userinfo-service", configuration = FeignConfig.class, url = "")
@Component
public interface UserinfoServiceClient {
@GetMapping("/userinfo/getUserinfoFromRedis") //注意路径正确
public ResponseMessage<UserinfoForm> getUserinfoFromRedis(@RequestParam("id") Long id);
}
相当于绑定了一个名叫userinfo-service( 不区分大小写)的服务提供者提供的/userinfo/getUserinfoFromRedis
接口。
6>实验与测试:调用feign服务
@Autowired
UserinfoServiceClient userinfoServiceClient;
……
userinfoServiceClient.getUserinfoFromRedis(id).getData();
7>超时设置
给FeignClient,添加属性配置:configuration
@FeignClient(name = "user-service", configuration = FeignConfig.class)
配置类:
@Configuration
public class FeignConfig{
private static final int CONNECT_TIME_OUT_MILLIS = 1000*600;
private static final int READ_TIME_OUT_MILLIS = 1000*600;
@Bean
public Request.Options options() {
return new Request.Options(CONNECT_TIME_OUT_MILLIS, READ_TIME_OUT_MILLIS);
}
@Bean
public Retryer feignRetryer(){
return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1),5);
}
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
到此就完了。也可以通过java代码设置:
public UserinfoServiceClient getUserinfoServiceClient () {
Request.Options connectionConfig =
new Request.Options(CONNECT_TIME_OUT_MILLIS, READ_TIME_OUT_MILLIS);
Feign.Builder builder = Feign.builder();
builder.decoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
builder.encoder(new SpringEncoder(this.messageConverters));
builder.options(connectionConfig);
SSLContext context = null;
try {
context = new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build();
} catch (Exception e) {
}
Client trustSSLSockets = new Client.Default(context.getSocketFactory(), new NoopHostnameVerifier());
builder.client(trustSSLSockets);
builder.contract(new SpringMvcContract());
//设置feign访问的url,与url属性功能一致。
return builder.target(UserinfoServiceClient .class, userUrl);
}