行业资讯
SpringCloud-RestTemplate发送Http请求讲解
一、RestTemplate 讲解1. 没有 RestTemplate 时Java 怎么发 HTTP 请求1.1 用 JDK 原生的 HttpURLConnection// 发一个 GET 请求获取用户信息 public String getUser(Long id) { try { URL url new URL(http://api.example.com/users/ id); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(GET); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); // 读取响应 BufferedReader reader new BufferedReader( new InputStreamReader(conn.getInputStream()) ); StringBuilder response new StringBuilder(); String line; while ((line reader.readLine()) ! null) { response.append(line); } reader.close(); return response.toString(); // 返回的是 JSON 字符串 } catch (IOException e) { throw new RuntimeException(e); } }弊端每发一个请求要写 20 行样板代码没有自动的 JSON 转换返回的是字符串你需要自己用ObjectMapper转成对象错误处理繁琐HTTP 404、500 需要手动判断conn.getResponseCode()没有连接复用每次openConnection()都新建 TCP 连接没有请求模板化每个接口都要重复写超时、请求头设置1.2 所以 Spring 设计了 RestTemplateRestTemplate的核心设计目标像调用本地方法一样发起 HTTP 请求2. RestTemplate 是什么它是Spring 提供的同步 HTTP 客户端封装了 JDK 的HttpURLConnection或 Apache HttpClient、OkHttp让你用更简洁的方式发送 HTTP 请求。它在 Spring 生态中的定位你的 Service 层 ↓ RestTemplate 发 HTTP 请求 ↓ HttpMessageConverter 对象 ↔ JSON/XML 转换 ↓ ClientHttpRequestFactory 实际网络层HttpURLConnection / HttpClient / OkHttp ↓ 目标服务器3. 基本用法对比原生代码3.1 GET 请求原生写法对比// 返回 JSON 字符串自己解析 String json getUser(1L); User user objectMapper.readValue(json, User.class);RestTemplate 写法Autowired private RestTemplate restTemplate; public User getUser(Long id) { // 直接返回 User 对象JSON 转换自动完成 User user restTemplate.getForObject( http://api.example.com/users/{id}, // URL{id} 是占位符 User.class, // 返回类型 id // 占位符的值 ); return user; }为什么能直接返回User对象因为RestTemplate内部注册了MappingJackson2HttpMessageConverter它自动把响应的 JSON 字符串反序列化成User对象。3.2 POST 请求带请求体public User createUser(UserDTO dto) { // 发送 POST 请求dto 自动序列化为 JSON User user restTemplate.postForObject( http://api.example.com/users, dto, // 请求体自动转 JSON User.class // 返回类型 ); return user; }3.3 带请求头如 Token 鉴权public User getUserWithAuth(Long id, String token) { // 构建请求头 HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer token); headers.setContentType(MediaType.APPLICATION_JSON); // 构建请求实体头 体 HttpEntityVoid requestEntity new HttpEntity(headers); // 发送请求 ResponseEntityUser response restTemplate.exchange( http://api.example.com/users/{id}, HttpMethod.GET, requestEntity, User.class, id ); // 获取状态码、响应头、响应体 HttpStatus status response.getStatusCode(); User user response.getBody(); return user; }exchange是最通用的方法可以处理所有 HTTP 方法并且能拿到完整的响应信息状态码、Header、Body。4. 核心设计为什么 RestTemplate 要这样设计4.1 为什么有getForObject和getForEntity两个方法// 方法 1只关心返回的数据 User user restTemplate.getForObject(url, User.class, id); // 方法 2关心完整响应状态码 Header Body ResponseEntityUser response restTemplate.getForEntity(url, User.class, id); HttpStatus status response.getStatusCode(); HttpHeaders headers response.getHeaders(); User user response.getBody();设计原因80% 的场景你只需要数据所以getForObject简化了调用20% 的场景你需要判断 HTTP 状态码或读取响应头所以提供getForEntity4.2 为什么 URL 支持{id}占位符restTemplate.getForObject( http://api.example.com/users/{id}/orders/{orderId}, Order.class, 1L, // 对应 {id} 100L // 对应 {orderId} );设计原因避免字符串拼接url / id容易出错且可读性差内部自动做 URL 编码比如参数里有空格、中文会自动转义4.3 HttpMessageConverter自动转换的秘密RestTemplate能自动处理 JSON是因为它内部维护了一个转换器列表RestTemplate ├── MappingJackson2HttpMessageConverter ← 处理 application/json ├── StringHttpMessageConverter ← 处理 text/plain ├── FormHttpMessageConverter ← 处理 application/x-www-form-urlencoded ├── ByteArrayHttpMessageConverter ← 处理二进制 └── ...执行流程发请求时 UserDTO 对象 ↓ 遍历 converters找到能处理 application/json 的 ↓ MappingJackson2HttpMessageConverter.write() ↓ JSON 字符串 → 发送 收响应时 响应 Content-Type: application/json ↓ 遍历 converters找到能处理 JSON 的 ↓ MappingJackson2HttpMessageConverter.read() ↓ User 对象5. 企业级用法RestTemplate 的配置5.1 为什么需要手动创建 RestTemplateSpring Boot 不会自动注入RestTemplate不像RedisTemplate你需要自己配置。原因HTTP 客户端的配置差异很大超时时间、连接池、SSL、代理框架无法预设一个适合所有场景的默认值。5.2 企业级配置类Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { // 使用 Apache HttpClient 作为底层比 JDK 的 HttpURLConnection 性能更好 HttpClient httpClient HttpClientBuilder.create() .setMaxConnTotal(200) // 最大连接数 .setMaxConnPerRoute(50) // 每个路由的最大连接如 api.example.com .setConnectionTimeToLive(30, TimeUnit.SECONDS) // 连接存活时间 .build(); // 创建工厂 HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(httpClient); factory.setConnectTimeout(5000); // 连接超时 5 秒 factory.setReadTimeout(10000); // 读取超时 10 秒 RestTemplate restTemplate new RestTemplate(factory); // 添加拦截器如统一加请求头、记录日志 restTemplate.getInterceptors().add(new LoggingInterceptor()); return restTemplate; } }5.3 为什么要用连接池没有连接池的问题每次请求都新建 TCP 连接三次握手高并发时TCP 连接数暴涨导致TIME_WAIT状态过多响应延迟高使用 Apache HttpClient 连接池后连接复用避免重复握手控制最大连接数防止资源耗尽支持 HTTP/1.1 的 Keep-Alive6. RestTemplate 的局限为什么 Spring 官方不推荐在新项目使用6.1 它是同步阻塞的User user restTemplate.getForObject(url, User.class); // 这行代码会阻塞当前线程直到 HTTP 响应返回 // 在高并发下线程池很快被耗尽弊端线程阻塞等待网络 IOCPU 空转不适合高并发场景如网关、聚合服务需要同时调多个下游接口6.2 替代方案WebClientSpring 5 引入Autowired private WebClient webClient; public MonoUser getUser(Long id) { return webClient.get() .uri(http://api.example.com/users/{id}, id) .retrieve() .bodyToMono(User.class); // 非阻塞返回 Mono响应式 }WebClient的优势基于 Netty异步非阻塞一个线程可以并发处理多个请求适合微服务架构下的高并发调用6.3 但 RestTemplate 仍然广泛使用因为代码简单直观适合大多数 CRUD 场景团队熟悉度高维护成本低很多老项目仍在使用7. 总结问题RestTemplate 的解决方案发 HTTP 请求代码繁琐封装为getForObject、postForObject等一行调用JSON 转换麻烦HttpMessageConverter自动序列化/反序列化URL 拼接易出错占位符{id}自动替换和编码需要处理响应状态码ResponseEntity封装完整响应连接管理低效可配置ClientHttpRequestFactory使用连接池同步阻塞性能差新项目推荐WebClient一句话RestTemplate是 Spring 对 HTTP 客户端的封装让你用调用本地方法的方式发起远程 HTTP 请求屏蔽了网络通信的底层细节。二、ObjectMapper 讲解1. 没有 ObjectMapper 时Java 怎么处理 JSON1.1 手动拼接 JSON 字符串假设你有一个User对象要返回给前端public class User { private Long id; private String name; private Integer age; private LocalDateTime createTime; // getter / setter }没有 ObjectMapper你只能手动拼字符串public String userToJson(User user) { return { \id\: user.getId() , \name\:\ user.getName() \, \age\: user.getAge() , \createTime\:\ user.getCreateTime() \ }; }弊端字段多了根本没法维护字符串拼接容易漏逗号、引号语法错误如果name里有特殊字符如或换行直接破坏 JSON 格式日期格式不统一有的接口返回2024-01-01有的返回毫秒时间戳嵌套对象User里有Address时拼接复杂度爆炸1.2 手动解析 JSON 更痛苦前端传过来一个 JSON 字符串你要转成 Java 对象{id:1,name:张三,age:25,createTime:2024-01-15 10:30:00}没有 ObjectMapper你只能手动解析public User jsonToUser(String json) { // 用字符串截取正则根本不可行 // 只能用 org.json 或 fastjson 的底层 API一行行 get JSONObject obj new JSONObject(json); User user new User(); user.setId(obj.getLong(id)); user.setName(obj.getString(name)); user.setAge(obj.getInt(age)); // ... 每个字段都要手动映射 return user; }弊端字段多了写死人字段名拼写错误在运行时才暴露类型转换容易出错比如 JSON 里age传了字符串25手动getInt可能抛异常嵌套对象需要递归解析1.3 所以 Jackson 库被创建出来Jackson 是 Java 世界最流行的 JSON 处理库而ObjectMapper是它的核心入口类。2. ObjectMapper 是什么ObjectMapper是 Jackson 库提供的对象映射器负责方向方法俗称Java 对象 → JSON 字符串writeValueAsString()序列化SerializationJSON 字符串 → Java 对象readValue()反序列化Deserialization3. 核心用法演示3.1 序列化对象 → JSONUser user new User(); user.setId(1L); user.setName(张三); user.setAge(25); ObjectMapper mapper new ObjectMapper(); String json mapper.writeValueAsString(user); System.out.println(json); // 输出{id:1,name:张三,age:25}对比手动拼接一行代码搞定自动处理引号、转义、特殊字符字段再多也不怕3.2 反序列化JSON → 对象String json {\id\:1,\name\:\张三\,\age\:25}; ObjectMapper mapper new ObjectMapper(); User user mapper.readValue(json, User.class); System.out.println(user.getName()); // 输出张三对比手动解析一行代码自动映射所有字段类型安全id自动转Longage自动转Integer字段名对应不上时报错明确3.3 处理集合和嵌套对象// List 序列化 ListUser list Arrays.asList(user1, user2); String json mapper.writeValueAsString(list); // 输出[{id:1,name:张三},{id:2,name:李四}] // List 反序列化 ListUser users mapper.readValue(json, new TypeReferenceListUser() {});TypeReference是什么因为 Java 泛型擦除mapper.readValue(json, List.class)无法知道 List 里是什么类型。TypeReference是 Jackson 提供的技巧利用匿名内部类保留泛型信息。4. Spring Boot 中 ObjectMapper 的自动配置4.1 你几乎不需要手动 new ObjectMapper()在 Spring Boot 项目中当你引入spring-boot-starter-webJackson 作为默认 JSON 库被引入Spring Boot 自动配置了一个ObjectMapperBean// Spring Boot 自动配置的源码简化 Bean Primary public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); // 自动应用 application.yml 中的配置 // 自动注册 classpath 下的模块 return mapper; }这意味着RestController返回对象时Spring 自动用这个ObjectMapper转 JSONRequestBody接收 JSON 时Spring 自动用这个ObjectMapper转对象你可以直接Autowired注入使用4.2 实际使用方式方式一让 Spring 自动处理最常用RestController public class UserController { GetMapping(/user/{id}) public User getUser(PathVariable Long id) { User user userService.getById(id); return user; // Spring 自动用 ObjectMapper 转成 JSON 返回 } PostMapping(/user) public User createUser(RequestBody UserDTO dto) { // Spring 自动用 ObjectMapper 把请求体的 JSON 转成 UserDTO return userService.save(dto); } }方式二手动注入使用特殊场景Service public class UserService { Autowired private ObjectMapper objectMapper; public void cacheUser(User user) { // 手动转成 JSON 字符串存入 Redis String json objectMapper.writeValueAsString(user); redisTemplate.opsForValue().set(user: user.getId(), json); } }5. ObjectMapper 的常用配置5.1 为什么需要配置默认的ObjectMapper行为不一定符合企业需求比如日期字段默认序列化为时间戳如1705312200000前端看不懂空对象可能抛异常字段命名风格可能不匹配Java 用驼峰userName前端要下划线user_name5.2 通过 application.yml 配置推荐spring: jackson: date-format: yyyy-MM-dd HH:mm:ss # 日期格式 time-zone: GMT8 # 时区 default-property-inclusion: non_null # 忽略 null 字段不输出 property-naming-strategy: SNAKE_CASE # 驼峰转下划线效果对比public class User { private Long id; private String name; private String email; // 假设为 null private LocalDateTime createTime; } // 默认配置输出 // {id:1,name:张三,email:null,createTime:1705312200000} // 加上面配置后输出 // {id:1,name:张三,create_time:2024-01-15 10:30:00} // 注意email 为 null 被忽略了createTime 变成了下划线 可读日期5.3 通过代码配置更灵活Bean public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); // 1. 日期格式转字符串不用时间戳 mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setDateFormat(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss)); // 2. 忽略 null 字段 mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 3. 驼峰转下划线 mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); // 4. 允许单引号前端有时传单引号 JSON mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); // 5. 未知字段不报错前端传了后端没有的字段忽略而不是抛异常 mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); return mapper; }6. 核心配置项详解企业级常用配置作用场景WRITE_DATES_AS_TIMESTAMPS控制日期序列化格式禁用后输出2024-01-15启用后输出1705312200000NON_NULL忽略值为 null 的字段减少 JSON 体积前端不用判空SNAKE_CASE驼峰命名转下划线Java 用userNameJSON 用user_nameFAIL_ON_UNKNOWN_PROPERTIES遇到未知字段是否报错禁用后前端多传字段不会导致 400 错误ALLOW_SINGLE_QUOTES允许 JSON 用单引号包裹兼容不规范的前端 JSON7. 注意事项7.1 ObjectMapper 是线程安全的// 正确全局复用一个实例 Service public class UserService { Autowired private ObjectMapper mapper; // Spring 注入的单例线程安全 } // 错误每次方法调用都 new 一个浪费资源 public void bad() { ObjectMapper mapper new ObjectMapper(); // 不要这样写 }设计原因ObjectMapper内部有大量缓存如类结构反射信息创建成本高应该复用。7.2 不要混用不同的 ObjectMapper 实例如果你手动new ObjectMapper()它的配置和 Spring 自动配置的那个可能不一样导致Controller 返回的 JSON 格式是一套规则你手动序列化存 Redis 的 JSON 是另一套规则两边不一致反序列化时可能出问题企业级做法统一使用 Spring 注入的ObjectMapper或者确保手动创建的实例配置完全一致。8. 总结问题ObjectMapper 的解决方案手动拼 JSON 容易出错writeValueAsString()自动序列化手动解析 JSON 繁琐readValue()自动反序列化日期格式不统一配置date-format字段命名风格不一致配置SNAKE_CASEnull 字段污染 JSON配置NON_NULL前端多传字段导致报错配置FAIL_ON_UNKNOWN_PROPERTIES一句话ObjectMapper是 Java 世界处理 JSON 的翻译官负责 Java 对象和 JSON 字符串之间的双向转换。在 Spring Boot 中它已经被自动配置好你只需要通过application.yml或代码调整它的行为就能统一控制整个应用的 JSON 格式。
郑州网站建设
网页设计
企业官网