SpringCloud-day2-Feign、Gateway

文章目录

    • 一、HTTP客户端Feign
      • 1. Feign代替RestTemplate
      • 2. 自定义配置
      • 3. Feign使用优化
      • 4. 最佳实践
    • 二、Gateway服务网关
      • 1. 为什么需要网关
      • 2. gateway快速入门
      • 3. 断言工厂
      • 4. 过滤器工厂
      • 5. 全局过滤器
      • 6. 过滤器执行顺序
      • 7. 跨域问题

一、HTTP客户端Feign

1. Feign代替RestTemplate

RestTemplate发起远程调用的代码

String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);

存在的问题:

  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护

Feign:是一个声明式客户端,作用是帮我们实现http请求的发送,解决上面提到的问题

定义和使用Feign客户端:

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
  2. 在order-service的启动类添加注解开启Feign的功能

    @SpringBootApplication
    @EnableFeignClients	//开启Feign的功能
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    }
    
  3. 编写Feign客户端

    @FeignClient("user-service")
    public interface UserClient {
    
        @GetMapping("/user/{id}")
        User findById(@PathVariable Long id);
    
    }
    

    主要是基于SpringMVC的注解来声明远程调用的信息,如:

    • 服务名称:userservice
    • 请求方式:GET
    • 请求路径:/user/{id}
    • 请求参数:Long id
    • 返回值类型:User
  4. 调用Feign客户端实现远程调用

    @Service
    public class OrderService {
    
        @Resource
        private OrderMapper orderMapper;
    
        @Autowired
        private UserClient userClient;
    
        public Order queryOrderById(Long orderId) {
            // 1.查询订单
            Order order = orderMapper.findById(orderId);
            //2. 利用Feign发起http请求,查询用户
            User user = userClient.findById(order.getUserId());
            //3. 封装user到order
            order.setUser(user);
            return order;
        }
    
    }
    

2. 自定义配置

Feign运行自定义配置来覆盖默认配置,可修改的配置如下:

SpringCloud-day2-Feign、Gateway_第1张图片

配置Feign日志有两种方式:

  • 方式一:配置文件方式

    • 全局生效:

      feign:
        client:
          config:
            default:	 # 此处为default对所有服务生效
              loggerLevel: FULL
      
    • 局部生效:

      feign:
        client:
          config:
            user-service: # 此处为服务名称只对该服务生效	
              loggerLevel: FULL
      
  • 方式二:通过java代码配置

    首先声明一个bean

    public class FeignClientConfiguration {
    
        @Bean
        public Logger.Level feignLogLevel(){
            return Logger.Level.BASIC;
        }
    
    }
    

    如果是全局配置,则把它放在启动类的@EnableFeignClients注解中

    @SpringBootApplication
    @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    }
    

    如果是局部配置,则把它放到对应client的@FeignClient这个注解中

    @FeignClient(value = "user-service", configuration = FeignClientConfiguration.class)
    public interface UserClient {
    
        @GetMapping("/user/{id}")
        User findById(@PathVariable Long id);
    
    }
    

3. Feign使用优化

Feign底层的客户端实现

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

优化Feign的性能主要包括:

  1. 使用连接池代替默认的URLConnection
  2. 日志级别,最好用basic或者none

Feign添加HttpClient的支持

  • 引入依赖

    <dependency>
        <groupId>io.github.openfeigngroupId>
        <artifactId>feign-httpclientartifactId>
    dependency>
    
  • 配置连接池

    feign:
      httpclient:
        enabled: true   # 开启feign对HttpClient的支持
        max-connections: 200  # 最大的连接数
        max-connections-per-route: 50 # 每个路径的最大连接数
    

4. 最佳实践

  • 方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准

    • 服务紧耦合
    • 父接口参数列表中的映射不会被继承
  • 方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

    1. 首先创建一个module,命名为feign-api,然后引入feign的starter依赖

      <dependency>
          <groupId>org.springframework.cloudgroupId>
          <artifactId>spring-cloud-starter-openfeignartifactId>
      dependency>
      
    2. 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

    3. 在order-service中引入feign-api的依赖

    4. 修改order-service中的所有与上述三个组件有关的import部分,改为导入feign-api中的包

    当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用,有两种解决方法

    • 方式一:指定FeignClient的所在包basePackages = "com.example.clients"

      @SpringBootApplication
      @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class, basePackages = "com.example.clients")
      public class OrderApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(OrderApplication.class, args);
          }
      }
      
    • 方式二:指定FeignClient字节码clients={UserClient.class}

      @SpringBootApplication
      @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class, clients = {UserClient.class})
      public class OrderApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(OrderApplication.class, args);
          }
      }
      

二、Gateway服务网关

1. 为什么需要网关

网关的功能:

  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流

在SpringCloud中网关的实现包括两种:gateway、zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具有更好的性能

