SpringCloud02-Nacos configuration management (hot update, sharing, clustering), Feign remote call (beautiful encapsulation of Http, extraction of feign-api), Gateway service gateway (routing rules, assertion factories, filters, cross-domain issues)

Article directory

0. Learning Objectives

1. Nacos configuration management

In addition to being a registration center, Nacos can also be used for configuration management.

1.1. Unified configuration management

When more and more instances of microservices are deployed, reaching dozens or hundreds, modifying the configuration of microservices one by one will make people crazy and error-prone. We need a unified configuration management solution that can centrally manage the configuration of all instances.

insert image description here

On the one hand, Nacos can centrally manage the configuration, and on the other hand, when the configuration changes, it can notify the microservice in time to realize hot update of the configuration.

1.1.1. Add configuration files in nacos

How to manage configuration in nacos?

Create a new configuration first, and then hand it over to nacos to manage
insert image description here

Then fill in the configuration information in the pop-up form:

insert image description here

ID is actually the name of the configuration file, which needs to be unique and named strictly according to the format. The full name of the suffix
is ​​actually the environment: dev, test, prod

Note: The core configuration of the project needs to be managed by nacos only when the hot update configuration is required. It is better to save some configurations that will not be changed locally in the microservice.

Click Publish to see the newly created configuration file, because it was created under the dev cluster when it was created before, so the configuration is under the dev cluster
insert image description here

Click Details to view detailed information:
insert image description here

In order to be consistent with the course, delete and create a new one under the public cluster:
insert image description here

1.1.2. Pull configuration from microservice

The microservice needs to pull the configuration managed in nacos and merge it with the local application.yml configuration (which is the complete configuration file) to complete the project startup.

The reading order is modified to read the configuration in nacos first, and then read the configuration in application.yml, but the address of nacos is still placed in application.yml, what should I do? That is:

But if application.yml has not been read yet, how do you know the address of nacos?

Therefore, spring introduces a new configuration file: bootstrap.yaml file, which will be read before application.yml , and the process is as follows:

insert image description here

1) Introduce nacos-config dependency

First, in the user-service service, introduce the client dependency of nacos-config:

Q: Why is it user-service?
Answer: Just use the user-service micro-service as an example. You can see the name (userservice-dev.yaml) of the previously created nacos configuration to know that it is the hot update configuration of the user-service micro-service.

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2) Add bootstrap.yaml

Then, add a bootstrap.yaml file in user-service with the following content:

The file name must be bootstrap.yaml. This is a special file. You can tell it is special by looking at the clouds on its icon.
insert image description here

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名

Here, the nacos address will be obtained according to spring.cloud.nacos.server-addr, and then according to

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}As a file id, to read the configuration.

In this example, it is to read userservice-dev.yaml:

insert image description here

  • Note that there are some repeated configurations in nacos, which can be deleted (or commented)
    insert image description here

3) Read nacos configuration

Add business logic to UserController in user-service, read pattern.dateformat configuration:

In the controller and java code, it is very simple to read the yml configuration and inject directly (the application configuration will be created as a bean and managed in the IOC container)

insert image description here

Full code:

package cn.whu.user.web;

import cn.whu.user.pojo.User;
import cn.whu.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    //controller里读取配置很简单 直接注入 (application配置是会创建成bean被放在IOC容器中管理的)
    @Value("${pattern.dateformat}") // 注意这个配置是写在nacos配置中心的,并不在本地 (本地只是在bootstrap里配置了该如何读取他 只有正常读到了,这里才能获取到他)
    private String dateformat;

    // 写一个controller方便查看有没有读取到
    @GetMapping("now") //不写/默认会给你加上/
    public String now(){
    
    
        String value = "格式: " + dateformat;
        DateTimeFormatter pattern = DateTimeFormatter.ofPattern(dateformat);
        String res = "时间: "+LocalDateTime.now().format(pattern);
        return  value + "\n" + res;
    }
}

When visiting the page, you can see the effect:

http://localhost:8081/user/now

insert image description here

  • summary

insert image description here

1.2. Configure hot update

Our ultimate goal is to modify the configuration in nacos, so that the microservice can make the configuration take effect without restarting, that is, configuration hot update .

Browser side, nacos console, modify configuration:
insert image description here
insert image description here

Then click Publish-"Confirm Publishing

