spring-cloud-Zuul学习(四)【中级】--自定义zuul Filter详解【重新定义spring cloud实践】
- 实现自定义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实践】的更多相关文章
- go微服务框架go-micro深度学习(四) rpc方法调用过程详解
上一篇帖子go微服务框架go-micro深度学习(三) Registry服务的注册和发现详细解释了go-micro是如何做服务注册和发现在,服务端注册server信息,client获取server的地 ...
- Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档
0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ...
- Spring事务Transaction配置的五种注入方式详解
Spring事务Transaction配置的五种注入方式详解 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学 ...
- IP地址和子网划分学习笔记之《IP地址详解》
2018-05-03 18:47:37 在学习IP地址和子网划分前,必须对进制计数有一定了解,尤其是二进制和十进制之间的相互转换,对于我们掌握IP地址和子网的划分非常有帮助,可参看如下目录详文. ...
- “全栈2019”Java第九十四章:局部内部类详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Spring中的注入方式 和使用的注解 详解
注解:http://www.cnblogs.com/liangxiaofeng/p/6390868.html 注入方式:http://www.cnblogs.com/java-class/p/4727 ...
- Docker学习(六)——Dockerfile文件详解
Docker学习(六)--Dockerfile文件详解 一.环境介绍 1.Dockerfile中所用的所有文件一定要和Dockerfile文件在同一级父目录下,可以为Dockerfile父目录的子目录 ...
- BaseAdapter自定义适配器——思路详解
BaseAdapter自定义适配器——思路详解 引言: Adapter用来把数据绑定到扩展了AdapterView类的视图组.系统自带了几个原生的Adapter. 由于原生的Adapter视图功能太少 ...
- ASP.NET MVC 5 学习教程:生成的代码详解
原文 ASP.NET MVC 5 学习教程:生成的代码详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 ...
- Spring Security Filter详解
Spring Security Filter详解 汇总 Filter 作用 DelegatingFilterProxy Spring Security基于这个Filter建立拦截机制 Abstract ...
随机推荐
- ubuntu 下开机启动项修复(进不去windows系统)
1.终端输入: sudo gedit /etc/default/grub 2.更改: GRUB_DEFAULT=0 改为 GRUB_DEFAULT=4 GRUB_TIMEOUT=10 改为 ...
- kali下的webshell工具-Weevely
Weevely ------------------------------------------------ 主要特点: · 隐蔽的类终端的PHP webshell · ...
- windows环境下curl 安装和使用
原文:https://blog.csdn.net/qq_21126979/article/details/78690960?locationNum=10&fps=1 一.curl 安装 cur ...
- 关于VC预定义常量_WIN32,WIN32,_WIN64等预定义宏的介绍(整理、转载)
参考帖子: (1)MSDN上专门讲预定义宏:https://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx (2)VS中属性页的配置介绍 ...
- proxy ubunta
/etc/environment : Is the correct place to specify system-wide environment variables that should be ...
- Linux搭建NodeJs环境
文件下载与解压 文件下载 wget https://npm.taobao.org/mirrors/node/v6.10.3/node-v6.10.3-linux-x64.tar.xz 如果要下载最新版 ...
- SQL语句完整的执行顺序(01)
一.sql语句的执行步骤: 1)语法分析,分析语句的语法是否符合规范,衡量语句中各表达式的意义. 2) 语义分析,检查语句中涉及的所有数据库对象是否存在,且用户有相应的权限. 3)视图转换,将涉 ...
- emacs Can't guess python-indent-offset, using defaults: 4
首先,这只是一个提示,Emacs 在打开python 文件时,如果是个空文件,会有此提示. 在python.el文件配置中,有如下代码: Python.el (defcustom python-ind ...
- JVM内存结构--新生代及新生代里的两个Survivor区(下一轮S0与S1交换角色,如此循环往复)、常见调优参数
一.为什么会有年轻代 我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我 ...
- linux条件判断:eq、ne、gt、lt、ge、le
-eq(equal) :判断是否相等,相等为真 -ne(inequality):判断是否不等,不等为真 -gt(greter than):判断是否大于,大于为真 -lt(less than):判断是否 ...