2. gateway快速入门

搭建网关服务的步骤:

  1. 创建新的moudle,引入SpringCloudGateway的依赖和Nacos的服务发现依赖

    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-gatewayartifactId>
    dependency>
    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
  2. 编写路由配置及nacos地址

    • 路由id:路由的唯一标识
    • 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
    • 路由断言(predicates):判断路由的规则
    • 路由过滤器(filters):对请求或响应做处理
    server:
      port: 10010 # 网关端口
    spring:
      application:
        name: gateway # 服务名称
      cloud:
        nacos:
          server-addr: localhost:8848 # nacos地址
        gateway:
          routes: # 网关路由配置
            - id: user-service  # 路由id,自定义,唯一
              uri: lb://userservice # 路由的目标地址,lb就是负载均衡,后面跟服务名称
              predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
                - Path=/user/** # 按照路径匹配,只要以/user/开头就符合要求
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
    

3. 断言工厂

  • 在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

  • 例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.predicate.PathRoutePredicateFactory类来处理的

  • 这样的断言工厂在SpringCloudGateway中还有十几个

4. 过滤器工厂

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

例如:AddRequestHeader Gateway Filter Factory

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:	# 局部过滤器,仅对当前路由生效
        - AddRequestHeader=X-Request-red, blue

其中:key是X-Request-red,value是blue,即X-Request-red=blue

如果需要对所有请求都生效,则可以使用default-filters

spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service  # 路由id,自定义,唯一
          uri: lb://userservice # 路由的目标地址,lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 按照路径匹配,只要以/user/开头就符合要求
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
            - Before=2033-01-20T17:42:47.789-07:00[Asia/Shanghai]
      default-filters:	# 默认过滤器,对所有路由都生效
        - AddRequestHeader=Truth, woc666666666

5. 全局过滤器

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样

区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现

定义方式是实现GlobalFilter接口

public interface GlobalFilter {
    /**
     * 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
     * @param exchange 请求上下文,里面可以获取Request、Response等信息
     * @param chain 用来把请求委托给下一个过滤器
     * @return {@code Mono} 返回标示当前过滤器业务街舞
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

例子:定义全局过滤器,拦截并判断用户身份

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足以下条件:

  • 参数中是否有authorization
  • authorization参数值是否为admin

如果同时满足则放行,否则拦截

@Component
public class AuthorizeFilter implements GlobalFilter, Order {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1. 获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        //2. 获取参数中的authorization参数
        String auth = params.getFirst("authorization");
        //3. 判断参数是否等于admin
        if ("admin".equals(auth)){
            //4. 是,放行
            return chain.filter(exchange);
        }
        //5. 否,拦截
        //5.1 设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
}

配置过滤器的优先级有两种方法:

  1. 使用@Order注解,里面的数字越小,过滤器的优先级越高

    @Order(-1)
    @Component
    public class AuthorizeFilter implements GlobalFilter, Order {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //1. 获取请求参数
            ServerHttpRequest request = exchange.getRequest();
            MultiValueMap<String, String> params = request.getQueryParams();
            //2. 获取参数中的authorization参数
            String auth = params.getFirst("authorization");
            //3. 判断参数是否等于admin
            if ("admin".equals(auth)){
                //4. 是,放行
                return chain.filter(exchange);
            }
            //5. 否,拦截
            //5.1 设置状态码
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
    
  2. 实现Ordered接口并重写其中的方法

    @Component
    public class AuthorizeFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //1. 获取请求参数
            ServerHttpRequest request = exchange.getRequest();
            MultiValueMap<String, String> params = request.getQueryParams();
            //2. 获取参数中的authorization参数
            String auth = params.getFirst("authorization");
            //3. 判断参数是否等于admin
            if ("admin".equals(auth)){
                //4. 是,放行
                return chain.filter(exchange);
            }
            //5. 否,拦截
            //5.1 设置状态码
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    
    
        @Override
        public int getOrder() {
            return -1;
        }
    }
    

6. 过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的路由器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order值由Spring指定,默认是按照声明顺序从1递增
  • 当过滤器的order值一样时,会按照defaultFilter >路由过滤器 > GlobalFilter的顺序执行

7. 跨域问题

跨域:域名不一致就是跨域,主要包括:

  • 域名不同:www.taobao.comwww.taobao.orgwww.jd.commiaosha.jd.com
  • 域名相同,端口不同:localhost:8080localhost:8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS

spring:
  application:
    name: gateway # 服务名称
  cloud:
	······
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截的问题
        cors-configurations:
          '[/**]':  # 拦截哪些请求,/**代表拦截一切请求
            allowedOrigins: # 允许哪些网站的跨域请求
              - ""
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 #这次跨域检测的有效期,有效期范围内浏览器将不再发起询问,直接放行

你可能感兴趣的:(学习,spring,cloud,java,spring)