Refresh the browser, and there is no hot update, indicating that to achieve hot update, the code needs to be slightly modified, and some configurations must be done

To achieve configuration hot update, two methods can be used:

There are two ways to read yml configuration ( springBoot course content-2.3 yaml data reading ), and there are also two configurations for corresponding hot update configuration

1.2.1. Method 1 (direct @Value injection)

Add annotations to the class where the variable injected by @Value is located @RefreshScope:

insert image description here

Restart the service after adding annotations, modify the yml configuration in nacos, refresh directly without restarting, and find that hot updates can really be achieved

1.2.2. Method 2 (inject into pojo)

Use the @ConfigurationProperties annotation instead of the @Value annotation.

In the user-service service, add a class to read the patternern.dateformat property:

cn.whu.user.config.PatternProperties

package cn.whu.user.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data //提供Get/Set方法 spring就能自动帮我们注入属性值了 (否则私有的,框架也访问不到(业务框架也不可能对你的类进行暴力反射))
@Component //做成spring Bean, IOC容器里有, 那么任何地方就都可以注入他后使用他了
@ConfigurationProperties(prefix = "pattern") // 约定优于配置: 前缀名和属性名拼在一起和配置文件里的配置名一致,就能完成属性的自动注入
public class PatternProperties {
    
    
    private String dateformat;//和nacos的yml配置文件里的名称保持一致
}

insert image description here


Use this class instead of @Value in UserController: (or if there is this class in Bean, just inject this class directly)

Note: Hot update of configuration can be realized without combining @RefreshScope annotation
insert image description here

Full code:

package cn.whu.user.web;

import cn.whu.user.config.PatternProperties;
import cn.whu.user.pojo.User;
import cn.whu.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {
    
    

    @Autowired
    private UserService userService;

    @Autowired
    private PatternProperties patternProperties;

    // 写一个controller方便查看有没有读取到
    @GetMapping("now") //不写/默认会给你加上/
    public String now(){
    
    
        String value = "格式: " + patternProperties.getDateformat();
        DateTimeFormatter pattern = DateTimeFormatter.ofPattern(patternProperties.getDateformat());
        String res = "时间: "+LocalDateTime.now().format(pattern);
        return  value + "\n" + res;
    }
}

Course recommended use method 2: @ConfigurationProperties directly inject configuration into POJO, then @Component is made into a bean, and then directly injected into the POJO, the POJO is automatically hot updated, no need to add other annotations. I feel that both
methods
are not bad

1.3. Configuration sharing

In fact, when the microservice starts, it will go to nacos to read multiple configuration files, for example:

  • [spring.application.name]-[spring.profiles.active].yaml, for example: userservice-dev.yaml

  • [spring.application.name].yaml, for example: userservice.yaml

Does not[spring.application.name].yaml contain environments, so can be shared by multiple environments.
(Simple understanding: there is no environment information in the naming of this configuration file, so no matter which environment, this configuration file will be loaded when the microservice starts)

Let's test the configuration sharing through the case

1) Add an environment sharing configuration

We add a userservice.yaml file in nacos:

insert image description here

insert image description here

Click Publish-"Confirm Release

2) Read shared configuration in user-service

In the user-service service, modify the PatternProperties class (just add a property), and read the newly added property:

insert image description here

In the user-service service, modify UserController and add a method:

insert image description here

@GetMapping("prop")
public PatternProperties prop(){
    
    
    return patternProperties;
}

3) Run two UserApplications, using different profiles

Modify the startup item UserApplication2 and change its profile value:

insert image description here

insert image description here

You can see that the second microservice took the initiative to read, userservice-test.yaml although there is no
insert image description here

The first one is naturally to userservice-dev.yaml read
insert image description here

In this way, the profile used by UserApplication (8081) is dev, and the profile used by UserApplication2 (8082) is test.

Start UserApplication and UserApplication2

Visit http://localhost:8081/user/prop , the result:

insert image description here

Visit http://localhost:8082/user/prop , the result:

insert image description here

It can be seen that both the dev and test environments have read the value of the attribute envSharedValue.

4) Configure the priority of sharing

When nacos and service local have the same attribute at the same time, the priority is divided into high and low:

insert image description here

Configure the same property in the application.yml of IDEA's user-service and continue testing:

pattern:
  name: 本地环境local
  envSharedValue: 本地环境共享值

You can get it by adding more attributes in POJO
insert image description here

The test results are as follows:

insert image description here

Configure in nacos

name is only available locally, so the local value is displayed, but envSharedValue is available locally and in nacos, so it naturally displays the nacos configuration with a higher priority, which also proves the correctness of the above configuration priority

You can also test in detail: write name in userservice.yaml in nacos console, it will overwrite the local name
and then write name in userservice-dev.yaml, it will overwrite the name of userservice.yaml

  • summary
    insert image description here

1.4. Build Nacos cluster

In the Nacos production environment, it must be deployed in a cluster state. For the deployment method, refer to the documents in the pre-course materials:

insert image description here

Link: https://pan.baidu.com/s/1ohYXtEMJIBi6fUG45Vyg3g
Extraction code: v8s5

Or directly refer to this blog: https://blog.csdn.net/hza419763578/article/details/130744500

2. Feign remote call

Let's first look at the code we used to initiate remote calls using RestTemplate:

insert image description here

There are following problems:

• Poor code readability and inconsistent programming experience

• URLs with complex parameters are difficult to maintain

Feign is a declarative http client, official address: https://github.com/OpenFeign/feign

Its role is to help us elegantly implement the sending of http requests and solve the problems mentioned above.

insert image description here

2.1. Feign replaces RestTemplate

The steps to use Fegin are as follows:

1) Introduce dependencies

We introduce the feign dependency in the pom file of the order-service service:

<!-- feign客户端依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

-starter- is the boot starting dependency, automatic assembly

2) Add annotations

Add annotations to the startup class (OrderApplication) of order-service to enable the function of Feign:@EnableFeignClients

insert image description here

3) Write Feign's client

Create a new interface in order-service with the following content:

cn.whu.order.clients.UserClient

package cn.whu.order.clients;

import cn.whu.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userservice")
public interface UserClient {
    
    

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

The @FeignClient annotation will also enable the class to be created as a bean for springIOC container management, so that any java code can be injected and accessed

Compared with the information of the original Url method, we have all the information, and now we only need to call the interface method, which is very convenient.
insert image description here

This client is mainly based on SpringMVC annotationsDeclare information about remote calls,for example:

  • Service name: userservice
  • Request method: GET
  • Request path: /user/{id}
  • Request parameter: Long id
  • Return value type: User

In this way, Feign can help us send http requests without using RestTemplate to send them ourselves.

4) test

Modify the queryOrderById method in the OrderService class in order-service, and use Feign client instead of RestTemplate:

insert image description here

@Service
public class OrderService {
    
    
    @Autowired
    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);
        // 4.返回
        return order;
    }
}

Doesn't it look more elegant.

1. Note that if the nacos cluster is configured before, the user-service registration center may be changed to port 80, which needs to be changed back to 8848 (otherwise they are not maintained in a registration center, how to call each other) user-service bootstrap.yml In server-addr: localhost:8848 # Nacos地址
2, namespace will isolate microservices, and you have to comment out: application.yml of order-service
insert image description here

Access normal
Going a step further, comment out all load balancing configurations:

order-service的application.yml
insert image description here
order-service的OrderApplication
insert image description here

Feign's automatic load balancing is reflected here, and there is no need to add the @LoadBalanced annotation to enable load balancing

Then there is no load balancing configuration in the code, execute again

It is found that load balancing still exists, and it is evenly distributed, feign automatically realizes load balancing (no need to add additional annotations)

insert image description here

But it seems that I still can’t perceive the existence of the cluster. All clusters are configured and commented out. It should be in the default cluster except UserApplication3, but there is no priority to access the microservices of this cluster (default cluster) [Feign does not need to add additional Note to enable load balancing, but if it involves nacos cluster, you still have to add a line of nacos load balancing policy configuration]

eg: Open the configuration of nacos load balancing strategy, you can perceive the cluster, give priority to access the microservices in this cluster (DEFAULT), and then visit http://localhost:8080/order/101
insert image description here
in the browser . Sure enough, the UserApplication3 of the ShangHai cluster does not will be called, and a line of log will not be printed

Ribbon must be integrated in feign, and there is indeed a check
insert image description here

5) Summary

Steps to use Feign:

① Introduce dependency

