• 实现自定义zuul Filter

方法很简单,只要继承ZuulFilter跟加入到spring IOC容器即可,zuulFilter是一个抽象类,里面包含以下方法需要我们实现:

String filterType():使用返回值设置Filter类型,可设置pre、route、post、error
int filterOrder():使用返回值设置filter执行次序
boolean shouldFilter():使用返回值设值改Filter是否执行
Object run() throws ZuulException:核心执行逻辑

说起这个,为了方便把上一节说的四种Filter类型粘上:
  •  Zuul总共有四种不同的生命周期类型的Filter:

pre:  在路由下级服务之前执行;比如鉴权、限流都是需要在此类Filter执行。

route:这类Filter是Zuul路由动作的执行者,是Apache HTTPClient或Netflix Ribbon构建和发送原始Http请求的地方,目前已支持OKHTTP。

post:这类Filter是源服务返回结果或发生异常信息发生后执行的;需要对返回信息做一些处理,可以在此类Filter处理。

error:在整个生命周期内如果发生异常,则会进入error Filter,可做全局异常处理。

  前面有说过了关于zuul-server的搭建,这里不说了,以表形式简单说明下:

项目名 端口 说明
eureka-server 8761 eureka注册中心
zuul-filter-server 6666 zuul服务端,里面将/client/**的请求路由到client-a去
client-a 7070 一个基本的接口提供者,有一个/student//getStudent接口

  以下为自定义pre类型的Filter测试类:

 /**
* 第一个zuul-Filter
* pre类型:路由到下游服务之前执行,可做限流、鉴权等这些
* @author libo
* @date 2019/4/28 17:47
*/
8 @Component
@Slf4j
public class FirstPreZuulFilter extends ZuulFilter { /**
*
* @return 使用返回值设置Filter类型,可设置pre、route、post、error
*/
@Override
public String filterType() {
return PRE_TYPE;
} /**
*
* @return 使用返回值设置filter执行次序
*/
@Override
public int filterOrder() {
return 0;
} /**
*
* @return 使用返回值设值改Filter是否执行
*/
@Override
public boolean shouldFilter() {
return true;
} /**
*
* @return 核心执行逻辑
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
log.info("========================这是第一个自定义的Zuul-filter");
return null;
}
}

补充说明:可以静态导入FilterConstants类,里面提供了很多常量信息。(import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;)

我个人喜欢使用postman测试接口,访问链接:localhost:6666/client//student//getStudent,可以看到控制台日志:

  • 多层Filter处理案例:

上面是一个最简单的Filter,实际开发中经常会根据需求,然后自定义实现以上类型的FIlter。在Filter之中,通过com.netflix.zuul.context。RequestContext类进行通讯,内部采用ThreadLocal保存每个请求的一些信息,包括请求路由、错误信息、HttpServletRequest、HTTPServletResponse,它还扩展了ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息。

模拟业务需求:先使用NamePreZuulFilter判断是否传入了name,接着使用AgePreZuulFilter判断是否传入age,最后在PostZuulFilter里统一处理返回内容。

先以表格方式展示有几个服务,服务介绍:

项目 端口 说明
eureka-server 8761 本地eureka注册中心
client-a 7070 接口提供者,提供一个接收name跟age参数的接口,/student//addStudent
zuul-filter-server 6666 zuul服务端,里面存在两个pre:NamePreZuulFilter(1)、AgePreZuulFilter(2),一个post:PostZuulFilter(3)

整个项目图:

zuul-filter-server:

依赖说明:三个子项目父级zuul-filter-server的pom.xml这里我只引入了eureka客户端依赖、gson工具依赖,eureka-server那里就一个eureka服务依赖(spring-boot那些我在zuul-Filter的父级spring-cloud-Zuul那里引入)。

zuul-filter-server的pom.xml:

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-Zuul</artifactId>
<groupId>lb.study</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>1.0-SNAPSHOT</version> <artifactId>zuul-Filter</artifactId> <dependencies>
<!-- eureka客户端依赖开始 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- eureka客户端依赖结束 --> <!-- ribbon依赖开始 -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>-->
<!-- ribbon依赖结束 --> <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies> <modules>
<module>zuul-filter-server</module>
<module>eureka-server</module>
<module>client-a</module>
</modules>
</project>

zuul-filter-server的pom:

    <dependencies>
<!-- zuul服务导包开始 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- zuul服务导包结束 -->
</dependencies>

zuul-filter-server启动类ZuulFilterServerApplication:

 package lb.study.zuul.zuulfilterserver;

 import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication
@EnableZuulProxy //@EnableZuulProxy区别@EnableZuulServer:@EnableZuulServer缺少几个zuul内置的Filter,不举出来了
@EnableDiscoveryClient
public class ZuulFilterServerApplication { public static void main(String[] args) {
SpringApplication.run(ZuulFilterServerApplication.class, args);
} }

zuul-filter-server配置文件:

spring:
application:
name: zuul-filter-server
server:
port: 6666 zuul:
routes:
client-a: /client/**
NamePreZuulFilter用来验证name是否存在,(int filterOrder())顺序为1。如果没传则使用setSendZuulResponse(false)禁止route Filter路由到源服务client-a,使用setResponseBody("信息")定制返回结果(在postZuulFilter有定制这个返回结果)。通过在上下文中设置参数
requestContext.set("logic-is-success",false)来作为下个Filter是否执行的标识:
 /**
* 用来判断name是否存在的Filter
* @author libo
* @date 2019/4/29 11:38
*/
@Component
@Slf4j
public class NamePreZuulFilter extends ZuulFilter { private static Gson gson = new Gson();
private Map params = new LinkedHashMap(); @Override
public String filterType() {
return PRE_TYPE;
} @Override
public int filterOrder() {
return 1;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() throws ZuulException {
log.info("进入NamePreZuulFilter-----------");
//取得Request对象并取name参数
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
System.out.println("NamePreZuulFilter--request-----------"+request);//这里的request跟源服务那里的request是不同的,所以底层是一个新的请求
String name = request.getParameter("name");
if(name == null){//如果不存在name
//则禁止路由下级服务,但还是会被下个Filter拦截哦,别理解错了
requestContext.setSendZuulResponse(false);
//设置返回body,也作为下级判断
params.put("msg","姓名不能为空");
params.put("lastDate",new Date());
requestContext.setResponseBody(gson.toJson(params));
//logic-is-success执行标识保存在上下文中,可作为同类型下游Filter的开关
requestContext.set("logic-is-success",false);
//结束
return null;
}
//成功,设置logic-is-success执行标识
requestContext.set("logic-is-success",true);
return null;
}
}
AgePreZuulFilter执行顺序为2,用来验证age是否传入,也根据NamePreZuulFilter返回的logic-is-success标识是否执行此Filter。其他的跟前面一样,定制信息、设置是否路由源服务:
 /**
* 接着判断age是否传过来
* @author libo
* @date 2019/4/29 13:58
*/
@Component
@Slf4j
public class AgePreZuulFilter extends ZuulFilter {
private static Gson gson = new Gson();
private Map params = new LinkedHashMap(); @Override
public String filterType() {
return PRE_TYPE;
} @Override
public int filterOrder() {
return 2;
} @Override
public boolean shouldFilter() {
//这里从上下文中取执行标识,并设置是否执行此Filter
RequestContext requestContext = RequestContext.getCurrentContext();
boolean bool = (boolean)requestContext.get("logic-is-success");
return bool;
} @Override
public Object run() throws ZuulException {
log.info("进入AgePreZuulFilter-----------");
//取得Request对象并取name参数
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String age = request.getParameter("age");
if(age==null){
//则禁止路由下级服务,但还是会被下个Filter拦截哦,别理解错了
requestContext.setSendZuulResponse(false);
//设置返回body,也作为下级判断
params.put("msg","年龄不能为空");
params.put("lastDate",new Date());
requestContext.setResponseBody(gson.toJson(params));
//logic-is-success执行标识保存在上下文中,可作为同类型下游Filter的开关
requestContext.set("logic-is-success",false);
//结束
return null;
}
//前面优先的FIlter设置默认logic-is-success=true了 这里就不设置了
return null;
}
}
PostZuulFilter是post类型FIlter,在源服务返回结果后执行,我这里设置顺序为3。在这里检查有无定制的ResponseBody,以及设置字符集,此外还设置了HTTP响应码:
 /**
* 源服务返回结果类型
* @author libo
* @date 2019/4/29 14:19
*/
@Component
@Slf4j
public class PostZuulFilter extends ZuulFilter { @Override
public String filterType() {
return POST_TYPE;
} @Override
public int filterOrder() {
return 3;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() throws ZuulException {
log.info("进入PostZuulFilter-----------");
//上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//取response对象
HttpServletResponse response = requestContext.getResponse();
//响应字符类型
response.setCharacterEncoding("utf-8");
//获取上下文中保存的responseBody
String responseBody = requestContext.getResponseBody();
if(responseBody != null){//如果不为空,则说明存在异常流程(上面只有在异常的时候设置这个)
//设置响应信息
requestContext.setResponseBody(responseBody);
requestContext.setResponseStatusCode(500);
}
return null;
}
}
  • 测试

使用postman访问localhost:6666/client//student//addStudent:

