<返回目录     Powered by claud/xia兄

第5课: 服务调用

RestTemplate、OpenFeign、WebClient 服务间通信实战

微服务间通信方式

在微服务架构中,服务间通信是核心功能。主要有以下几种方式:

RestTemplate 同步调用

RestTemplate 是 Spring 提供的同步 HTTP 客户端,简单易用。

1. RestTemplate 基础配置

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced  // 启用负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    // 自定义配置
    @Bean
    public RestTemplate customRestTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(5000);  // 连接超时5秒
        factory.setReadTimeout(10000);    // 读取超时10秒
        return new RestTemplate(factory);
    }
}

2. GET 请求示例

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    // 1. getForObject - 直接返回对象
    public User getUserById(Long id) {
        String url = "http://user-service/api/users/" + id;
        return restTemplate.getForObject(url, User.class);
    }

    // 2. getForEntity - 返回 ResponseEntity
    public ResponseEntity<User> getUserWithStatus(Long id) {
        String url = "http://user-service/api/users/" + id;
        return restTemplate.getForEntity(url, User.class);
    }

    // 3. 带参数的 GET 请求
    public List<User> searchUsers(String keyword, Integer page) {
        String url = "http://user-service/api/users/search?keyword={keyword}&page={page}";

        Map<String, Object> params = new HashMap<>();
        params.put("keyword", keyword);
        params.put("page", page);

        return restTemplate.exchange(
            url,
            HttpMethod.GET,
            null,
            new ParameterizedTypeReference<List<User>>() {},
            params
        ).getBody();
    }
}

3. POST 请求示例

@Service
public class OrderService {

    @Autowired
    private RestTemplate restTemplate;

    // 1. postForObject - 创建订单
    public Order createOrder(OrderRequest request) {
        String url = "http://order-service/api/orders";
        return restTemplate.postForObject(url, request, Order.class);
    }

    // 2. postForEntity - 获取完整响应
    public ResponseEntity<Order> createOrderWithStatus(OrderRequest request) {
        String url = "http://order-service/api/orders";
        return restTemplate.postForEntity(url, request, Order.class);
    }

    // 3. 带请求头的 POST 请求
    public Order createOrderWithHeaders(OrderRequest request, String token) {
        String url = "http://order-service/api/orders";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "Bearer " + token);

        HttpEntity<OrderRequest> entity = new HttpEntity<>(request, headers);

        return restTemplate.postForObject(url, entity, Order.class);
    }
}

4. PUT 和 DELETE 请求

@Service
public class ProductService {

    @Autowired
    private RestTemplate restTemplate;

    // PUT 请求 - 更新产品
    public void updateProduct(Long id, Product product) {
        String url = "http://product-service/api/products/" + id;
        restTemplate.put(url, product);
    }

    // DELETE 请求 - 删除产品
    public void deleteProduct(Long id) {
        String url = "http://product-service/api/products/" + id;
        restTemplate.delete(url);
    }

    // exchange 方法 - 通用请求
    public ResponseEntity<Product> updateProductWithResponse(Long id, Product product) {
        String url = "http://product-service/api/products/" + id;

        HttpEntity<Product> entity = new HttpEntity<>(product);

        return restTemplate.exchange(
            url,
            HttpMethod.PUT,
            entity,
            Product.class
        );
    }
}

OpenFeign 声明式调用

OpenFeign 是声明式的 HTTP 客户端,通过接口和注解定义服务调用,代码更简洁。

5. OpenFeign 基础配置

// pom.xml 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

// 启动类启用 Feign
@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// application.yml 配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000
        loggerLevel: full
  compression:
    request:
      enabled: true
    response:
      enabled: true

6. Feign 客户端定义

@FeignClient(name = "user-service", path = "/api/users")
public interface UserServiceClient {

    // GET 请求
    @GetMapping("/{id}")
    User getUserById(@PathVariable("id") Long id);

    // GET 请求带参数
    @GetMapping("/search")
    List<User> searchUsers(@RequestParam("keyword") String keyword,
                           @RequestParam("page") Integer page);

    // POST 请求
    @PostMapping
    User createUser(@RequestBody User user);

    // PUT 请求
    @PutMapping("/{id}")
    User updateUser(@PathVariable("id") Long id, @RequestBody User user);

    // DELETE 请求
    @DeleteMapping("/{id}")
    void deleteUser(@PathVariable("id") Long id);

    // 带请求头
    @GetMapping("/{id}")
    User getUserWithToken(@PathVariable("id") Long id,
                         @RequestHeader("Authorization") String token);
}

7. Feign 使用示例

@Service
public class OrderService {

    @Autowired
    private UserServiceClient userServiceClient;

    @Autowired
    private ProductServiceClient productServiceClient;