② Add @EnableFeignClients annotation

③ Write the FeignClient interface

④ Use the method defined in FeignClient instead of RestTemplate

2.2. Custom configuration

Basically, it is enough to use the default configuration of Boot's automatic assembly, but boot allows us to override the default configuration.

Feign can support many custom configurations, as shown in the following table: (just 5 examples, not only these 5)

type effect illustrate
feign.Logger.Level Modify log level Contains four different levels: NONE, BASIC, HEADERS, FULL
feign.codec.Decoder Parser for the response result Parsing the results of http remote calls, such as parsing json strings into java objects
feign.codec.Encoder request parameter encoding Encode request parameters for sending via http requests
feign. Contract Supported Annotation Formats The default is the annotation of SpringMVC
feign. Retryer Failure retry mechanism The retry mechanism for request failure, the default is no, but Ribbon's retry will be used

The bottom layer of feign is the ribbon, which is equivalent to having a failure retry mechanism

Under normal circumstances, the default value is enough for us to use. If you want to customize it, you only need to create a custom @Bean to override the default Bean.

The following uses logs as an example to demonstrate how to customize the configuration.

2.2.1. Configuration file method

Modifying feign's log level based on the configuration file can target a single service:

feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

It is also possible to target all services:

feign:  
  client:
    config: 
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

Add a new configuration at the bottom of the application.yml of the consumer order-service.
insert image description here
After restarting, visit again and check the log

You can see the feign log of the remote call

05-18 16:01:25:642  INFO 113788 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
05-18 16:01:25:642  INFO 113788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
05-18 16:01:25:644  INFO 113788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
05-18 16:01:25:666  INFO 113788 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
05-18 16:01:25:748  INFO 113788 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
05-18 16:01:25:752 DEBUG 113788 --- [nio-8080-exec-1] c.whu.order.mapper.OrderMapper.findById  : ==>  Preparing: select * from tb_order where id = ? 
05-18 16:01:25:760 DEBUG 113788 --- [nio-8080-exec-1] c.whu.order.mapper.OrderMapper.findById  : ==> Parameters: 101(Long)
05-18 16:01:25:768 DEBUG 113788 --- [nio-8080-exec-1] c.whu.order.mapper.OrderMapper.findById  : <==      Total: 1
05-18 16:01:25:771 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] ---> GET http://userservice/user/1 HTTP/1.1
05-18 16:01:25:771 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] ---> END HTTP (0-byte body)
05-18 16:01:25:800 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] <--- HTTP/1.1 200 (27ms)
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] connection: keep-alive
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] content-type: application/json
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] date: Thu, 18 May 2023 08:01:25 GMT
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] keep-alive: timeout=60
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] transfer-encoding: chunked
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] 
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] {
    
    "id":1,"username":"柳岩","address":"湖南省衡阳市"}
05-18 16:01:25:801 DEBUG 113788 --- [nio-8080-exec-1] cn.whu.order.clients.UserClient          : [UserClient#findById] <--- END HTTP (59-byte body)

The log level is divided into four types:

  • NONE: Do not record any log information, which is the default value. (just Feign's log)
  • BASIC: Only record the request method, URL, response status code and execution time (when the http request was sent, when it ended, and how long it took)
  • HEADERS: On the basis of BASIC, the header information of the request and response is additionally recorded
  • FULL: Record details of all requests and responses, including header information, request body, and metadata.

2.2.2. Java code method

You can also modify the log level based on Java code, first declare a class, and then declare a Logger.Level object:

cn.whu.order.config.DefaultFeignConfiguration.java

public class DefaultFeignConfiguration  {
    
    
    @Bean
    public Logger.Level feignLogLevel(){
    
    
        return Logger.Level.BASIC; // 日志级别为BASIC
    }
}

There is no annotation added to this class, and it will definitely not take effect by default. How to make it take effect?
​​There are two ways

If you want to take effect globally , put it in the @EnableFeignClients annotation of the startup class:defaultConfiguration=

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 

insert image description here

Annotate the configuration in the original yml, and test again, and found that there are only BASIC-level Feign logs
insert image description here
insert image description here

If it is locally effective , put it in the corresponding (xxxClient interface) @FeignClient annotation:configuration=

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class) 

insert image description here

Comment out the configuration in OrderApplication,
insert image description here
restart the service test, and find that the BASIC level log is still there
insert image description here

  • summary
    insert image description here

Log level: use FULL for debugging, use NONE, BASIC for normal use

2.3. Feign usage optimization

The bottom layer of Feign initiates http requests and relies on other frameworks. Its underlying client implementation includes:

•URLConnection: default implementation, does not support connection pool (JDK comes with it, performance is very low, every time a new TCP connection is created, 3 handshakes, 4 breakups when released)

• Apache HttpClient: support connection pool

• OKHttp: support connection pool

Therefore, the main means to improve the performance of Feign is to use the connection pool instead of the default URLConnection.

Here we use Apache's HttpClient to demonstrate. (The bottom layer of Spring is also the default)

1) Introduce dependencies