  • 测试案例一:name跟age都为空:

状态码500(在postZuulFilter那里设置的HTTP响应状态码)

可以看到跳过了AgeZuulFilter直接进入PostZuulFilter。

  • 测试案例二:name不为空,age为空:

  • 测试案例三:name不为空,age不为空:

requestContext通讯分析:在使用requestContext进行通讯的时候,其实我想过:这个通讯不就是在上下文中存东西,然后用来通讯的吗?那我是不是可以直接用ServletRequest进行通讯?这个其实也是可以的,因为在各层Filter中那是同一个request,路由到源服务然后再回来也是同一个,毕竟没有去改存于ConcurrentHashMap内的request对象。这个可以通过RequestContext源码看清,它有继承ConcurrentHashMap,将一些request、response这些东西都存那里。

     /**
* sets a key value to Boolen.TRUE
*
* @param key
*/
public void set(String key) {
put(key, Boolean.TRUE);
} /**
* puts the key, value into the map. a null value will remove the key from the map
*
* @param key
* @param value
*/
public void set(String key, Object value) {
if (value != null) put(key, value);
else remove(key);
}
     /**
* @return the HttpServletRequest from the "request" key
*/
public HttpServletRequest getRequest() {
return (HttpServletRequest) get("request");
} /**
* sets the HttpServletRequest into the "request" key
*
* @param request
*/
public void setRequest(HttpServletRequest request) {
put("request", request);
} /**
* @return the HttpServletResponse from the "response" key
*/
public HttpServletResponse getResponse() {
return (HttpServletResponse) get("response");
} /**
* sets the "response" key to the HttpServletResponse passed in
*
* @param response
*/
public void setResponse(HttpServletResponse response) {
set("response", response);
}

到此一个多层Filter业务处理完成。RequestContext中还有很多的API,比如通过InputStream responseDataStream = requestContext.getResponseDataStream();获取源服务返回的数据流,再做些统一处理。

本人所有的源码都存在github.com上面

spring-cloud-Zuul学习(四)【中级】--自定义zuul Filter详解【重新定义spring cloud实践】的更多相关文章

