首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud Zuul实现多级自定义Filter

Spring Cloud Zuul实现多级自定义Filter

作者头像
胖虎
发布2019-06-26 16:25:15
8680
发布2019-06-26 16:25:15
举报
文章被收录于专栏:晏霖晏霖

前言

声明:此文Demo摘自《重新定义》已得作者许可。

这篇博客不会去介绍关于zuul的基础知识以及配置详解,我会以代码的形式一步一步带领大家实现,利用多种或一种不同类型的过滤器进行处理相应的业务,以便让大家了解各个过滤器什么时候用,用来干什么,解决大家实际工作中可能碰到的问题。

正文

本篇博客会涉及到三个工程依次是一个客户端client-a、一个eureka服务(和前文的案例没有区别)、一个zuul-server。

这三个工程都依赖一个父级pom,和前文介绍的一样。为了让大家看到我使用的版本,我再一次把pom依赖贴出来。

<modules>
		<module>ch8-1-eureka-server</module>
		<module>ch8-1-zuul-server</module>
		<module>ch8-1-client-a</module>
	</modules>
 
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
	</parent>
	
	<!--  利用传递依赖,公共部分  --> 
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<!-- springboot web -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
 
	<!-- 管理依赖 -->
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Finchley.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
 
	<!--注意: 这里必须要添加,否则各种依赖有问题 -->
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
 
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

第一步

还是需要一个eureka服务端,在以后的案例里我不会再贴出来eureka服务端的配置以及启动类了。由于这次笔者加入了日志配置文件,所以在这三个工程的resources下都会有一个logback.log文件,由于springboot默认使用的日志就是logback,所以我们可以直接使用,如果你想了解springboot使用log4j,那么请自行百度,本文不介绍。下面就是日志配置文件,由于三个工程都有共同的文件,所以我只贴一次,唯一不同的就是生成日志文件的前缀需要大家更改

<property name="LOG_PREFIX" value="eureka-server" /> ,这里的value可以随意写,只要能区分出不同的工程就可以。

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->
<configuration scan="true" scanPeriod="10 seconds">
	<!--<include resource="org/springframework/boot/logging/logback/base.xml" 
		/> -->
 
	<!--定义日志文件的存储地址和前缀名 -->
	<property name="LOG_HOME" value="logs" />
	<property name="LOG_PREFIX" value="eureka-server" />
 
	<!-- 一般信息按照每天生成日志文件 -->
	<appender name="INFO_FILE"
		class="ch.qos.logback.core.rolling.RollingFileAppender">
		<File>${LOG_HOME}/${LOG_PREFIX}-info.log</File>
		<rollingPolicy
			class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
			<!-- 每天一归档 -->
			<fileNamePattern>${LOG_HOME}/${LOG_PREFIX}-info-%d{yyyyMMdd}.log.%i
			</fileNamePattern>
			<!-- 单个日志文件最多500MB, 30天的日志周期,最大不能超过20GB -->
			<maxFileSize>100MB</maxFileSize>
			<maxHistory>30</maxHistory>
			<totalSizeCap>20GB</totalSizeCap>
		</rollingPolicy>
		<encoder>
			<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
			<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}
				-%msg%n</Pattern>
		</encoder>
	</appender>
 
	<!--错误信息按照每天生成日志文件 -->
	<appender name="ERROR_FILE"
		class="ch.qos.logback.core.rolling.RollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>ERROR</level>
		</filter>
		<File>${LOG_HOME}/${LOG_PREFIX}-error.log</File>
		<rollingPolicy
			class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
			<!-- 每天一归档 -->
			<fileNamePattern>${LOG_HOME}/${LOG_PREFIX}-error-%d{yyyyMMdd}.log.%i
			</fileNamePattern>
			<!-- 单个日志文件最多500MB, 30天的日志周期,最大不能超过20GB -->
			<maxFileSize>100MB</maxFileSize>
			<maxHistory>30</maxHistory>
			<totalSizeCap>20GB</totalSizeCap>
		</rollingPolicy>
		<encoder>
			<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
			<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}
				-%msg%n</Pattern>
		</encoder>
	</appender>
 
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
				%msg%n</pattern>
		</encoder>
	</appender>
 
	<!-- 日志输出级别 这样设置不打印日志 -->
	<root level="INFO">
		<appender-ref ref="STDOUT" />
		<appender-ref ref="INFO_FILE" />
		<appender-ref ref="ERROR_FILE" />
	</root>
 
</configuration>

第二步

创建client-a工程,一个eureka客户端,代码很简单,下面依次贴出启动类、controller、yml、pom文件

@SpringBootApplication
@EnableDiscoveryClient
public class ClientAApplication {
	
    public static void main(String[] args) {
        SpringApplication.run(ClientAApplication.class, args);
    }
}
@RestController
public class TestController {
 
