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 实例对象是什么时候创建和销毁的呢?

  1. client请求server;
  2. server根据http协议格式解析请求内容;创建请求对象: HttpServletRequest 的实现类 RequestFacade 的对象
  3. 通过set方法,将解析出的数据封装到请求对象中, HttpServletRequest 实例初始化完毕。
  4. 建立响应对象,server向client发送响应。
  5. 销毁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);
}

hutool工具类:

<!-- 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

String getRemoteAddr()

获取客户机的IP地址

注意如果反向代理或者路由转发等无法获取到客户机ip地址,见上文

String getParameter(String name)

根据参数名称获取参数值

String[] getParameterValues(String name)

根据参数名称获取参数值的数组

Enumeration getParameterNames()

获取所有请求的参数名称

Map<String,String[]> getParameterMap()

获取所有参数的map集合

  1. header内容获取

方法

说明

备注

String getHeader(String name)

通过请求头的名称获取请求头的值

Enumeration getHeaderNames()

获取所有的请求头名称

  1. body请求体获取,只有post方法支持

方法

说明

备注

BufferedReader getReader()

获取字符输入流,只能操作字符数据

ServletInputStream getInputStream()

获取字节输入流,可以操作所有类型数据

  1. 请求转发(路由转发)
    一种在服务器内部的资源跳转方式(路由转发,区别于网址重定向)
    特点:
    a. 浏览器地址栏路径不发生变化
    b. 转发只能访问当前服务器下的资源
    c. 转发是一次请求,可以使用request对象来共享数据

方法

说明

备注

RequestDispatcher getRequestDispatcher(String path)

通过request对象获取请求转发器对象

路由跳转、请求转发

forward(ServletRequest request, ServletResponse response)

使用RequestDispatcher对象来进行转发

路由跳转、请求转发

  1. 共享数据:

域对象:
一个有作用范围的对象,可以在范围内共享数据

request域
代表一次请求的范围,一般用于请求转发的多个资源中共享数据

方法

说明

备注

void setAttribute(String name,Object obj)

存储数据

Object getAttitude(String name)

通过键获取值

void removeAttribute(String name)

通过键移除键值对

5>中文乱码问题

  1. get方式:tomcat 8 已经将get方式乱码问题解决了
  2. 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

  1. 设置响应消息

方法

说明

备注

setStatus(int sc)

设置状态码

setHeader(String name, String value)

设置响应头

  1. 设置响应体

方法

说明

备注

PrintWriter getWriter()

获取字符输出流

ServletOutputStream getOutputStream()

获取字节输出流

  1. 重定向
    特点:
  2. 传递302消息给浏览器,地址栏发生变化
  3. 请求参数将失效
  4. 重定向是两次请求,不能使用request对象来共享数据
  5. 重定向可以访问其他站点(服务器)的资源

方法

说明

备注

response.sendRedirect(url)

url重定向

3>中文乱码问题

  1. PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1
  2. 设置该流的默认编码
  3. 告诉浏览器响应体使用的编码
//简单的形式,设置编码,是在获取流之前设置
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);
    }