Introduce Apache's HttpClient dependency in the pom file of order-service:

<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

2) Configure the connection pool

Add configuration in application.yml of order-service:

feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

Next, break the point in the loadBalance method in FeignClientFactoryBean: (double-click Shfit to search for FeignClientFactoryBean)

insert image description here

Start the order-service service in Debug mode, you can see the client here, the bottom layer is Apache HttpClient:

insert image description here

insert image description here

  • In summary, Feign's optimization:

    1. Try to use basic as the log level

    2. Use HttpClient or OKHttp instead of URLConnection

    ① Introduce feign-httpClient dependency

    ② The configuration file enables the httpClient function and sets the connection pool parameters

2.4. Best Practices

The so-called best practice refers to the experience summarized in the use process, the best way to use it.

Self-study observation shows that Feign's client is very similar to the controller code of the service provider:

feign client:

insert image description here

UserController:

insert image description here

Is there a way to simplify this repetitive coding?

2.4.1. Inheritance method

The same code can be shared through inheritance:

1) Define an API interface, use the definition method, and make a statement based on SpringMVC annotations.

2) Both the Feign client and the Controller inherit this interface

insert image description here

advantage:

  • Simple
  • code sharing

shortcoming:

  • Service provider and service consumer are tightly coupled

  • The annotation mapping in the parameter list will not be inherited, so the method, parameter list, and annotation must be declared again in the Controller

2.4.2. Extraction method

Extract Feign's Client as an independent module, and put the POJO related to the interface and the default Feign configuration into this module, and provide it to all consumers.

For example, the default configurations of UserClient, User, and Feign are all extracted into a feign-api package, and all microservices can be used directly by referencing this dependency package.

insert image description here

  • summary
    insert image description here

2.4.3. Implementing extraction-based best practices

1) extraction

First create a module named feign-api:

insert image description here

Project structure:
insert image description here

In feign-api, then introduce feign's starter dependency

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Then, the UserClient, User, and DefaultFeignConfiguration written in order-service are all copied to the feign-api project (just copy the package directly)

insert image description here

Note that the user package in UserClient is re-imported, after all, the package structure name has changed

It turns out that the related packages and pojo in the order-service can be directly deleted.
The direct delete in the red box below, don’t feel bad
insert image description here

After the deletion, the OrderService class of order-service reports an error, don’t worry and continue.

2) Use feign-api in order-service

First, delete classes or interfaces such as UserClient, User, and DefaultFeignConfiguration in order-service.

Introduce the dependency of feign-api in the pom file of order-service:

<dependency>
    <groupId>cn.whu.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

Modify all the package import parts related to the above three components in order-service, and change it to import the package in feign-api

1. User in Order.java
2. User and UserClient in OrderService.java

3) Restart the test

After restarting, it was found that the service reported an error:

insert image description here
Compilation does not report an error, indicating that the class exists, and the execution reports an error, saying that the bean corresponding to the class cannot be found, indicating that spring has not scanned the UserClient class at all, so it cannot create an object for it, and it cannot inject the corresponding bean

This is because UserClient is now under the cn.whu.feign.clients package,

The @EnableFeignClients annotation of order-service is under the cn.whu.order package, not in the same package, and UserClient cannot be scanned.
insert image description here
insert image description here
By default, only classes in the cn.whu.order package and its subpackages can be scanned and objects created

springBoot will also scan and import the classes in the jar package. As long as the package structure is consistent with the project structure of this project, it will be scanned. The
package structure is different, so it cannot be scanned automatically. It can only be configured manually and let it scan

