Spring Cloud笔记(五)

一. 前言

​ 随着微服务的数量越来越多,外部客户端调用起来也就越来越麻烦。一个大型的项目一般都有上百个微服务,如果像我们之前的调用方法去调用,对于客户来说是很难接受的。所以这时候需要一个微服务的网关,作为一个中间层,简化这些操作。这部分讲述的是Zuul。

二. 使用Zuul构建微服务网关

由于不同的微服务会有不同的网络地址/端口号(比如上面的各种微服务,虽然有相同的地址,但端口号各不相同),当微服务数量很多时,外部客户端调用起来就比较麻烦。所以这时候需要一个网关,作为客户端与服务端的中间层,由网关层负责转发,这样客户端就只需要记住网关一个地址。同时网关还有其他的作用,比如统一认证(上面的Security都是东一个西一个,比较杂乱)。下面是具体的Zuul的作用列表:

sb_33

可以看到,它整合了Ribbon实现了负载均衡,也整合了Hystrix实现了监控,同时也具备Security,Spring MVC(Dynamic Routing)的作用。


使用Zuul微服务网关的步骤:

(Zuul作为一个新的子module)

①导入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

②启动类上加上@EnableZuulProxy

(这里还有的两个方法,一个是添加hystrix.stream结点,一个是用于注册到Eureka的认证,都是前面的内容了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
@Bean
public ServletRegistrationBean getServlet() {
//...
}

@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
//...
}

public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}

③编写yml(都是端口号,服务名,注册到哪个Eureka等等的基础)

1
2
3
4
5
6
7
8
9
server:
port: 8040
spring:
application:
name: microservice-gateway-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

这时候理应就完成了,确实很简单,唯一特别的就是一个注解@EnableZuulProxy。该注解是声明一个Zuul代理,该代理会使用Ribbon来定位注册在Eureka上的所有微服务。所以,使用该注解的Zuul,如果不添加其他配置,就是默认监控所有的其他注册在Eureka的服务。

但是还是出现了bugs,也进行了很长时间的排查。遇到的问题:

Zuul理应是已经代理了所有的微服务,这时候直接使用Zuul的地址,然后加上被代理的服务名,还有后面的路径应该就可以成功,比如: localhost:8040/micro…/user/1。但是显示404错误

这个错误表明没有注册成功,可是@EnableZuulProxy是默认注册的,根本不需要手动配置。一开始猜想是不是又是版本的原因?所以还尝试了一下比较含糊不清的手动配置。但由于一键配置都没搞好,于是手动配置也出错,也可能是因为这个注册就是一键配置的,手动配置的都会被discard。然后到了最后,发现错误竟然是!

①url区分大小写。其实直到目前为止,我都没有配置url大小写敏感的问题。这个并不是在zuul里配置,而是在整个的Spring boot,Spring MVC里配置,因为url一只是case sensitive的。

②我把服务名字记错了。。类名是……movie,结果yml里写成了……user

把这个问题解决之后,确实就不再是404,而是401,也就是认证的问题。关于认证,也存在一个很严重的问题。无论我如何配置security,如何配置Configuration类,都无法通过认证。具体表现为,打开8040端口(Zuul端口),这时候会弹出认证框,因为我需要访问8011(Consumer服务端口)。我可以传递一次参数给8011,使得它通过认证,但是8011后续访问8001(Provider服务端口)也需要认证,而这个参数是不会继续传递的。这也很好理解,如果8011需要访问多个端口的集成,那么参数该如何传递?所以不继续传递参数应该是一个正确的设定。那么问题来了,应该如何解决这个问题?

最后的解决方案是,直接把provider里的Security内容去掉。看起来很tricky,但其实很合理。因为上面就已经提到了,Zuul的作用之一便是Authentication。所以服务的安全性问题,应该放在Zuul里处理,而Eureka仍然是特殊独立的存在,不需要更改。所以去掉 Service的 Security是合理的。

