点赞再看,养成习惯,微信搜索【牧小农】关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友。

项目源码地址:公众号回复 sentinel,即可免费获取源码

背景

在微服务架构中,通常一个系统会被拆分为多个微服务,面对这么多微服务客户端应该如何去调用呢?如果没有其他更优方法,我们只能记录每个微服务对应的地址,分别去调用,但是这样会有很多的问题和潜在因素。

  1. 客户端多次请求不同的微服务,会增加客户端代码和配置的复杂性,维护成本比价高。
  2. 认证复杂,每个微服务可能存在不同的认证方式,客户端去调用,要去适配不同的认证,
  3. 存在跨域的请求,调用链有一定的相对复杂性(防火墙 / 浏览器不友好的协议)。
  4. 难以重构,随着项目的迭代,可能需要重新划分微服务

为了解决上面的问题,微服务引入了 网关 的概念,网关为微服务架构的系统提供简单、有效且统一的API路由管理,作为系统的统一入口,提供内部服务的路由中转,给客户端提供统一的服务,可以实现一些和业务没有耦合的公用逻辑,主要功能包含认证、鉴权、路由转发、安全策略、防刷、流量控制、监控日志等。

网关在微服务中的位置:

网关对比

  • Zuul 1.0 : Netflix开源的网关,使用Java开发,基于Servlet架构构建,便于二次开发。因为基于Servlet内部延迟严重,并发场景不友好,一个线程只能处理一次连接请求。

  • Zuul 2.0 : 采用Netty实现异步非阻塞编程模型,一个CPU一个线程,能够处理所有的请求和响应,请求响应的生命周期通过事件和回调进行处理,减少线程数量,开销较小

  • GateWay : 是Spring Cloud的一个全新的API网关项目,替换Zuul开发的网关服务,基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能高于Zuul

  • Nginx+lua : 性能要比上面的强很多,使用Nginx的反向代码和负载均衡实现对API服务器的负载均衡以及高可用,lua作为一款脚本语言,可以编写一些简单的逻辑,但是无法嵌入到微服务架构中

  • Kong : 基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,性能高效且稳定,支持多个可用插件(限流、鉴权)等,开箱即可用,只支持HTTP协议,且二次开发扩展难,缺乏更易用的管理和配置方式

GateWay

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

Spring Cloud Gateway 是Spring Cloud的一个全新的API网关项目,目的是为了替换掉Zuul1,它基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能⾼于Zuul,官⽅测试,Spring Cloud GateWay是Zuul的1.6倍 ,旨在为微服务架构提供⼀种简单有效的统⼀的API路由管理⽅式。

  1. 可以与Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等组件配合使用,实现路由转发、负载均衡、熔断、鉴权、路径重写、⽇志监控等

  2. Gateway还内置了限流过滤器,实现了限流的功能。

  3. 设计优雅,容易拓展

基本概念

路由(Route)是GateWay中最基本的组件之一,表示一个具体的路由信息载体,主要由下面几个部分组成:

  1. id:路由唯一标识,区别于其他的route
  2. url: 路由指向的目的地URL,客户端请求最终被转发到的微服务
  3. order: 用于多个Route之间的排序,数值越小越靠前,匹配优先级越高
  4. predicate:断言的作用是进行条件判断,只有断言为true,才执行路由
  5. filter: 过滤器用于修改请求和响应信息

核心流程

核心概念:

  1. Gateway ClientSpring Cloud Gateway 发送请求
  2. 请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文
  3. 然后网关的上下文会传递到 DispatcherHandler ,它负责将请求分发给 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用
  5. 如果过断言成功,由 FilteringWebHandler 创建过滤器链并调用
  6. 通过特定于请求的 Fliter 链运行请求,Filter 被虚线分隔的原因是Filter可以在发送代理请求之前(pre)和之后(post)运行逻辑
  7. 执行所有pre过滤器逻辑。然后进行代理请求。发出代理请求后,将运行“post”过滤器逻辑。
  8. 处理完毕之后将 Response 返回到 Gateway 客户端

Filter过滤器:

  • Filter在pre类型的过滤器可以做参数效验、权限效验、流量监控、日志输出、协议转换等。
  • Filter在post类型的过滤器可以做响应内容、响应头的修改、日志输出、流量监控等