  1. go微服务框架go-micro深度学习(四) rpc方法调用过程详解

    上一篇帖子go微服务框架go-micro深度学习(三) Registry服务的注册和发现详细解释了go-micro是如何做服务注册和发现在,服务端注册server信息,client获取server的地 ...

  2. Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档

    0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ...

  3. Spring事务Transaction配置的五种注入方式详解

    Spring事务Transaction配置的五种注入方式详解 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学 ...

  4. IP地址和子网划分学习笔记之《IP地址详解》

    2018-05-03 18:47:37   在学习IP地址和子网划分前,必须对进制计数有一定了解,尤其是二进制和十进制之间的相互转换,对于我们掌握IP地址和子网的划分非常有帮助,可参看如下目录详文. ...

  5. “全栈2019”Java第九十四章:局部内部类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  6. Spring中的注入方式 和使用的注解 详解

    注解:http://www.cnblogs.com/liangxiaofeng/p/6390868.html 注入方式:http://www.cnblogs.com/java-class/p/4727 ...

  7. Docker学习(六)——Dockerfile文件详解

    Docker学习(六)--Dockerfile文件详解 一.环境介绍 1.Dockerfile中所用的所有文件一定要和Dockerfile文件在同一级父目录下,可以为Dockerfile父目录的子目录 ...

  8. BaseAdapter自定义适配器——思路详解

    BaseAdapter自定义适配器——思路详解 引言: Adapter用来把数据绑定到扩展了AdapterView类的视图组.系统自带了几个原生的Adapter. 由于原生的Adapter视图功能太少 ...

  9. ASP.NET MVC 5 学习教程:生成的代码详解

    原文 ASP.NET MVC 5 学习教程:生成的代码详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 ...

  10. Spring Security Filter详解

    Spring Security Filter详解 汇总 Filter 作用 DelegatingFilterProxy Spring Security基于这个Filter建立拦截机制 Abstract ...

随机推荐

  1. vue 生命周期详解

  2. 1、Altium Designer 入门

    一.新建工程 File-->new-->Project-->newPCB Project 1.添加原理图 在Project面板选中项目,右键Add New to Project--& ...

  3. Babel学习小记

    一.babel配置文件中的plugins和presets是什么? 1.首先说说babel是什么,babel是一个JavaScript转码器,帮助我们把浏览器不兼容的ES6语法转换成ES5语法: 2.接 ...

  4. tp5.0 queue 队列操作

    检查是否安装redis(没有请自行百度安装): phpinfo: 配置thinkphp-queue,没有请执行 composer require topthink/think-queue 加入: 创建 ...

  5. zynq DMA控制器

    Zynq-7000系列器件PS端的DMA控制器采用ARM的IP核DMA-330(PL-330)实现. 特点: 1.8个独立的通道,4个可用于PL—PS间数据管理,每个通道有1024Byte的MFIFO ...

  6. C# EntityFramework Code First 迁移

    如果使用的是 Code First 工作流,推荐使用 Code First 迁移改进应用程序的数据库架构. 迁移提供一组允许以下操作的工具: 创建可用于 EF 模型的初始数据库 生成迁移以跟踪对 EF ...

  7. VS Code 1.28版本设置中文界面的方法

    最近将vscode升级到1.28版本,发现升级后默认界面变成英文了,而且在按照网上的说法在locale.json设置locale: "zh-cn"也不起效,解决的解决方法很简单: ...

  8. css属性之统一设置文本及div之间的对齐方式

    设为 Flex 布局以后,子元素的float.clear和vertical-align属性将失效.hdp-uf{ display: -webkit-box; /* 老版本语法: Safari, iOS ...

  9. 爬虫-request和BeautifulSoup模块

    requests简介 Python标准库中提供了:urllib.urllib2.httplib等模块以供Http请求,但是,它的 API 太渣了.它是为另一个时代.另一个互联网所创建的.它需要巨量的工 ...

  10. PHP使用urlencode对中文编码时空格、加号的问题

    使用urlencode这个函数进行格式化,urlencode函数会把空格编码为为:+ 当然,前端在接收时可以解码后进行替换 + 为空格的方式处理. 但是这样就多做了一步,很麻烦,有的时候我们的数据接口 ...