自此,就可以通过Zuul端口来访问其他服务了。据说整合了Ribbon,但懒得测试负载均衡了。而Hystrix,应该是版本问题了,所以还是要自己手动添加了hystrix.stream端点,然后跟Dashboard等等的整合也是可以,唯一奇怪的点是,使用Dashboard的时候,Circuit的显示数据都是正常的,但Thread Pools没有数据(之前使用Turbine都有的),看了一下书,刚好图只截到了Circuit,到了Thread Pools就没了,但这部分在后面的部分会解决,这里暂时不考虑.

由于Zuul作为一个网关,管理了比较多的服务,所以使用actuator管理端点就比较重要了。

导入了依赖之后,在ymll直接暴露所有的端点:(注意,不能直接用*,也不能用双引号

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: '*'

还可以添加exclude,此处略。

比较重要的端点都有routes,filters,caches等等。同时还能自定义endpoints。具体的可以参考这篇文章:https://www.cnblogs.com/baidawei/p/9183531.html

关于routing的一些配置:

1
2
3
4
5
zuul:
routes:
microservice-simple-consumer-user: /user111/**
ignored-services: microservice-simple-consumer-user
ignored-patterns: /**/user/** # 忽略所有包含user的路径

此处就是, /user111 就相当于 /microservice-simple-consumer-user。/**是指可能有多个路径。值得注意的是,虽然ignored了consumer,但其实只是ignored了这个默认的映射,而我们手动配置了它的映射路径别名,所以仍然可以通过user111来映射到consumer服务。而且可以通过ignored_patterns,忽略掉包含xxx的路径,比如把敏感信息放到admin,这样忽略掉之后,就无法通过zuul访问该admin路径了。这里我用的是忽略user,可以看到下面的效果图,对于访问user1没有影响,却无法访问user。如图:

sb_34

sb_35

还有一些关于actuator的端点:

sb_36

sb_37

filters端点包含了error,post,pre,route四种类型的过滤器,并且包含执行顺序order,可以用于定位Zuul问题。

关于routes,还可以通过/routes/details来获取更具体的信息

sb_38

三. Zuul的一些其他配置

Zuul的安全与Header。由于Zuul是一个代理网关,因而它可以使得同一个系统的服务之间共享Header。但有一些敏感的Header不应该外泄,因此需要指定敏感的Header列表,下面是默认的配置:(users是一个服务名)所有的服务都默认将Cookie,Set-Cookie,Authorization设为敏感。

1
2
3
4
5
6
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream

如果需要取消所有敏感Header,比如服务只在内部传递,为了共享所有的Header,包括Cookie,可以将sensitiveHeaders设为空。 (之前看过这种写法,还以为是错误的写法)

上面的是对单个服务的配置,可以直接用 zuul-sensitiveHeaders 来进行全局配置。

zuul-ignored-headers。忽略Header。

ignored-headers与sensitive-headers的区别:

一个很清晰的答案:

sb_39

sensitive-headers是指明敏感headers,避免数据泄漏。而ignored-headers是直接指明要忽略的headers,使得不仅是zuul传递到下游服务的request,还是下游服务的response,都会忽略掉该headers(作用不同,目的也不同,但据说sensitive也会默认添加到ignored,所以区别不大)

多层路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
zuul:
routes:
first:
path: /first/**
url: https://first.example.com
second:
path: /second/**
url: forward:/second
third:
path: /third/**
url: forward:/3rd
legacy:
path: /**
url: https://legacy.example.com

看起来legacy会跟另外三个冲突,但由于另外三个更complex,所以当第一个url为first/second/third,就会优先调用前三个,其他的url才会调用legacy,很好理解。官方文档称之为:strangle patterns


实现上传文件功能:

①Controller:

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class FileUploadController {
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody String handleFileUpload(
@RequestParam(value = "file") MultipartFile file) throws IOException {
byte[] bytes = file.getBytes();
File fileToSave = new File(file.getOriginalFilename());
FileCopyUtils.copy(bytes, fileToSave);
return fileToSave.getAbsolutePath();
}
}

② yml:(不加单位默认是KB,单位必须两个都大写,当前版本2.2.0.RELEASE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8050
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
spring:
application:
name: microservice-file-upload
servlet:
multipart:
max-file-size: 200MB # default 1M
max-request-size: 300MB # default 10M

需要用到的依赖包是netflix-eureka-client,启动类需要的是@SpringBootApplication, @EnableEurekaClient注解。

③使用curl测试:

(1)直接通过FileUpload服务的端口调用

sb_40

(2)使用Zuul代理:

sb_41

值得注意的是,使用Zuul代理时,当传输超过1MB(默认)的文件时,需要增加zuul路径,否则会报错(即使没有超过yml里设定的大小)。而无论是否使用zuul,传输的文件都不能超过yml里设定的大小。而直接调用FileUpload服务时,则不需要该路径。

关于为什么要这个zuul路径,官方文档的解释:

If you use @EnableZuulProxy, you can use the proxy paths to upload files and it should work, so long as the files are small. For large files there is an alternative path that bypasses the Spring DispatcherServlet (to avoid multipart processing) in “/zuul/“. In other words, if you have zuul.routes.customers=/customers/**, then you can POST large files to /zuul/customers/. The servlet path is externalized via zuul.servletPath. If the proxy route takes you through a Ribbon load balancer, extremely large files also require elevated timeout settings, as shown in the following example:

意思就是,使用zuul代理时,传递大文件时的路径会转换为/zuul/xxx,所以就需要/zuul路径了。

Zuul is implemented as a Servlet.For the general cases,Zuul is embedded into the Spring Dispatch mechanism.This lets Spring MVC be in control of the routing.In this case,Zuul buffers requests.If there is a need to go through Zuul without buffering requests (for example,for large file uploads), the Servlet is also installed outside of the Spring Dispatcher.By default,the servlet has an address of /zuul.This path can be changed with the zuul.servlet-pathproperty.

关于查询参数的编码和解码,直接看官方文档:

sb_42

简单的说就是,使用Zuul传递参数的时候,参数可能会发生变化(比如使用了JS的encodeURIComponent方法),这可能会导致一些奇怪的错误。如果需要禁止参数变化,保持查询参数不会在传递时发生改变,就需要设定forceOriginalQueryStringEncoding参数为true。

关于这个参数,默认是false的:

1
private boolean forceOriginalQueryStringEncoding = false;

GitHub上有人提出了issue,认为应该默认是true,否则容易出现奇怪的问题。管理员也认同这个观点,但项目已经进入了Maintenance Mode,所以基本不会再维护了。所以只能手动配置参数。

(同理的还有Request URI Encoding,是否对URI进行decode,默认是true。而URI包含“/”的时候,decode会出现意想不到的错误,此时可以设定zuul-decodeUrl为false,避免decode。)

关于@EnableZuulServer与@EnableZuulProxy

二者区别:

Spring Cloud Netflix installs a number of filters,depending on which annotation was used to enabled Zuul.@EnableZuulProxy is a superset of @EnableZuulServer.In other words,@EnableZuulProxy *contains all the filters installed by *@EnableZuulServer.The additional filters in the “proxy” enable routing functionality.If you want a “blank” Zuul,you should use @EnableZuulServer.

二者都可以启用Zuul,并且都会包含以下过滤器:

① Pre filters:

ServletDetectionFilter: Detects whether the request is through the Spring Dispatcher.Sets a boolean with a key of FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY.

FormBodyWrapperFilter: Parses form data and re-encodes it for downstream requests.

DebugFilter: If the debug request parameter is set,setsRequestContext.setDebugRouting()and RequestContext.setDebugRequest()to true.

② Route filters:

SendForwardFilter: Forwards requests by using the Servlet RequestDispatcher.The forwarding location is stored in the RequestContext attribute,FilterConstants.FORWARD_TO_KEY.This is useful for forwarding to endpoints in the current application.

③ Post filters:

SendResponseFilter: Writes responses from proxied requests to the current response.

④ Error filters:

SendErrorFilter: Forward to /error (by default) if RequestContext.getThrowable()is not null.You can change the default forwarding path (/error) by setting the ``error.path` property.

而EnableZuulProxy还包含以下过滤器:

① Pre filters:

PreDecorationFilter: Determines where and how to route,depending on the supplied RouteLocator.It also sets various proxy-related headers for downstream requests.

② Route filters:

RibbonRoutingFilter: User Ribbon,Hystrix,and pluggable HTTP clients to send requests.Service IDs are found in the RequestContext attribute,FilterConstants.SERVICE_ID_KEY.This filter can user different HTTP clients:

​ (1) Apache HttpClient: The default client.

​ (2) Squareup OkHttpClient v3: Enable by having the com.squareup.okhttp3:okhttp library on the classpath and settingribbon.okhttp.enabled = true.

​ (3) Netflix Ribbon HTTP client: Enabled by setting ribbon.restclient.enabled=true.This client has limitations,including that it does not support the PATCH method,but also has built-in retry.

SimpleHostRoutingFilter: Sends requests to predetermined URLs through an Apache HttpClient.URLs are found in RequestContext.getRouteHost( ).

测试,本来想把@EnableZuulProxy注解换成@EnableZuulServer的,理论上项目不会有任何出错,确实也没有出错,但是却找不到filters端点。一开始以为是没有启动actuator端点,但实际上其他的各种env,health,caches,beans等等的都有,唯独只少了一个filters端点。而且我在yml里是有配置具体的URL映射的:

1
2
3
zuul:
routes:
microservice-simple-consumer-user: /user111/**

使用EnableZuulServer的时候,没有报错,如果映射失败,应该会出现404错误。但是没有报错,也没有任何界面。所以还是用Proxy吧。


禁用filters:

Zuul for Spring Cloud comes with a number of ZuulFilter beans enabled by default in both proxy and server mode.See the Zuul filters package for the list of filters that you can enable.If you want to disable one,setzuul.\<SimpleClassName>.\<filterType>.disable=true.By convention,thepackage after filtersis the Zuul filter type.For exapmple to disable org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,set zuul.SendResponseFilter.post.disable=true.

包括自定义的filters和系统预定义的filters都可以,类型就是pre,post,route,error。

(PS:具体的过滤器内容,还是得看源码才能更清晰,现在大概直到是什么个情况就好。关于过滤器这一块,还能配合Spring的过滤器机制一起看,毕竟像BeanPostProcessor等等的,一直没有去研究,到时可以专门再写一个叫做《阅读Spring&Spring boot的interceptor机制源码》)

关于Zuul的高可用:创建多个Zuul实例,并且使用Nginx,F5等等的负载均衡器来实现负载均衡。

给Zuul的hystrix添加fallback方法:

创建一个FallbackProvider的Bean(也就是这个接口/抽象类的具体类),然后注入(@Component)即可。书上1.5用的是ZuulFallbackProvider,2.x应该是没有这个东西了,直接换成FallbackProvder

Providing Hystrix Fallbacks For Routes

When a circuit for a given route in Zuul is tripped,you can provide a fallback response by creating a bean of type FallbackProvider.Within this bean,you need to specify the route ID the fallback is for and provide a ClientHttpResponse to return as a fallback.The following example shows a relatively simple FallbackProvider implementation.

需要指明要为哪个/哪些服务进行fallback功能,以及回退的逻辑。这里就是简单地几个文字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Component
public class UserFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {

return "*";
}

@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}

@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}

@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}

@Override
public void close() {

}

@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("用户微服务不可使用".getBytes());
}

@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mt);
return headers;
}
};
}
}

这样子之后,当调用的service出现问题,会显示:用户微服务不可使用。

关于Dashboard监控Zuul的Hystrix。

前面有提到Zuul的Hystrix,在Dashboard并没有Thread pools的数据,一开始还以为是作者偷懒,其实是把坑留到后面了。因为Zuul里的Hystrix的隔离策略默认是SEMAPHORE,所以自然就没有Thread pools的数据了。可以使用:zuul.ribbon-isolation-strategy = thread设置为THREAD。

(PS:这样默认会所有的服务都公用一个线程池,需要额外的配置来使得每一个路由/服务使用一个独立的线程池,zuul.threadPool.useSeparateThreadPools : true

关于Zuul整合非JVM服务。上文提到的Zuul管理的服务,都是基于JVM的服务,在一些特定的情况可能不太方便,比如我们可能需要一些其他的诸如python,node.js等等的webservice。然而Zuul是可以整合非JVM服务的,只需要使用Sidecar,这样除了JVM服务,还能注册非JVM服务。

步骤:

①先写一个非JVM微服务,这里用Node.js为例。(即使不懂Node,也能大致看懂功能)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var http = require('http');
var url = require('url');
var path = require('path');
// 创建Server
var server = http.createServer(function(req, res) {
// 获取请求的路径
var pathname = url.parse(req.url).pathname;
res.writeHead(200, {'Content-Type' : 'application/json; charset=utf-8'});
// 访问http://localhost:8060/, 将会返回{"index": "欢迎来到首页"}
if (pathname === '/') {
res.end(JSON.stringify({"index" : "欢迎来到首页"}));
}
// 访问http://localhost:8060/health,将会返回{"status": "UP"}
else if (pathname === '/health.json') {
res.end(JSON.stringify({"status" : "UP"}));
}
else {
res.end("404"); // 其他情况返回404
}
});
// 创建监听,并打印日志
server.listen(8060, function() {
console.log('listening on localhost:8060');
});

②给Zuul添加Sidecar的依赖包

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>

③启动类添加@EnableSidecar注解。This annotation includes @EnableCircuitBreaker, @EnableDiscoveryClient, and @EnableZuulProxy。

④yml里配置sidecar参数,表示要代理的sidecar(非JVM)的uri地址和端口号等等:

1
2
3
sidecar:
port: 8060
health-uri: http://localhost:8060/health.json

⑤controller里写一下调用该sidecar服务的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class testController {
public final RestTemplate restTemplate;

@Autowired
public testController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

@GetMapping("/test/{string}")
public String findById(@PathVariable string string) {
return restTemplate.getForObject("http://localhost:8060/" + string,String.class);
}

@GetMapping("/test11/{id}")
public User test(@PathVariable String id) {
return restTemplate.getForObject("http://localhost:8001/" + id, User.class);
}
}

⑥启动node.js的微服务,启动Eureka,Zuul等等,使用:localhost:8040/test/health.json,调用成功.

中途遇到的问题:其实对于web的基本架构都很了解了,所以虽然书上是在IDEA里一起写的非JVM服务,但我确信在cmd也是一样的,结果却不行。后来发现只是controller的注解,不小心写成了@Controller,导致无法处理JSON数据,因为Node.js里写的返回数据类型是JSON。所以一切其实都很顺理成章。虽然这里调用的是简单的非JVM微服务,但调用其他的webservice其实也是如此罢了。

关于使用Zuul整合多个微服务,其实就是客户端一次调用,然后Zuul端会调用多个微服务。书上使用了RxJava,但我现在还不会,先搁置吧。其实我觉得这个需求直接在service层里编写逻辑也可以完成。当然,至于RxJava的异步优势,后面再考虑。

最后还学习了一下OkHttp3。OkHttp3是一个优秀的HTTP客户端,可以更加高效地使用HTTP。

写了两个简单的GET例子和POST例子,感觉都可行。

依赖包:

1
2
3
4
5
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>

GetExample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GetExample {
private OkHttpClient client = new OkHttpClient();

public String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
return Objects.requireNonNull(response.body()).string();
}
}

public static void main(String[] args) throws IOException {
GetExample example = new GetExample();
String response =
example.run("https://github.com/Hongscar/blog/blob/master/README.md");
System.out.println(response);
}
}

PostExample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class PostExample {
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

private OkHttpClient client = new OkHttpClient();

public String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder().url(url).post(body).build();
try (Response response = client.newCall(request).execute()) {
return Objects.requireNonNull(response.body()).string();
}
}

public String bowlingJson(String player1, String player2) {
return "{'winCondition':'HIGH_SCORE',"
+ "'name':'Bowling',"
+ "'round':4,"
+ "'lastSaved':1367702411696,"
+ "'dateStarted':1367702378785,"
+ "'players':["
+ "{'name':'" + player1 + "','history':
[10,8,6,7,8],'color':-13388315,'total':39},"
+ "{'name':'" + player2 + "','history':
[6,10,5,10,10],'color':-48060,'total':41}"
+ "]}";
}

public static void main(String[] args) throws IOException {
PostExample example = new PostExample();
String json = example.bowlingJson("Jesse", "Jake");
String response = example.post("http://www.roundsapp.com/post", json);
System.out.println(response);
}
}

逻辑也是很清晰,GET方法就是最简单的,获取指定URL的内容,然后把Response的body转换成字符串作为return值。POST方法,需要先传递一个RequestBody到服务端,可以看到在post方法里是先构造了一个RequestBody,再把这个body设置到Request当中,最后再返回Response。服务端获取到了RequestBody之后(在这里是一个JSON字符串),它会根据这个JSON字符串进行生成页面操作(这里的代码是在服务端完成的),生成的结果就作为Response返回。GET方法就是获取了哪个github的README.md的内容,POST方法的结果如下:(可以看到内容就是根据RequestBody生成的

sb_43

中间还遇到了一个比较麻烦的问题(一开始认为是strange error)。

在使用OkHttp3的时候,其实Maven仓库的最新版本是4.2.2,理应使用最新版本。下载也没有问题,写代码也不会报错。但是当我写代码看到new Request.Builder ……其实我大概也知道这是一个内部类,但我就是很想去看一下它的代码,可是跳转过去之后的class文件,并没有代码。它每一个方法的内容都是complied code,还有open等我不认识的关键字,如下:

sb_44

当时显示了一个错误,我没有在意,但后面问题就很大了。我点了run项目,然后项目右下角说没有找到xxx文件(其实就是Request.class文件),但console处没有任何输出,就像是run键没有任何反应。这时候更关键的是,其他的module也无法run。当时以为是IDEA抽风了,还打算从GitHub重新把项目导入一遍。结果不小心点到了debug,发现其他module的debug可用,而run不可用,其实这时候我大概就应该猜到是什么原因了。后面过了许久,其他项目的run也可行了。我再次对GetExample进行run,又是一样的毫无反应,然后其他项目也变得无法run,但可以debug。紧接着我对GetExample进行debug,也是毫无反应。这时候其他的项目不仅无法run,而且无法debug。那么这个看似是“IDEA抽风”的问题就找到了,这个Maven导入的依赖包有问题,虽然没有任何报错,但就是会出错,而且甚至把整个run / debug功能都会卡住。以往如果是项目卡住,好歹还是会有红色的running的显示符,这次真的就像是完全没有反应,不得不说确实还是算IDEA跟Maven的bug吧,但至少知道了是什么原因造成的。于是我就把OkHttp3的版本换成了3.11.0,因为网上有文章的源码解读是使用这个版本的。确实,改了版本之后,跳转到Request就有具体的代码了:

sb_45

然后就不再出现问题,而且也成功运行。那么为什么4.2.2版本不可用?因为OkHttp是适用于Android,Kotlin和Java的HTTP客户端,而且在4.x版本开始,使用了Kotlin重写,从上面的4.2.2的源码其实就可以看到有Kotlin的字样。那么到底Java中能否使用Kotlin编写的OkHttp4.x?估计是可以的,但要配置一些其他东西,但已经超出太远的范围了,所以这里还是就用OkHttp3吧。而且看起来,3跟4的区别好像就只是用Kotlin重新写了一遍而已,并没有什么新的特性,只是更适用于Android开发而已,所以这里不需要考虑了。

至此,Zuul章节完毕。

-------------本文结束感谢您的阅读-------------