    public OrderDTO createOrder(OrderRequest request) {
        // 调用用户服务
        User user = userServiceClient.getUserById(request.getUserId());
        if (user == null) {
            throw new BusinessException("用户不存在");
        }

        // 调用产品服务
        Product product = productServiceClient.getProductById(request.getProductId());
        if (product == null) {
            throw new BusinessException("产品不存在");
        }

        // 检查库存
        if (product.getStock() < request.getQuantity()) {
            throw new BusinessException("库存不足");
        }

        // 创建订单
        Order order = new Order();
        order.setUserId(user.getId());
        order.setProductId(product.getId());
        order.setQuantity(request.getQuantity());
        order.setTotalPrice(product.getPrice() * request.getQuantity());

        // 保存订单并返回
        return orderRepository.save(order);
    }
}

8. Feign 拦截器

// 自定义 Feign 拦截器
@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        // 添加请求头
        template.header("X-Request-Id", UUID.randomUUID().toString());

        // 从上下文获取 Token
        ServletRequestAttributes attributes =
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("Authorization");
            if (token != null) {
                template.header("Authorization", token);
            }
        }
    }
}

// 配置拦截器
@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return new FeignRequestInterceptor();
    }

    // 日志级别
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

WebClient 响应式调用

WebClient 是 Spring 5 引入的响应式 HTTP 客户端,支持异步非阻塞调用。

9. WebClient 配置

// pom.xml 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient webClient() {
        return WebClient.builder()
            .baseUrl("http://localhost:8080")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .defaultHeader("User-Agent", "Spring WebClient")
            .build();
    }

    // 带负载均衡的 WebClient
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

10. WebClient 使用示例

@Service
public class ReactiveUserService {

    @Autowired
    private WebClient.Builder webClientBuilder;

    // GET 请求 - 返回 Mono
    public Mono<User> getUserById(Long id) {
        return webClientBuilder.build()
            .get()
            .uri("http://user-service/api/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
    }

    // GET 请求 - 返回 Flux
    public Flux<User> getAllUsers() {
        return webClientBuilder.build()
            .get()
            .uri("http://user-service/api/users")
            .retrieve()
            .bodyToFlux(User.class);
    }

    // POST 请求
    public Mono<User> createUser(User user) {
        return webClientBuilder.build()
            .post()
            .uri("http://user-service/api/users")
            .bodyValue(user)
            .retrieve()
            .bodyToMono(User.class);
    }

    // 带错误处理
    public Mono<User> getUserWithErrorHandling(Long id) {
        return webClientBuilder.build()
            .get()
            .uri("http://user-service/api/users/{id}", id)
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError,
                response -> Mono.error(new BusinessException("客户端错误")))
            .onStatus(HttpStatus::is5xxServerError,
                response -> Mono.error(new BusinessException("服务器错误")))
            .bodyToMono(User.class)
            .timeout(Duration.ofSeconds(5))
            .retry(3);
    }

    // 并行调用多个服务
    public Mono<OrderDTO> getOrderDetails(Long orderId) {
        Mono<Order> orderMono = getOrder(orderId);
        Mono<User> userMono = orderMono.flatMap(order -> getUserById(order.getUserId()));
        Mono<Product> productMono = orderMono.flatMap(order -> getProductById(order.getProductId()));

        return Mono.zip(orderMono, userMono, productMono)
            .map(tuple -> {
                OrderDTO dto = new OrderDTO();
                dto.setOrder(tuple.getT1());
                dto.setUser(tuple.getT2());
                dto.setProduct(tuple.getT3());
                return dto;
            });
    }
}
最佳实践:

实践练习

练习任务:
  1. RestTemplate 调用:使用 RestTemplate 实现用户服务的 CRUD 操作
  2. Feign 客户端:定义 Feign 接口,实现订单服务调用用户和产品服务
  3. 拦截器实现:编写 Feign 拦截器,自动添加认证 Token
  4. WebClient 异步调用:使用 WebClient 并行调用多个服务,提升性能
  5. 超时和重试:配置合理的超时时间和重试策略
  6. 异常处理:统一处理服务调用异常,实现优雅降级
  7. 性能对比:对比 RestTemplate、Feign、WebClient 的性能差异
  8. 链路追踪:集成 Sleuth,实现服务调用链路追踪

常见问题

Q: RestTemplate、Feign、WebClient 如何选择?

A: RestTemplate 适合简单场景,Feign 适合复杂的服务调用(推荐),WebClient 适合高并发响应式场景。

Q: 如何处理服务调用超时?

A: 设置合理的超时时间,实现重试机制,结合熔断降级保护系统。超时时间应根据业务场景设置,一般 3-10 秒。

总结

服务调用是微服务通信的核心。通过本课学习,你应该掌握: