1. 概述

在现代前后端分离的架构中,前端应用和后端 REST API 通常独立部署。这种部署方式会遇到浏览器的 CORS(跨域资源共享)同源策略(Same Origin Policy) 的限制,导致前端无法直接调用后端接口。

为了解决这个问题,我们可以通过 Zuul 代理 来实现请求转发。Zuul 是 Netflix 开源的 JVM 路由器和服务器端负载均衡器,Spring Cloud 对其做了很好的集成。

本文将创建两个独立应用:

  • 一个简单的 UI 应用(运行在 8080 端口)
  • 一个 REST API 服务(运行在 8081 端口)

并通过在 UI 应用中集成 Zuul 代理,实现对 REST API 的安全访问。

2. Maven 依赖配置

首先,在 UI 应用的 pom.xml 中添加 Zuul 的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

✅ 最新版本可以在这里查看:Maven Repository

3. Zuul 配置

在 Spring Boot 项目中,我们通过 application.yml 配置 Zuul 路由规则:

zuul:
  routes:
    foos:
      path: /foos/**
      url: http://localhost:8081/spring-zuul-foos-resource/foos

说明:

  • 所有以 /foos/ 开头的请求都会被转发到 http://localhost:8081/spring-zuul-foos-resource/foos
  • 这样,前端就可以通过相对路径访问后端资源,而无需关心跨域问题

4. 后端 REST API

API 应用是一个标准的 Spring Boot 工程,部署在 8081 端口。

DTO 定义:

public class Foo {
    private long id;
    private String name;

    // standard getters and setters
}

控制器:

@RestController
public class FooController {

    @GetMapping("/foos/{id}")
    public Foo findById(
      @PathVariable long id, HttpServletRequest req, HttpServletResponse res) {
        return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }
}

5. 前端 UI 应用

UI 应用也是一个 Spring Boot 工程,运行在 8080 端口。

前端页面(index.html)使用 AngularJS:

<html>
<body ng-app="myApp" ng-controller="mainCtrl">
<script src="angular.min.js"></script>
<script src="angular-resource.min.js"></script>

<script>
var app = angular.module('myApp', ["ngResource"]);

app.controller('mainCtrl', function($scope,$resource,$http) {
    $scope.foo = {id:0 , name:"sample foo"};
    $scope.foos = $resource("/foos/:fooId",{fooId:'@id'});
    
    $scope.getFoo = function(){
        $scope.foo = $scope.foos.get({fooId:$scope.foo.id});
    }  
});
</script>

<div>
    <h1>Foo Details</h1>
    <span>{{foo.id}}</span>
    <span>{{foo.name}}</span>
    <a href="#" ng-click="getFoo()">New Foo</a>
</div>
</body>
</html>

⚠️ 注意:前端使用的是相对路径 /foos/,这在正常情况下是跨域的,但通过 Zuul 代理后就能正常访问。

主启动类:

@EnableZuulProxy
@SpringBootApplication
public class UiApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(UiApplication.class, args);
    }
}
  • @EnableZuulProxy 是关键注解,启用 Zuul 代理功能

6. 测试路由功能

使用 RestAssured 测试请求是否被正确代理:

@Test
public void whenSendRequestToFooResource_thenOK() {
    Response response = RestAssured.get("http://localhost:8080/foos/1");
 
    assertEquals(200, response.getStatusCode());
}

✅ 通过测试说明 Zuul 代理已正常工作。

7. 自定义 Zuul Filter

Zuul 提供了强大的过滤器机制,我们可以自定义 Filter 来增强请求处理逻辑。

示例:添加请求头

@Component
public class CustomZuulFilter extends ZuulFilter {

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader("Test", "TestSample");
        return null;
    }

    @Override
    public boolean shouldFilter() {
       return true;
    }
    // ...
}

这个 Filter 会在请求转发前添加一个名为 Test 的请求头。

8. 测试自定义 Filter

修改后端 API 控制器,读取并返回该请求头:

@RestController
public class FooController {

    @GetMapping("/foos/{id}")
    public Foo findById(
      @PathVariable long id, HttpServletRequest req, HttpServletResponse res) {
        if (req.getHeader("Test") != null) {
            res.addHeader("Test", req.getHeader("Test"));
        }
        return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }
}

编写测试验证:

@Test
public void whenSendRequest_thenHeaderAdded() {
    Response response = RestAssured.get("http://localhost:8080/foos/1");
 
    assertEquals(200, response.getStatusCode());
    assertEquals("TestSample", response.getHeader("Test"));
}

✅ 测试通过,说明自定义 Filter 成功生效。

9. 总结

本文展示了如何通过 Zuul 代理解决前后端分离部署中的跨域问题,同时演示了如何通过自定义 Filter 增强请求处理逻辑。

核心要点:

  • Zuul 代理可以透明地处理跨域请求
  • 使用 @EnableZuulProxy 快速集成
  • 可以通过 Filter 自定义请求头、日志、认证等逻辑

📦 完整代码见 GitHub 项目:spring-cloud-zuul 示例


原始标题:Spring REST with a Zuul Proxy

« 上一篇: Java周报,109期