博客同步至GitPress:https://gitpress.io/@yangshijie/spring_cloud_zuul
示例代码已上传Github: https://github.com/yshijie/spring_cloud_demo
Zuul组件简介
Zuul组件主要是用来做动态路由转发和请求拦截过滤等;
在客户端访问服务端时,经常会有一些通过验证用户的token或者请求头之类的去判断并且指定具体访问哪些数据服务,例如不同组织机构的用户通过请求的token的不同被服务端指定分发到不同的数据中心去查询对应的数据;
以及在客户端请求服务端时,服务端拦截客户端请求,可以对请求做一些预处理等操作;
在SpringCloud工程中,创建了两个数据服务datacenter-a和datacenter-b,以及一个注册服务,一个分发服务,这个分发服务就是用来指定路由去访问具体哪个数据中心的数据;
核心类
Zuul组件的核心类是ZuulFilter,通过自定义过滤类继承该类,并重写指定方法进行请求的拦截处理;
public class MyFilter extends ZuulFilter {
Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
return null;
}
}
filterType方法:指定在什么地方进行拦截,可以使是请求前/后,发生错误时等;
pre:在路由转发之前作用;
routing:在路由时起作用;
post:在返回结果时作用;
error:在整个路由阶段,出现异常时作用;
filterOder方法:拦截的优先级,0表示最大;
shouldFliter方法:是否拦截,可以通过一些逻辑判断是否需要进行拦截;
run方法:执行具体的拦截操作;
添加依赖
在需要进行动态路由转发的服务(即分发服务)的pom.xml文件中加入依赖:
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-eureka
动态路由转发
通过请求地址不同将服务指向不同的子服务,例如,服务集群中,A服务用于存储用户信息,B服务用于存储相关的商品信息,在请求是通过路径设定user/goods将查询指向不同的服务;
配置转发信息
启动类注解
在需要进行动态路由转发的服务(即分发服务)的启动类上配置@EnableZuulProxy注解:
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class DispatchServerApplication {
public static void main(String[] args) {
SpringApplication.run(DispatchServerApplication.class, args);
}
}
application.yml文件配置
在分发服务的application.yml文件中配置需要转发的逻辑和转发的对应服务地址:
spring:
application:
name: dispatch-server
server:
port: 8760
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
retryable: false
routes:
data-a:
path: /user/**
serviceId: data-server-a
data-b:
path: /goods/**
serviceId: data-server-b
请求测试
通过网关访问:
http://localhost:8760/alarm/aaa/getData
http://localhost:8760/goods/aaa/getData
此时根据上面yml中的配置,第一个请求将自动转发到data-server-a这个数据服务,第二个请求将自动转发到data-server-b这个数据服务;
请求拦截过滤
4中的方法是一个比较简单直白的做法,也可以对请求进行拦截判断,通过请求的不同参数、或者请求头信息等,将客户端请求指向不同的服务;
yml配置路由信息
zuul:
routes:
data-a:
path: /**
serviceId: service-datacenter-a
data-b:
path:/**
serviceId:service-datacenter-b
过滤请求
@Value("${zuul.routes.data-a.serviceId}")
private String aa;
@Value("${zuul.routes.data-b.serviceId}")
private String bb;
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();
String userType = request.getParameter("userType");
String serviceId = null;
try {
if (userType != null) {
if (userType.equals("A")) {
serviceId = aa;
} else if (userType.equals("B")) {
serviceId = bb;
}
} else {
serviceId = "xxx";
}
} catch (Exception e) {
e.printStackTrace();
}
if (serviceId == null) {
//服务查询失败 不再路由直接返回
response.setHeader("Content-Type", "application/json;charset=UTF-8");
ctx.setSendZuulResponse(false);
ctx.setResponseBody("Error: No service");
} else {
//转发请求
log.info("--->>> Forward to service :" + serviceId);
ctx.set("serviceId", serviceId);
}
return null;
}
在该类上添加@Configuration注解表明是个配置类;
以上做法中,访问的地址为同一个,但是根据传入的参数不同,利用Zuul拦截请求,将请求重新定义定指定转发到不同的子服务中;
请求测试
两个子服务分别访问地址为:
A:http://localhost:8763/aaa/getData?userType=""
B:http://localhost:8762/aaa/getData?userType=""
通过网关访问时,请求http://localhost:8760/aaa/getData?userType="",当参数传入A时,请求转发到A服务,当传输传入B时,转发到B服务;
这样对用户来说是无感觉的,可以将判断标准定为请求的token等来作为转发的判断条件等;