	@GetMapping("/add")
	public Integer add(Integer a, Integer b){
		return a + b;
	}
	
	@GetMapping("/a/add")
	public Integer aadd(Integer a, Integer b){
		return a + b;
	}
	
	@GetMapping("/sub")
	public Integer sub(Integer a, Integer b){
		return a - b;
	}
	
	@GetMapping("/mul")
	public String mul(Integer a, Integer b){
		System.out.println("进入client-a!");
		return "client-a-" + a * b;
	}
	
	@GetMapping("/div")
	public Integer div(Integer a, Integer b){
		return a / b;
	}
}
server:
  port: 7070
spring:
  application:
    name: client-a
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/
  instance:
    prefer-ip-address: true
<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
	</dependencies>
<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

第三步

大家对eureka应用过的话,我上面所有代码都不用看。

下面才是重点,创建一个zuul-server服务。我们都知道,在zuul过滤器里PRE_TYPE类型是在路由前执行的,所以我要给大家演示配置三个PRE_TYPE类型的过滤器,按照顺序依次处理不同的业务。以及,三个PRE_TYPE类型过滤器中任意一个出现异常时他的下游业务应该怎么处理。

首先给大家一个项目的目录,

我会分别贴出各个类的代码,这里作者已经把每行注释写的足够详细,我也没什么好介绍的了。

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulServerApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
    
    @Bean
    public FirstPreFilter firstPreFilter(){
    	return new FirstPreFilter();
    }
    
    @Bean
    public SecondPreFilter secondPreFilter(){
    	return new SecondPreFilter();
    }
    
    @Bean
    public ThirdPreFilter thirdPreFilter(){
    	return new ThirdPreFilter();
    }
    
    @Bean
    public ErrorFilter errorFilter(){
    	return new ErrorFilter();
    }
    
    @Bean
    public PostFilter postFilter(){
    	return new PostFilter();
    }
}
public class ErrorFilter extends ZuulFilter {
	
	@Override
	public String filterType() {
		return ERROR_TYPE;
	}
	
	@Override
	public int filterOrder() {
		return -1;
	}
 
	@Override
	public boolean shouldFilter() {
		return true;
	}
 
	@Override
	public Object run() throws ZuulException {
		RequestContext ctx = RequestContext.getCurrentContext();
		System.out.println("这是ErrorFilter");
		
		return null;
	}
 
}
public class FirstPreFilter extends ZuulFilter {
	
	@Override
	public String filterType() {
		return PRE_TYPE;
	}
	
	@Override
	public int filterOrder() {
		return 0;
	}
 
	@Override
	public boolean shouldFilter() {
		return true;
	}
 
	@Override
	public Object run() throws ZuulException {
		System.out.println("这是第一个自定义Zuul Filter!");
		return null;
	}
}
public class SecondPreFilter extends ZuulFilter {
 
	@Override
	public String filterType() {
		return PRE_TYPE;
	}
 
	@Override
	public int filterOrder() {
		return 2;
	}
 
	@Override
	public boolean shouldFilter() {
		return true;
	}
 
	@Override
	public Object run() throws ZuulException {
		System.out.println("这是SecondPreFilter!");
		//从RequestContext获取上下文
		RequestContext ctx = RequestContext.getCurrentContext();
		//从上下文获取HttpServletRequest
		HttpServletRequest request = ctx.getRequest();
		//从request尝试获取a参数值
		String a = request.getParameter("a");
		//如果a参数值为空则进入此逻辑
		if (null == a) {
			//对该请求禁止路由,也就是禁止访问下游服务
			ctx.setSendZuulResponse(false);
			//设定responseBody供PostFilter使用
			ctx.setResponseBody("{\"status\":500,\"message\":\"a参数为空!\"}");
			//logic-is-success保存于上下文,作为同类型下游Filter的执行开关
			ctx.set("logic-is-success", false);
			//到这里此Filter逻辑结束
			return null;
		}
		//设置避免报空
		ctx.set("logic-is-success", true);
		return null;
	}
}
public class ThirdPreFilter extends ZuulFilter {
	
	@Override
	public String filterType() {
		return PRE_TYPE;
	}
	
	@Override
	public int filterOrder() {
		return 3;
	}
 
	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		//从上下文获取logic-is-success值,用于判断此Filter是否执行
		return (boolean)ctx.get("logic-is-success");
	}
 
	@Override
	public Object run() throws ZuulException {
		System.out.println("这是ThirdPreFilter!");
		//从RequestContext获取上下文
		RequestContext ctx = RequestContext.getCurrentContext();
		//从上下文获取HttpServletRequest
		HttpServletRequest request = ctx.getRequest();
		//从request尝试获取b参数值
		String b = request.getParameter("b");
		//如果b参数值为空则进入此逻辑
		if (null == b) {
			//对该请求禁止路由,也就是禁止访问下游服务
			ctx.setSendZuulResponse(false);
			//设定responseBody供PostFilter使用
			ctx.setResponseBody("{\"status\":500,\"message\":\"b参数为空!\"}");
			//logic-is-success保存于上下文,作为同类型下游Filter的执行开关,假定后续还有自定义Filter当设置此值
			ctx.set("logic-is-success", false);
			//到这里此Filter逻辑结束
			return null;
		}
		return null;
	}
}
public class PostFilter extends ZuulFilter {
	