核心思想

当用户发出请求达到 GateWay 之后,会通过一些匹配条件,定位到真正的服务节点,并且在这个转发过程前后,进行一些细粒度的控制,其中 Predicate(断言) 是我们的匹配条件,Filter 是一个拦截器,有了这两点,再加上URL,就可以实现一个具体的路由,核心思想:路由转发+执行过滤器链

这个过程就好比考试,我们考试首先要找到对应的考场,我们需要知道考场的地址和名称(id和url),然后我们进入考场之前会有考官查看我们的准考证是否匹配(断言),如果匹配才会进入考场,我们进入考场之后,(路由之前)会进行身份的登记和考试的科目,填写考试信息,当我们考试完成之后(路由之后)会进行签字交卷,走出考场,这个就类似我们的过滤器

Route(路由) :构建网关的基础模块,由ID、目标URL、过滤器等组成

Predicate(断言) :开发人员可以匹配HTTP请求中的内容(请求头和请求参数),如果请求断言匹配贼进行路由

Filter(过滤) :GateWayFilter的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改

框架搭建

通过上述讲解已经了解了基础概念,我们来动手搭建一个GateWay项目,来看看它到底是如何运行的

新建项目:cloud-alibaba-gateway-9006

版本对应:

GateWay属于SprinigCloud且有web依赖,在我们导入对应依赖时,要注意版本关系,我们这里使用的版本是 2.2.x的版本,所以配合使用的Hoxton.SR5版本

在这里我们要注意的是引入GateWay一定要删除spring-boot-starter-web依赖,否则会有冲突无法启动

父类pom引用:

<spring-cloud-gateway-varsion>Hoxton.SR5</spring-cloud-gateway-varsion>

 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-gateway-varsion}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

子类POM引用:

<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

yml配置

server:
port: 9006
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: false #开启注册中心路由功能
routes: # 路由
- id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: http://localhost:9001/nacos-provider # 匹配提供服务的路由地址 lb://表示开启负载均衡
predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由

我们在之前的cloud-alibaba-nacos-9001项目中添加下面测试代码

@RestController
@RequestMapping("/mxn")
public class DemoController { @Value("${server.port}")
private String serverPort; @GetMapping(value = "/hello")
public String hello(){
return "hello world ,my port is :"+serverPort;
} }

启动Nacos、cloud-alibaba-nacos-9001cloud-alibaba-gateway-9006通过gateway网关去访问9001的mxn/order看看。

首先我们在Nacos中看到我们服务是注册到Nacos中了

然后我们访问http://localhost:9001/mxn/hello,确保是成功的,在通过http://localhost:9006/mxn/hello去访问,也是OK,说明我们GateWay搭建成功,我们进入下一步

在上述方法中我们是通过YML去完成的配置,GateWay还提供了另外一种配置方式,就是通过代码的方式进行配置,@Bean 注入一个 RouteLocator

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class GateWayConfig { /*
配置了一个id为path_mxn的路由规则
当访问地址http://localhost:9999/mxn/**
就会转发到http://localhost:9001/nacos-provider/mxn/任何地址
*/
@Bean
public RouteLocator gateWayConfigInfo(RouteLocatorBuilder routeLocatorBuilder){
// 构建多个路由routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 具体路由地址
routes.route("path_mxn",r -> r.path("/mxn/**").uri("http://localhost:9001/nacos-provider")).build();
// 返回所有路由规则
return routes.build();
}
}

我们可以将路由注释掉之后看一下,重启9006服务,访问地址http://localhost:9006/mxn/hello 就可以转发到9001中具体的接口中

这里并不推荐,使用代码的方式来进行配置gateWay,大家有个了解就可以,因为代码的配置维护的成本比较高,而且对于一些需要修改的项,需要改代码才可以完成,这样不利于维护和拓展,所以还是推荐大家使用yml进行配置。

GateWay负载均衡

在上述的讲解中,我们已经掌握了 GateWay 的一些基本配置和两种使用方式,下面我们就来讲解一下 GateWay 如何实现负载均衡

我们只需要在9006中添加lb://nacos-provider就可以显示负载均衡。