4) Solve the problem of scanning package

method one:

Specify the packages that Feign should scan:

@EnableFeignClients(basePackages = "cn.whu.feign.clients")

Method 2:

Specify the Client interface that needs to be loaded:

@EnableFeignClients(clients = {
    
    UserClient.class})

It is recommended to use the second method

3.Gateway service gateway

Spring Cloud Gateway is a new project of Spring Cloud, which is a gateway developed based on Spring 5.0, Spring Boot 2.0 and Project Reactor and other reactive programming and event flow technologies. It aims to provide a simple and effective unified API route management method.

3.1. Why do we need a gateway

Gateway is the gatekeeper of our services, the unified entrance of all microservices.

The core functional characteristics of the gateway :

  • request routing
  • access control
  • Limiting

Architecture diagram:

insert image description here

Access control : As the entry point of microservices, the gateway needs to verify whether the user is eligible for the request, and intercept it if not.

Routing and load balancing : All requests must first pass through the gateway, but the gateway does not process business, but forwards the request to a microservice according to certain rules. This process is called routing. Of course, when there are multiple target services for routing, load balancing is also required.

Current limiting : When the request traffic is too high, the gateway will release the request according to the speed that the downstream microservice can accept, so as to avoid excessive service pressure.

There are two types of gateway implementations in Spring Cloud:

  • gateway
  • zul

Zuul is a Servlet-based implementation and belongs to blocking programming. Spring Cloud Gateway is based on WebFlux provided in Spring 5, which is an implementation of responsive programming and has better performance.

  • summary
    insert image description here

3.2. Gateway quick start

Next, we will demonstrate the basic routing function of the gateway. The basic steps are as follows:

  1. Create a SpringBoot project gateway and introduce gateway dependencies
  2. Write startup class
  3. Write basic configuration and routing rules
  4. Start the gateway service for testing

1) Create a gateway service and introduce dependencies

Create a service:

insert image description here
insert image description here

Import dependencies:

<!--网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2) Write the startup class

package cn.whu.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

3) Write basic configuration and routing rules

Create an application.yml file with the following content:

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称 (这也是一个微服务而已)
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称  (lb:loadBalance)
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**

insert image description here

We will Pathproxy all requests that match the rules to urithe address specified by the parameter. ( Agents can also configure themselves in yml )

In this example, we proxy /user/**the initial request to, lb://userservicelb is load balancing, pull the service list according to the service name, and realize load balancing.

4) Restart the test

Start GatewayApplication.Main

Restart the gateway, when accessing http://localhost:10010/user/1 , it complies with /user/**the rules, the request is forwarded to uri: http://userservice/user/1, and the result is obtained:

insert image description here

In the same way, you can visit: http://localhost:10010/order/101

insert image description here

5) Flow chart of gateway routing

The entire access process is as follows:

insert image description here

Summarize:

Gateway construction steps:

  1. Create a project, introduce nacos service discovery and gateway dependencies

  2. Configure application.yml, including basic service information, nacos address, routing

Routing configuration includes:

  1. Route id: the unique identifier of the route

  2. Routing destination (uri): the destination address of the routing, http stands for fixed address, lb stands for load balancing based on service name

  3. Routing assertions (predicates): rules for judging routing,

  4. Route filters (filters): process the request or response

Next, focus on learning the detailed knowledge of routing assertions and routing filters

3.3. Assertion Factory

The assertion rules we write in the configuration file are just strings, which will be read and processed by the Predicate Factory and turned into conditions for routing judgments

For example, Path=/user/** is matched according to the path. This rule is determined by

org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactoryclass to

For processing, there are more than a dozen assertion factories like this in SpringCloudGateway: The
11 basic Predicate factories provided by Spring are listed below:

name illustrate example
After is a request after a certain point in time - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before is a request before some point in time - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between is a request before a certain two points in time - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie Requests must contain certain cookies - Cookie=chocolate, ch.p
Header Requests must contain certain headers - Header=X-Request-Id, \d+
Host The request must be to access a certain host (domain name) - Host=.somehost.org,.anotherhost.org
Method The request method must be specified - Method=GET,POST
Path The request path must conform to the specified rules - Path=/red/{segment},/blue/**
Query The request parameters must contain the specified parameters - Query=name, Jack or - Query=name
RemoteAddr The requester's ip must be in the specified range - RemoteAddr=192.168.1.1/24
Weight weight processing

Find out: eg:RemoteAddr can block the IP of foreign network segments, and does not allow foreign access, After can only route the
official routing documents after the specified time point
insert image description here

You can play according to the official Diplay.
insert image description here
After restarting, if you want to access the order-service microservice from the gateway, you will not be able to access it.report 404: http://localhost:10010/order/101
insert image description here
is changed to before, and it can be accessed again
.

But it is still possible to access the user-service microservice from the gateway: http://localhost:10010/user/1
insert image description here

We use the fourth from last one, which is Path
We only need to master the routing engineering of Path .

  • summary
    insert image description here

3.4. Filter Factory

Requests go through the gateway to reach the microservices
So what happens inside the gateway? After the previous study, we know that we must at least go through the route of the assertion factory.
In fact, after routing, you have to pass through the filter

GatewayFilter is a filter provided in the gateway, which can process the requests entering the gateway and the responses returned by microservices:

insert image description here

3.4.1. Types of routing filters

Spring provides 31 different route filter factories. For example:

name illustrate
AddRequestHeader Add a request header to the current request
RemoveRequestHeader Remove a request header from the request
AddResponseHeader Add a response header to the response result
RemoveResponseHeader There is a response header removed from the response result
RequestRateLimiter limit the amount of requests

At present, the official website has been updated to 37 routing filter factories

3.4.2. Request header filter

Let's take AddRequestHeader as an example to explain.

Requirement : Add a request header to all requests entering userservice: Truth=itcast is freaking awesome!

Just modify the application.yml file of the gateway service and add route filtering:

The format is as follows:

- AddRequestHeader=key, value # key是请求头名称   value是具体的值
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/** 
        filters: # 过滤器
        - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头

The current filter is written under the userservice route, so it is only valid for requests to access userservice.

  • Test
    the method of modifying UserController, get the request header parameters and print it: insert image description here
    then access through the gateway: http://localhost:10010/user/1
    Sure enough, the console has output, indicating that the request header has been added successfully
    insert image description here

3.4.3. Default filter

If you want to take effect for all routes, you can write the filter factory under default. The format is as follows:

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
      default-filters: # 默认(全局)过滤项  所有微服务的路由请求都会执行这个过滤器的操作
        - AddRequestHeader=Truth, whu is freaking awesome!! # 添加请求头

insert image description here

Restart GatewayApplication, revisit: http://localhost:10010/user/1, the console can still print:
insert image description here
insert image description here

3.4.4. Summary

What is the role of the filter?

① Process the routing request or response, such as adding a request header

② The filter configured under the route only takes effect for the request of the current route

What is the role of defaultFilters?

① A filter that is effective for all routes

3.5. Global filter

For the filters learned in the previous section, the gateway provides 31 types, but the role of each filter is fixed. If we want to intercept requests and do our own business logic, there is no way to do it.

3.5.1. Global filter function

The role of GatewayFilter is the same as that of the previous defaultFilters, but defaultFilters is configured to perform filtering operations,
while GatewayFilter can flexibly write the logic we want in java code

The role of the global filter is also to process all requests and microservice responses entering the gateway, which is the same as the role of GatewayFilter. The difference is that GatewayFilter is defined through configuration, and the processing logic is fixed; while the logic of GlobalFilter needs to be implemented by writing code yourself.

The way of definition is to implement the GlobalFilter interface.

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

Write custom logic in filter to achieve the following functions:

  • Login Status Judgment
  • permission check
  • Request throttling, etc.

3.5.2. Custom global filter

Requirements: Define a global filter, intercept requests, and determine whether the parameters of the request meet the following conditions:

  • Whether there is authorization in the parameter,

  • Whether the authorization parameter value is admin

If it is satisfied at the same time, let it go, otherwise block it

accomplish:

Define a filter in gateway:

insert image description here

package cn.whu.gateway;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Order(-1) //过滤器优先级 值越小,优先级越高
@Component //做成bean才能生效
public class AuthorizeFilter implements GlobalFilter {
    
    
    @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"); //get方法获取所有  getFirst获取第一个匹配的

        // 3. 判断参数值是否等于 admin
        if("admin".equals(auth)){
    
    
            // 4. 是:放行
            return chain.filter(exchange);
        }
        // 5. 否:拦截
        // 5.1 拦截之前给出提示 提高用户体验 具体实现就是:
        // 设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//401未登陆  已经帮你定义好了各种枚举
        // 5.2 拦截请求
        return exchange.getResponse().setComplete();
    }
}

Restart the gateway microservice-GatewayApplication
access: http://localhost:10010/user/2 is blocked, report 401
Access: http://localhost:10010/user/2?authorization=admin can be accessed normally

  • summary
    insert image description here

3.5.3. Filter execution order

When a request enters the gateway, it will encounter three types of filters: current route filter, DefaultFilter, GlobalFilter

After requesting routing, the current routing filter, DefaultFilter, and GlobalFilter will be merged into a filter chain (collection), and each filter will be executed in turn after sorting:

What are the rules for sorting?

  • Each filter must specify an int type order value, the smaller the order value, the higher the priority, and the higher the execution order .
  • GlobalFilter specifies the order value by implementing the Ordered interface or adding the @Order annotation, which is specified by ourselves
  • The order of routing filters and defaultFilter is specified by Spring, and the default is to increase from 1 according to the order of declaration .
  • When the order values ​​of the filters are the same, they will be executed in the order of defaultFilter > routing filter > GlobalFilter.

The declaration order refers to the order of configuration in the yml configuration file, and the routing filter and defaultFilter each start counting from 1
.
insert image description here

insert image description here

The bottom layer of the default filter and routing filter is GateWayFilter. The
global filter is GlobalFilter, which looks completely different, but there is a GatewayFilterAdapter in FilteringWebHandler.java, which adapts GlobalFilter to GateWayFilter through the adapter mode,
so the essence of all the three filters in the above figure Both of them can be regarded as GateWayFilter , with the same type , so they can be sorted in a collection

For details, you can view the source code:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()The method is to load the defaultFilters first, then load the filters of a certain route, and then merge them.

insert image description here

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()The method will load the global filter, sort according to the order after merging with the previous filter, and organize the filter chain

insert image description here

  • summary
    insert image description here

3.6. Cross-domain issues

3.6.1. What is a cross domain problem

Cross-domain: Inconsistent domain names are cross-domain, mainly including:

  • Different domain names: www.taobao.com and www.taobao.org and www.jd.com and miaosha.jd.com

  • Same domain name, different ports: localhost:8080 and localhost8081

Cross-domain problem: The browser prohibits the originator of the request from making a cross-domain ajax request with the server, and the request is intercepted by the browser

Solution: CORS, this should have been learned before, so I won't repeat it here. Friends who don’t know can check https://www.ruanyifeng.com/blog/2016/04/cors.html

3.6.2. Simulate cross-domain problems

Link: https://pan.baidu.com/s/1ohYXtEMJIBi6fUG45Vyg3g
Extraction code: v8s5

Find the page file for the pre-course materials:

Put it into a web server such as tomcat or nginx, start it and access it. (Note that it is another server, mainly to ensure that url(ip:端口)it cannot be the same)

You can see the following error in the browser console:

insert image description here

Access localhost:10010 from localhost:8090, the port is different, it is obviously a cross-domain request.

3.6.3. Solving cross-domain problems

In the application.yml file of the gateway service, add the following configuration:

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题 (浏览器会先去询问服务器让不让这个请求跨域,这个询问请求默认会被拦截,但是肯定不能被拦截,此行配置true就是不拦截这个询问请求)
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息 (*表示一切请求头)
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

How to test?

No front-end, and port 8080 of tomcat has been occupied, you can create a new boot project and configure different ports to
insert image description here
insert image description here
insert image description here
apply.yml

server:
  port: 8089

Among them, index.hmtl is very simple, which is to send a cross-domain request with ajax

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<pre>
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
  axios.get("http://localhost:10010/user/1?authorization=admin")
  .then(resp => console.log(resp.data))
  .catch(err => console.log(err))
</script>
</html>

Start the web service, visit: http://localhost:8090/

Press F12 and find an error
insert image description here

Then add the configuration in the gateway, restart the gateway, and then visit: http://localhost:8090/

insert image description here
The browser's cross-domain ajax request is successful

Guess you like

Origin blog.csdn.net/hza419763578/article/details/130715725