	@Override
	public String filterType() {
		return POST_TYPE;
	}
	
	@Override
	public int filterOrder() {
		return 0;
	}
 
	@Override
	public boolean shouldFilter() {
		return true;
	}
 
	@Override
	public Object run() throws ZuulException {
		System.out.println("这是PostFilter!");
		//从RequestContext获取上下文
		RequestContext ctx = RequestContext.getCurrentContext();
		//处理返回中文乱码
		ctx.getResponse().setCharacterEncoding("GBK");
		//获取上下文中保存的responseBody
		String responseBody = ctx.getResponseBody();
		//如果responseBody不为空,则说明流程有异常发生
		if (null != responseBody) {
			//设定返回状态码
			ctx.setResponseStatusCode(500);
			//替换响应报文
	        ctx.setResponseBody(responseBody);
		}
		return null;
	}
}
spring:
  application:
    name: zuul-server
server:
  port: 5555
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8888}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
zuul:
  routes:
    client-a:
      path: /client/**
      serviceId: client-a
management.endpoints.web.exposure.include=*
#management.endpoint.shutdown.enabled=true
management.endpoint.health.show-details=always
<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>  
	</dependencies>
 
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

这里我简单介绍一下上面的代码

1.配置每种过滤器的类都要继承ZuulFilter。

2.每种过滤器实现类必须将他注入spring bean容器。

3.重写ZuulFilter的方法中,

filterType方法:使用返回值设定Filter类型,可以设置pre、route、post、error。

filterOrder方法:使用返回值确定当前类型的过滤器执行顺序,同一类型的过滤器,数值越小越先执行,如果每种类型都有一个过滤器的话,返回值写0就好了。

shouldFilter方法:使用返回值确定该Filter是否执行/生效,可以当作这个过滤器的开关,true:开,false:关。

run方法:这里就写你这个过滤器的业务逻辑,想让该过滤器做什么都可以在这里写。

4.上文的FirstPreFilter代表第一级pre过滤器,所以执行顺序我写了0。

5.SecondPreFilter类为第二级pre过滤器,执行顺序是2,用来从request尝试获取a参数值,如果获取到了a参数,就给RequestContext对象加入一个自定义的属性:logic-is-success,值:true。如果没有获取到就在相应体加入状态:500,描述:a参数为空!,自定义属性:logic-is-success, 值为:false,自定义logic-is-success属性的目的是为了当前过滤器的下游使用。

6.ThirdPreFilter类为第三级pre过滤器,执行顺序是3,用来从request尝试获取b参数值。如果没有获取到就在相应体加入状态:500,描述:b参数为空!,自定义属性:logic-is-success, 值为:false,自定义logic-is-success属性的目的是为了当前过滤器的下游使用,在这个类里shouldFilter方法获取了a参数如果a参数有值,ThirdPreFilter方法才会生效,否则不执行ThirdPreFilter方法,

7.ErrorFilter类是在pre、route、post过滤器中发生类异常才会触发error过滤器执行,并且触发类error后再执行post过滤器。所以说这里可以再pre过滤器中设置权限,不符合可以抛出异常,这样再error接收到可以做相应的处理。

8.

zuul:

routes:

client-a:

path: /client/**

serviceId: client-a

配置的含义是把所有请求为http://ip:5555/client/……所有的都映射到client-a服务中,……代表的是client-a服务中的url。

实际开发中类似这样映射服务的配置会有很多。

测试

1.这里为了给大家演示,我故意再第一个pre过滤器里出现异常,然后观察控制台打印效果。

public class FirstPreFilter extends ZuulFilter {
	
	@Override
	public String filterType() {
		return PRE_TYPE;
	}
	
	@Override
	public int filterOrder() {
		return 0;
	}
 
	@Override
	public boolean shouldFilter() {
		return true;
	}
 
	@Override
	public Object run() throws ZuulException {
		System.out.println("这是第一个自定义Zuul Filter!");
		int a = 1/0;
		return null;
	}
}

中间异常信息省略

可以看到如果pre出现了异常,过滤器的执行顺序先是error,然后是post

2.接下来把手动触发异常的代码删掉,测试上面给出客户端的请求,这里我就简单使用一个请求给大家演示。

访问http://localhost:5555/client/add?a=1

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 晏霖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档