当我们去访问http://localhost:9006/mxn/hello的时候,就可以看到9001和9002不停的切换

Predicate 断言

在这一篇中我们来研究一下 断言 ,我们可以理解为:当满足条件后才会进行转发路由,如果是多个,那么多个条件需要同时满足

在官方提供的断言种类有11种(最新的有12种类型):

具体地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

  1. After:匹配在指定日期时间之后发生的请求。
  2. Before:匹配在指定日期之前发生的请求。
  3. Between:需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。
  4. Cookie:需要指定两个参数,分别为name和regexp(正则表达式),也可以理解Key和Value,匹配具有给定名称且其值与正则表达式匹配的Cookie。
  5. Header:需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。
  6. Host:匹配当前请求是否来自于设置的主机。
  7. Method:可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
  8. Path:匹配指定路径下的请求,可以是多个用逗号分隔
  9. Query:需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。
  10. RemoteAddr:匹配指定IP或IP段,符合条件转发。
  11. Weight:需要两个参数group和weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由

1. After : 表示配置时间之后才进行转发

时间戳获取代码,用于时间代码的获取:

    public static void main(String[] args) {
ZonedDateTime zbj = ZonedDateTime.now();//默认时区
System.out.println(zbj);
}
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #开启注册中心路由功能
routes: # 路由
- id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: lb://nacos-provider # 匹配提供服务的路由地址 lb://表示开启负载均衡
predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
- After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问

如果在时间段之前访问则404

Before

匹配ZonedDateTime类型的时间,表示匹配在指定日期时间之前的请求,之后的请求则拒绝404错误

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]

Between

Between 可以匹配ZonedDateTime类型的时间,由两个ZonedDateTime参数组成,第一个参数为开始时间,第二参数为结束时间,逗号进行分隔,匹配在指定的开始时间与结束时间之内的请求,配置如下:

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]

Cookie

由两个参数组成,分别为name(Key)regexp(正则表达式)(Value),匹配具有给定名称且其值与正则表达式匹配的Cookie。

路由规则会通过获取Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果匹配不上则不执行。

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母

小写字母匹配成功:

数字匹配不成功:

Header

由两个参数组成,第一个参数为Header名称,第二参数为Header的Value值,指定名称的其值和正则表达式相匹配的Header的请求

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
- Header=headerName, \d+ # \d表示数字

请求头携带数字断言请求成功,

断言字母匹配失败:

Host

匹配当前请求是否来自于设置的主机。

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
# - Header=headerName, \d+ # \d表示数字
- Host=**.muxiaonong.com #匹配当前的主机地址发出的请求

满足Host断言,请求成功



不满足Host断言失败

**Method **

可以设置一个或多个参数,匹配HTTP请求,比如POST,PUT,GET,DELETE

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
# - Header=headerName, \d+ # \d表示数字
# - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
- Method=POST,GET

GET断言成功



PUT断言请求失败

Query

由两个参数组成,第一个为参数名称(必须),第二个为参数值(可选-正则表达式),匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合第二个正则表达式。

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
# - Header=headerName, \d+ # \d表示数字
# - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
# - Method=POST,GET
- Query=id,.+ # 匹配任意请求参数,这里如果需要匹配多个参数,可以写多个- Query=

断言匹配 请求成功

RemoteAddr

参数由CIDR 表示法(IPv4 或 IPv6)字符串组成,也就是匹配的ID地址,配置如下:

predicates: # 断言
- Path=/mxn/** # 断言,路径相匹配进行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
# - Header=headerName, \d+ # \d表示数字
# - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
# - Method=POST,GET
# - Query=id,.+ # 匹配任意请求参数,这里如果需要匹配多个参数,可以写多个Query
- RemoteAddr=192.168.1.1/24

RemoteAddr

需要两个参数group和weight(int)权重数值,实现了路由权重功能,表示将相同的请求根据权重跳转到不同的uri地址,要求group的名称必须一致

routes:  # 路由
- id: weight_high #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: https://blog.csdn.net/qq_14996421
predicates: # 断言
- Weight=groupName,8
- id: weight_low #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: https://juejin.cn/user/2700056290405815
predicates: # 断言
- Weight=groupName,2

直接访问http://localhost:9006/可以看到我们请求的地址成8/2比例交替显示, 80% 的流量转发到https://blog.csdn.net/qq_14996421,将约 20% 的流量转发https://juejin.cn/user/2700056290405815

Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。如果有多个断言则全部命中后进行处理

GateWay Filter

路由过滤器允许修改传入的HTTP请求或者返回的HTTP响应,路由过滤器的范围是特定的路由.

Spring Cloud GateWay 内置的Filter生命周期有两种:pre(业务逻辑之前)、post(业务逻辑之后)

GateWay本身自带的Filter分为两种: GateWayFilter(单一)、GlobalFilter(全局)

GateWay Filter提供了丰富的过滤器的使用,单一的有32种,全局的有9种,有兴趣的小伙伴可以了解一下。

官方参考网址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

StripPrefix

StripPrefix 在我们当前请求中,通过规则值去掉某一部分地址,比如我们有一台服务中加入了一个前端nacos-provider想要通过这个去访问,我们在项目cloud-alibaba-nacos-9001中加入 context-path

server:
port: 9001
servlet:
context-path: /nacos-provider

现在9001的访问路径变为http://localhost:9001/nacos-provider/mxn/hello,但是如果我们通过网关去访问路径就会变成http://localhost:9006/mxn/nacos-provider/mxn/hello 这个时候我们通过这个路径去访问是访问不成功的,想要解决这个方法,这个就用到了我们FIlter 中的 StripPrefix

routes:  # 路由
- id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: lb://nacos-provider
predicates: # 断言
- Path=/mxn/** # 匹配对应地址
filters:
- StripPrefix=1 # 去掉地址中的第一部分

我们重新启动9006项目,再去访问

自定义Filter

虽然Gateway给我们提供了丰富的内置Filter,但是实际项目中,自定义Filter的场景非常常见,因此单独介绍下自定义FIlter的使用。

想要实现GateWay自定义过滤器,那么我们需要实现GatewayFilter接口和Ordered接口

@Slf4j
@Component
public class MyFilter implements Ordered, GlobalFilter {
/**
* @param exchange 可以拿到对应的request和response
* @param chain 过滤器链
* @return 是否放行
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取第一个参数
String id = exchange.getRequest().getQueryParams().getFirst("id");
//打印当前时间
log.info("MyFilter 当前请求时间为:"+new Date());
//判断用户是否存在
if(StringUtils.isEmpty(id)){
log.info("用户名不存在,非法请求!");
//如果username为空,返回状态码为407,需要代理身份验证
exchange.getResponse().setStatusCode(HttpStatus.PROXY_AUTHENTICATION_REQUIRED);
// 后置过滤器
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
} /**
* 设定过滤器的优先级,值越小则优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}

当我们访问http://localhost:9006/mxn/nacos-provider/mxn/hello请求,没有携带ID参数,请求失败

当我们访问http://localhost:9006/mxn/nacos-provider/mxn/hello?id=1请求,请求成功

总结

到这里我们的GateWay就讲解完了,对于GateWay的核心点主要有三个Route\Predicate\Filter,我们搞懂了这三点,基本上对于GateWay的知识就掌握的差不多了,GateWay核心的流程就是:路由转发+执行过滤器链,如果对文中有疑问的小伙伴,欢迎留言讨论。

创作不易,如果文中对你有帮助,记得点赞关注,您的支持是我创作的最大动力。

我是牧小农,怕什么真理无穷,进一步有进一步的欢喜~

这篇SpringCloud GateWay 详解,你用的到的更多相关文章

  1. mysql基础篇 - SELECT 语句详解

    基础篇 - SELECT 语句详解         SELECT语句详解 一.实验简介 SQL 中最常用的 SELECT 语句,用来在表中选取数据,本节实验中将通过一系列的动手操作详细学习 SELEC ...

  2. 入木三分学网络第一篇--VRRP协议详解第一篇(转)

    因为keepalived使用了VRRP协议,所有有必要熟悉一下. 虚拟路由冗余协议(Virtual Router Redundancy Protocol,简称VRRP)是解决局域网中配置静态网关时,静 ...

  3. SpringCloud微服务项目实战 - API网关Gateway详解实现

    前面讲过zuul的网关实现,那为什么今天又要讲Spring Cloud Gateway呢?原因很简单.就是Spring Cloud已经放弃Netflix Zuul了.现在Spring Cloud中引用 ...

  4. iOS开发——控制器OC篇&UINavigationController&UITabBarController详解

    UINavigationController&UITabBarController详解 一:UINavigationController 控制器的属性: UINavigationControl ...

  5. vue.js基础知识篇(6):组件详解

    第11章:组件详解 组件是Vue.js最推崇也最强大的功能之一,核心目标是可重用性. 我们把组件代码按照template.style.script的拆分方式,放置到对应的.vue文件中. 1.注册 V ...

  6. Spring之旅第三篇-Spring配置详解

    上一篇学习了IOC的概念并初步分析了实现原理,这篇主要学习Spring的配置,话不多说,让我们开始! 一.Bean元素配置 1.1 基本配置 看一个最基本的bean配置 <bean name=& ...

  7. 实时通讯系列目录篇之SignalR详解

    一. 简单说几句 最早使用SignalR的时候大约是两年前了,记得当时是一个OA中消息的实时提醒,轮询的方式有点耗资源,WebSocket写起来又比较麻烦,最终选择了SignalR,当时是什么版本已经 ...

  8. J2EE学习篇之--Struts1详解

    今天来看一下Struts1的相关知识,其实Struts现在是出名的,每个Web开发者都会知道的,也是现在比较流行的框架,下面就来看一下我们为什么要用Struts框架呢? 摘要 1.建立在mvc这种好的 ...

  9. (转)写的非常好的一篇HTTP协议详解

    原文地址:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html#3931004(推荐看原文,因为原文文章末有更详细链接) 当今 ...

随机推荐

  1. php文件下载服务器代码

    事情的起因 额,平板想下载电脑上的pdf文件,我开启了web服务,局域网下的ipad访问该文件web路径会直接打开该pdf,而不是下载.于是本小白就折腾了一下. 源代码 <?php forceD ...

  2. pip:带你认识一个 Python 开发工作流程中的重要工具

    摘要:许多Python项目使用pip包管理器来管理它们的依赖项.它包含在Python安装程序中,是Python中依赖项管理的重要工具. 本文分享自华为云社区<使用Python的pip管理项目的依 ...

  3. B. Build the Permutation

    题目分析:我们先简单的分析一下这道题是在干什么啊,给我们三个整数n,a,b,问我们能否构造这样的排列使得序列中有a个极大值,b个极小值,能的话就给出任意一种可能的情况,不能的话就输出-1: 其实一开始 ...

  4. SpringMVC踩坑2

    Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exc ...

  5. 牛客网 第十八届浙大城市学院程序设计竞赛(同步赛)J--万万没想到 啦啦啦啦啦

    我觉得我可以继续wa下去(手动魔鬼笑)--------------------------------------------   原题链接:https://ac.nowcoder.com/acm/c ...

  6. 2021.08.03 P1197 星球大战(并查集)

    2021.08.03 P1197 星球大战(并查集) [P1197 JSOI2008]星球大战 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1.可以离线处理.把在线变为离 ...

  7. IDEA打包javaFX及踩坑解决

    开门见山的说,先打包,再说坑. File-->Project Structure --> Artifacts-->(此处点加号)JAR-->From modules with ...

  8. UML 类之间的关系

    统一建模语言(Unified Modeling Language,UML) 作用:对软件系统进行说明 如果说软件系统是一本小说的话,那么 UML 就是将小说动漫化. 也许你看过这本小说,但是时间一长, ...

  9. python数据处理-matplotlib入门(2)-利用随机函数生成变化图形2

    鉴于上一篇中最后三个问题: 1.上述程序是否能进行优化(比如功能相同的) 2.创建三个3个实例,用了3个语句,能否建一个函数,只输入一个数n,就自动创建n个实例?同时,每个实例的num_times随机 ...

  10. 修改本地仓库Maven,设置镜像

    修改本地仓库的位置: 先建立 文件路径: 进入文件夹目录: 就可以看到一个配置文件夹 就在里面修改本地仓库的路径,设置阿里云镜像 添加本地阿里云镜像: 如下图: 内容如下: <mirror> ...