Spring Cloud 的整体架构
Service Provider : 暴露服务的服务提供方
Service Consumer : 调用远程服务的服务消费方
EureKa Server :服务注册中心和服务发现中心
(这里可以类比dubbo进行学习)
消费者通过服务名称寻找生产者的位置(ip+端口),dubbo是使用RPC远程调用的方式,这是使用http方式进行访问,返回个json数据
注册中心Eureka
Eureka
是一个服务治理组件,基于REST
的服务,它主要包括服务注册和服务发现,主要用来搭建服务注册中心。
Eureka
是Netflix 公司开发的,Spring Cloud
封装了 Netflix 公司开发的 Eureka
模块来实现服务注册和发现,也就是说Spring Cloud
对Netflix Eureka
做了二次封装;
Eureka
采用了C-S
(客户端/服务端)的设计架构,也就是Eureka
由两个组件组成:Eureka
服务端和Eureka
客户端。Eureka Server
作为服务注册的服务端,它是服务注册中心,而系统中的其他微服务,使用Eureka
的客户端连接到Eureka Server
服务端,并维持心跳连接,Eureka
客户端是一个Java
客户端,用来简化与服务器的交互、负载均衡,服务的故障切换等;有了Eureka
注册中心,系统的维护人员就可以通过Eureka Server
来监控系统中各个微服务是否正常运行。
Eureka与Zookeeper的比较
著名的CAP
理论指出,一个分布式系统不可能同时满足C
(一致性)、A
(可用性)和P
(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A
和C
之间进行权衡,在此Zookeeper
保证的是CP
, 而Eureka则是AP
。
Zookeeper保证CP
在ZooKeeper
中,当master
节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader
选举,但是问题在于,选举leader
需要一定时间, 且选举期间整个ZooKeeper
集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得ZooKeeper
集群失去master
节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。
Eureka保证AP
Eureka
优先保证可用性,Eureka
各个节点是平等的,某几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka
的客户端在向某个Eureka
注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka
还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
搭建和配置Eureka服务注册中心
- 创建一个SpringBoot模块(我这里起名eureka-server),并且添加SpringBoot的相关依赖;
- 勾选 Web --> Spring Web
- 勾选 Spring Cloud Discovery --> Eureka Server
也可以直接添加Eureka Server的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
如果手动添加依赖,必须添加Maven的依赖管理器,否则eureka无法被识别
<properties>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意:如果通过SpringBoot的开发工具创建Web工程那么这个依赖以及依赖管理是自动添加的
- 在application.properties文件中配置Eureka服务注册中心信息
#内嵌定时tomcat的端口
server.port=9100
#设置该服务注册中心的hostname
eureka.instance.hostname=localhost
#由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,
#设置为false表示禁止这种自己向自己注册的默认行为
eureka.client.register-with-eureka=false
#表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务
eureka.client.fetch-registry=false
#指定服务注册中心的位置
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
- 在Spring Boot的入口类上添加一个@EnableEurekaServer注解,用于开启Eureka注册中心服务端
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
至此,Eureka Server 搭建完毕,接下来就进行测试:
启动Spring Boot 内嵌 Tomcat ,启动成功之后,通过在浏览器地址栏访问我们的注册中心;访问地址为:
# 两个参数均为application.properties文件中配置的值
http://${eureka.instance.hostname}:${server.port}
如果按我上面配置的参数,则访问地址为:http://localhost:9100
如果访问成功,则会显示如下的页面:
Eureka的自我保护机制
自我保护机制是当服务器与Eureka注册中心发生网络连接问题时,注册中心通过心跳检测没有发现该服务,会启动自我保护机制,将已注册的服务保留下来,等待服务连接成功;如果关闭自我保护机制,发生网络连接问题时,在注册中心找不到服务地址,Eureka注册中心会将这条服务地址移出注册中心,当网络连接问题解决时,找不到这条地址。
自我保护机制的原则是:宁可保护所有服务,也不盲目注销任何健康的微服务(宁错杀不放过)
application.properties
eureka.server.enable-self-preservation=false
false 表示关闭自我保护机制,true 表示开启自我保护机制,默认是true
开启自我保护机制页面会出现如下红色字体:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
关闭自我保护机制页面会出现如下红色字体:
RENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
Ribbon(客户端负载均衡)
Ribbon是一个客户端负载均衡的算法, 默认通过轮询的方式进行负载均衡
当两个或多个服务名相同地址不同时,需要进行负载均衡。
如何改变负载均衡方式?
在RestTemplateConfig(这个类是自定义配置类,注入restTemplate对象)中,@LoadBalance表示默认使用轮询方式进行负载均衡
将下面方法粘进去即可:
@Bean
public IRule iRule(){
//表示通过随机的方式进行负载均衡,不使用轮询的方式
//在此处使用什么类型的对象,就是用什么方式的负载均衡
return new RandomRule();
}
负载均衡方式有哪些?
RandomRule | 随机 |
RoundRobinRule | 轮询 |
AvailabilityFilteringRule | 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问; |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用 |
RoundRobinRule | 策略,待统计信息足够会切换到该WeightedResponseTimeRule策略; |
RetryRule | 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务; |
BestAvailableRule | 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务; |
ZoneAvoidanceRule | 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务; |
Rest请求模板类—RestTemplate
get请求(查询)
getForEntity()—返回一个ResponseEntity类型的封装,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,比如响应码、contentType、contentLength、响应消息体等;
//用法一:
ResponseEntity<String> entity = restTemplate.getForEntity("url",返回值类型);
String body = entity.getBody();
HttpStatus code = entity.getStatusCode();
int codeValue = entity.getStatusCodeValue();
HttpHeaders headers = entity.getHeaders();
//用法二:不推荐
Object [] params={1,”张无忌”};
//这里的数字0,1 表示params数组中的索引值
restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello?id={0}&name={1}", String.class, params).getBody();
//用法三:推荐
Map<String, Object> paramMap = new ConcurrentHashMap<>();
paramMap.put("id", 1);
paramMap.put("name", "张无忌");
restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello?id={id}&name={name}", String.class, paramMap).getBody();
getForObject—是对getForEntity()的封装,返回一个具体类型的对象,具体用法参照getForEntity();
这里推荐使用getForObject()方法
post请求(添加)
postForEntity(),postForObject()类比get请求,这里以postForObject为例(推荐使用)
//下面三种用法的第二个参数是Object request,表示请求参数列表,null表示不传递参数
//1. 第二个参数是Object类型,但不是什么类型都可以传递,只能传递LinkedMultiVlaueMap类型的对象,或者HashMap的value值是List类型的
//2. 如果第二个参数不传递,建议使用get方式的请求,因为给get方式比post方式要快
//用法一:
ResponseEntity<String> entity = restTemplate.postForObject("url",null,返回值类型);
//用法二:不推荐
Object [] params={1,”张无忌”};
//这里的数字0,1 表示params数组中的索引值
restTemplate.postForObject("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello?id={0}&name={1}",null, String.class, params).getBody();
//用法三:推荐
Map<String, Object> paramMap = new ConcurrentHashMap<>();
paramMap.put("id", 1);
paramMap.put("name", "张无忌");
restTemplate.postForObject("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello?id={id}&name={name}",null, String.class, paramMap).getBody();
restTemplate.postForLocation()
put请求(修改)
put()是void类型,无返回值,是以post方式发出请求,用于访问后台提供者的putMapping请求,因为无返回值,前台接收不到具体的值,后台可以接收到。
put(“url”,Object request)
put(“url”,Object request,可变长参数)
put(“url”,Object request,Map<String,?> map)
//1. put请求不能获取服务提供者的响应数据,实际工作不建议使用,可以使用get或者post对应的方法替代
//2. 这个方法针对后台服务提供者的PutMapping请求,适用于数据修改
LinkedMultiValueMap params = new LinkedMultiValueMap();
params.add("name" ,"chenqi");
params.add("age" , "27"):
restTemplate.put("http://localhost:8080/put",params);
delete请求(删除)
只支持get请求,用于访问后台服务提供者的DeleteMapping 请求。
由于delete无法获取服务提供者响应数据,建议使用getForEntity或getForObject进行替换
delete(“url”)
delete(“url”,Map<String,?> map)
delete(“url”,可变长参数)
//1. 第一个参数是请求路径,第二个参数是请求参数,可以是map对象,也可以是可变长参数
//2. 这个方法针对后台服务提供者的PutMapping请求,适用于数据修改
Map params = new HashMap();
params.add("name" ,"chenqi");
params.add("age" , "27"):
restTemplate.delete("http://localhost:8080/put?name={name}&age={age}",params);
服务熔断
什么是服务熔断?
当提供者对消费者的响应产生堆积时,不能及时处理,或者消费者在一定时间不能及时获取到提供者的响应内容,此时,消费者就采用自己的响应内容返回给客户端,这就是服务熔断机制。
服务熔断的使用者是谁?
消费者采取服务熔断机制。
如何使用服务熔断?
第一步:添加依赖包,此处建议使用自动化工程生成的依赖包,保证版本一致
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
第二步:在入口处添加注解,启动服务熔断机制
//这里也可以使用@SpringCloudApplication注解,它包括以下三个注解的功能
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第三步:在需要进行熔断的位置(方法)添加注解
//@HystrixCommand 用于标志当前方法是熔断机制,只能用在消费者一端
//fallbackMethod后面是回调方法名,@HystrixProperty指定hystrix的某个属性
@HystrixCommand(fallbackMethod="error",commandProperties={@HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds", value="5000")})
@RequestMapping("/test")
public String test(){
return "来自消费者的Hystrix测试";
}
public String error(){
return "服务器繁忙请稍后重试";
}
服务降级与异常处理
什么是服务降级?
当服务提供者或中服务消费者发生异常后或者超时,会触发熔断机制中fallbackMethod中指定的方法,这就是服务降级。
如何进行异常处理?
- 通过服务降级的方式
- 通过自定义熔断器类
当在服务生产者发生异常,如何在服务消费者一端了解异常内容?
在熔断机制中fallbackMethod中指定的方法,对其添加Throwable类型的参数,通过System.out.println(Throwable类型的参数)输出到控制台即可。
自定义Hystrix请求的服务异常熔断处理
MyHystrixCommand.java
public class MyHystrixCommand extends HystrixCommand {
//手动添加两个属性
private String url;
private RestTemplate restTemplate;
//在重写的构造方法中添加两个属性URL、restTemplate
protected MyHystrixCommand(Setter setter,RestTemplate restTemplate ,String url) {
super(setter);
this.restTemplate = restTemplate;
this.url = url;
}
//这个方法不能进行手动调用,手动调用不能进行服务熔断
protected Object run() throws Exception {
return restTemplate.getForEntity(url, String.class).getBody();
}
//返回熔断后的响应数据来替代服务提供者的响应信息
protected Object getFallback() {
//服务因异常而熔断则用于获取错误的异常信息对象
Throwable throwable = super.getExecutionException();
return "服务被熔断了";
}
}
TestController.java
@RequestMapping("/test02")
public Object test01() {
String url = "http://localhost:8081/test";
MyHystrixCommand command = new MyHystrixCommand(
com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")),
restTemplate,url);
Object result = command.execute();
return result;
}
声明式服务消费Feign
什么是Feign?
Feign是SpringCloud中对负载均衡Ribbon和服务熔断Hystrix的整合,是对它们的封装,简化了开发工作
Feign的使用对象是谁?
服务消费者
Feign的使用步骤有哪些?(以下仅描述了消费者)
- 在SpringBoot工程中添加如下依赖
<!-- web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka客户端起步依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- feign起步依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Hystrix负载均衡起步依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 在项目入口类中添加@EnableFeignClients注解,开启Feign功能
- 声明服务,定义一个service接口
//@FeignClient 注解作用是标记当前接口是Feign的一个客户端接口
//属性name 用于指定服务提供者的服务名称
//@FeignClient(name ="11-EUREKA-CLIENT-FEIGN-PROVIDER",fallback= MyFallBack.class)
@FeignClient(name ="11-EUREKA-CLIENT-FEIGN-PROVIDER",fallbackFactory = MyFallbackFactory.class)
public interface TestService {
//标记当前方法用于请求远程服务提供者
//属性 /test 用于指定需要访问的服务的具体请求名称
//注意:当前方法的返回值最好和服务提供者的请求返回值类型相同
// 方法名任意但建议与服务提供者的名字相同
@RequestMapping("/test")
String test();
}
- 自定义Hystrix负载均衡类MyFallBack 实现TestService接口(两种方式)
//第一种方式
@Component
public class MyFallBack implements TestService {
@Override
public String test() {
return "test请求不可用请稍后再试";
}
public String test2() {
return "test2请求不可用请稍后再试-------";
}
}
//第二种方式:可以获取异常信息
@Component
public class MyFallbackFactory implements FallbackFactory<HelloService> {
@Override
public HelloService create(Throwable throwable) {
return new HelloService() {
@Override
public String hello() {
return throwable.getMessage();
}
};
}
}
- controller测试
@RestController
public class TestController {
@Resource
private TestService testService;
@RequestMapping("/test")
public String test() {
String result = testService.test();
return "来自消费者的test============"+result;
}
}
API网关Zuul
什么是网关?
网关是服务消费者对服务提供者进行访问请求时,对这些请求进行管理调度的服务程序。它就像一个安检站一样,所有外部的请求都需要经过它的调度与过滤。
网关管理的是什么?
它管理的是外部请求,是消费者对服务提供者的请求
网关的作用有哪些?
API网关实现请求路由、负载均衡、权限验证等功能
使用Zuul构建API网关步骤
首先要有eureka服务模块、服务提供者模块,服务消费者模块
- 新建api网关模块12-eureka-client-api-gateway ,添加依赖包
<!--添加spring cloud的zuul的起步依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--添加spring cloud的eureka的客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在入口类上添加注解@EnableZuulProxy,开启Zuul的 API网关服务功能
- 在application.properties文件中配置路由规则
#配置服务内嵌的Tomcat端口
server.port=8083
#配置服务的名称
spring.application.name=12-eureka-client-api-gateway
#配置路由规则zuul.routes.api-zuul.path 中api-zuul 可以任意填写
#/api-zuul/** 表示请求的拦截规则类似拦截器的拦截规则以/api-zuul开头的任意目录以及子孙目录中所有请求都会被拦截
zuul.routes.api-zuul.path=/api-zuul/**
#指向服务名字 用于对这个服务下的某个写特定请求进行拦截
zuul.routes.api-zuul.serviceId=12-eureka-client-zuul-provider
#配置API网关到注册中心上,API网关也将作为一个服务注册到eureka-server上
eureka.client.service-url.defaultZone=http://localhost:9100/eureka
- 请求测试1
- http://localhost:8081/test 测试结果:通过Zuul的服务提供者
- http://localhost:8082/test 测试结果:没执行-------来自消费者Zuul的test------------通过Zuul的服务提供者
- http://localhost:8083/api-zuul/test 测试结果:通过Zuul的服务提供者
- 服务消费者测试类
@RestController
public class TestController {
@Resource
private RestTemplate restTemplate;
@RequestMapping("/test")
public String test() {
ResponseEntity<String> result = restTemplate.getForEntity("http://12-EUREKA-CLIENT-ZUUL-PROVIDER/test", String.class);
return "没执行-------来自消费者Zuul的test------------"+result.getBody();
}
@RequestMapping("/test02")
public String test02() {
ResponseEntity<String> result = restTemplate.getForEntity("http://12-eureka-client-api-gateway/api-zuul/test", String.class);
return "执行了-------来自消费者Zuul的test------------"+result.getBody();
}
}
- 请求测试2
- http://localhost:8081/test 测试结果:通过Zuul的服务提供者
- http://localhost:8082/test 测试结果:没执行-------来自消费者Zuul的test------------通过Zuul的服务提供者
- http://localhost:8083/api-zuul/test 测试结果:通过Zuul的服务提供者
- http://localhost:8082/test02 测试结果:执行了-------来自消费者Zuul的test------------通过Zuul的服务提供者
使用Zuul进行请求过滤
自定义过滤器类,这个类定义在API网关模块,
//自定义一个过滤器类用于拦截用户的请求 并继承Zuul提供的过滤器类
@Component
public class AuthFilter extends ZuulFilter {
//过滤器类型 决定式在请求之前还是请求之后指定当前过滤器
@Override
public String filterType() {
//返回pre表示要在请求之前执行过滤器,其他值还有post、error、route和static,当然也可以自定义。
return "pre";
}
//过滤器的排序,如果有多个过滤器那么这些过滤器将按照返回值大小直接排序执行
@Override
public int filterOrder() {
return 0;
}
//是否启动当前过滤器如果返回true表示将要执行这个过滤器,如果返回false表示不执行这个过滤器
@Override
public boolean shouldFilter() {
return true;
}
//如果进入当前过滤后,这个方法就是过滤器中具体的业务逻辑
//注意:这个返回值目前版本没有什么特殊作用 因此返回 null即可
@Override
public Object run() throws ZuulException {
//获取当前请求的上下文对象
RequestContext requestContext=RequestContext.getCurrentContext();
//获取当前请求对象
HttpServletRequest request=requestContext.getRequest();
//获取请求参数中的token数据(用户的身份令牌)
String token=request.getParameter("token");
if(token==null){//进入if表示当前请求中没有携带token我们任务当前请求是非法请求
//通知ZuulAPI网关当前请求非法
requestContext.setSendZuulResponse(false);
//设置响应编码为401 表示权限不足
requestContext.setResponseStatusCode(401);
//设置响应的头文件信息以html或文本响应编码格式为utf-8
requestContext.addZuulResponseHeader("content-type","text/html;charset=utf-8");
//设置响应的内容
requestContext.setResponseBody("非法访问");
}else{
System.out.println("用户携带了身份令牌需要验证这个身份是否真的合法");
}
return null;
}
}
Zuul路由规则
- 简化配置
#配置路由规则
zuul.routes.api-zuul.path=/api-zuul/**
zuul.routes.api-zuul.serviceId=08-springcloud-eureka-client-zuul-consumer
当访问地址符合/api-zuul/**规则的时候,会被自动定位到08-springcloud-eureka-client-zuul-consumer服务上,不过两行代码有点麻烦,还可以简化为:
#zuul.routes后面跟着的是服务名,服务名后面跟着的是路径规则,这种配置方式更简单。
zuul.routes.08-springcloud-eureka-client-zuul-consumer =/api-zuul/**
- 忽略掉某些接口路径,添加配置
#忽略掉某一些接口路径
zuul.ignored-patterns=/**/hello/**
- 配置网关路由前缀
#配置网关路由的前缀
zuul.prefix=/myapi
此时我们的访问路径就变成了http://localhost:8080/myapi/api-zuul/test
- 路由规则通配符的含义:
通配符 | 含义 | 举例 | 说明 |
---|---|---|---|
? | 匹配任意单个字符 | /05-springcloud-service-feign/? | 匹配 /05-springcloud-service-feign/a,/05-springcloud-service-feign/b,/05-springcloud-service-feign/c等 |
* | 匹配任意数量的字符 | /05-springcloud-service-feign/* | 匹配 /05-springcloud-service-feign/aaa,/05-springcloud-service-feign/bbb,/05-springcloud-service-feign/ccc等,无法匹配 /05-springcloud-service-feign/a/b/c |
** | 匹配任意数量的字符 | /05-springcloud-service-feign/** | 匹配 /05-springcloud-service-feign/aaa,/05-springcloud-service-feign/bbb,/05-springcloud-service-feign/ccc等,也可以匹配 /05-springcloud-service-feign/a/b/c |
- 在API网关服务上做一些特殊的业务逻辑处理:让请求到达API网关后,再转发给自己本身,由API网关自己来处理
在08-springcloud-eureka-client-zuul项目中新建如下Controller:
@RestController
public class GateWayController {
@RequestMapping("/api/local")
public String hello() {
return "exec the api gateway.";
}
}
然后在application.properties文件中配置:
zuul.routes.gateway.path=/gateway/**
zuul.routes.gateway.url=forward:/api/local
Zuul的异常处理
Zuul请求的生命周期图:
- 正常情况下所有的请求都是按照pre、route、post的顺序来执行,然后由post返回response
- 在pre阶段,如果有自定义的过滤器则执行自定义的过滤器
- pre、routing、post的任意一个阶段如果抛异常了,则执行error过滤器
我们可以有两种方式统一处理异常:
1、禁用zuul默认的异常处理SendErrorFilter过滤器,然后自定义我们自己的Errorfilter过滤器
zuul.SendErrorFilter.error.disable=true
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
try {
RequestContext context = RequestContext.getCurrentContext();
ZuulException exception = (ZuulException)context.getThrowable();
logger.error("进入系统异常拦截", exception);
HttpServletResponse response = context.getResponse();
response.setContentType("application/json; charset=utf8");
response.setStatus(exception.nStatusCode);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.print("{code:"+ exception.nStatusCode +",message:\""+ exception.getMessage() +"\"}");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(writer!=null){
writer.close();
}
}
} catch (Exception var5) {
ReflectionUtils.rethrowRuntimeException(var5);
}
return null;
}
}
2、自定义全局error错误页面
@RestController
public class ErrorHandlerController implements ErrorController {
/**
* 出异常后进入该方法,交由下面的方法处理
*/
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public Object error(){
RequestContext ctx = RequestContext.getCurrentContext();
ZuulException exception = (ZuulException)ctx.getThrowable();
return exception.nStatusCode + "--" + exception.getMessage();
}
}
注意:全局error错误页面与自定义异常的过滤器有冲突二选一即可