SpringCloud第三弹(Feign客户端)

Posted 小LUA

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud第三弹(Feign客户端)相关的知识,希望对你有一定的参考价值。

入职新公司,用的SpringCloud,重新开始学习一下

简介

之前接触Ribbon,可以知道调用微服务的方法是指定地址,然后通过RestTemplate来实现调用,用起来有点别扭,因为跟使用HttpClient来调用http接口的感觉是一样的,完全不是面向接口编程。

 Feign是一个声明性的Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,只需要创建一个接口 + 注释。比如:

@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
    @GetMapping(value = "/stores")
    List<Store> getStores();

    @GetMapping(value = "/stores")
    Page<Store> getStores(Pageable pageable);

    @PostMapping(value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

关于FeignClient注解

public @interface FeignClient {

    /**
     * 可选协议前缀的服务名称。是name的同义词。无论是否提供url,都必须为所有客户端指定名称。可以指定为属性键,例如:${propertyKey}。
     * @return the name of the service with optional protocol prefix
     */
    @AliasFor("name")
    String value() default "";

    /**
     * @deprecated 已废弃,请使用name
     * @return the service id with optional protocol prefix
     */
    @Deprecated
    String serviceId() default "";

    /**
     * 指定bean名称,而不是服务名称
     * @return bean name instead of name if present
     */
    String contextId() default "";

    /**
     * @return value的同义词
     */
    @AliasFor("value")
    String name() default "";

    /**
     * @return the <code>@Qualifier</code> value for the feign client.
     */
    String qualifier() default "";

    /**
     * 服务地址
     * @return 绝对URL或可解析主机名(协议可选)。
     */
    String url() default "";

    /**
     * @return 是否应该解码404,而不是抛出feignexception
     */
    boolean decode404() default false;

    /**
     * 自定义的feign client配置类,可以配置{@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.等等
     * @see FeignClientsConfiguration for the defaults
     * @return list of configurations for feign client
     */
    Class<?>[] configuration() default {};

    /**
     * 指定fallback类。fallback类必须实现由该注解注释的接口,并且必须是一个有效的spring bean。
     * @return fallback class for the specified Feign client interface
     */
    Class<?> fallback() default void.class;

    /**
     * 指定FallbackFactory,FallbackFactory生成的实例必须实现由 @FeignClient 注释的接口,并且必须是一个有效的spring bean。
     * @see feign.hystrix.FallbackFactory for details.
     * @return fallback factory for the specified Feign client interface
     */
    Class<?> fallbackFactory() default void.class;

    /**
     * @return 所有方法级映射使用的路径前缀
     */
    String path() default "";

    /**
     * @return whether to mark the feign proxy as a primary bean. Defaults to true.
     */
    boolean primary() default true;

}

主要这几个:

name和value:服务名称
contextId:bean名称
url:服务地址
configuration:配置类
fallback:容错处理类,需要实现注解了@FeignClient的接口
path:方法级映射使用的路径前缀

正文

建立父工程

next

next

Finish

删除src目录

建立Eureka工程

给父工程添加module

Next

Next

Next

Finish

修改application.properties,加入

spring.application.name=eureka-registry-center

server.port=8888
# 不向注册中心注册自己
eureka.client.register-with-eureka=false
# 自己是注册中心
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

修改启动类,加入 @EnableEurekaServer

启动,并访问 http://localhost:8888/

建立公共模块工程

Next

 Next

Finish

api的pom文件引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

新建实体类

package com.example.cloudapi.entity;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor @Getter @Setter
public class User { public User(Integer id, String name) { this.id = id; this.name = name; } private Integer id; private String name; }

目录结构如下

建立服务提供者工程

Next

Next

Next

Finish

将API的依赖添加进来

<dependency>
    <groupId>com.example</groupId>
    <artifactId>cloud-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

修改application.properties

spring.application.name=service-provider
server.port=8080

eureka.client.service-url.defaultZone=http://localhost:8888/eureka
eureka.instance.instance-id=service-provider
eureka.instance.prefer-ip-address=true

修改启动类,增加  @EnableEurekaClient 注解

建立服务接口

package com.example.cloudprovider.service;

import com.example.cloudapi.entity.User;

import java.util.List;

public interface UserService {

    List<User> getList();
}

服务实现

package com.example.cloudprovider.service.impl;

import com.example.cloudapi.entity.User;
import com.example.cloudprovider.service.UserService;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public List<User> getList() {
        return Arrays.asList(new User(1, "小红"),new User(2, "小明"));
    }
}

Controller

package com.example.cloudprovider.controller;

import com.example.cloudapi.entity.User;
import com.example.cloudprovider.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/list")
    public List<User> getList(){
        return userService.getList();
    }
}

目录结构如下:

启动测试

Eureka里出现新的服务

实际上,如果不配置  eureka.instance.instance-id ,有个默认值【IP:spring.application.name:端口

访问 http://localhost:8080/list

建立服务消费者工程

Next

Next

Next

Finish

将API的依赖添加进来

<dependency>
    <groupId>com.example</groupId>
    <artifactId>cloud-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

api工程,新建Feign接口

package com.example.cloudapi.api;

import com.example.cloudapi.entity.User;
import com.example.cloudapi.fallback.UserServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@FeignClient(name = "SERVICE-PROVIDER" ,fallbackFactory = UserServiceFallbackFactory.class )
public interface UserService {

    @GetMapping("/list")
    List<User> getList();
}

再新建一个fallback接口,作用在于:如果提供者服务不可用,客户端可以返回默认内容

package com.example.cloudapi.fallback;

import com.example.cloudapi.api.UserService;
import com.example.cloudapi.entity.User;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;

@Component
public class UserServiceFallbackFactory implements FallbackFactory<UserService> {
    public UserService create(Throwable throwable) {
        return new UserService() {
            public List<User> getList() {
                return Collections.singletonList(new User(0, "服务提供者异常,使用消费者的降级信息"));
            }
        };
    }
}

api工程目录结构

 

回到Consumer工程

新建Controller

package com.example.cloudconsumer.controller;

import com.example.cloudapi.api.UserService;
import com.example.cloudapi.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/list")
    List<User> getList(){
        return userService.getList();
    }
}

修改 application.properties文件

spring.application.name=service-consumer
server.port=8081

eureka.client.service-url.defaultZone=http://localhost:8888/eureka
eureka.client.register-with-eureka=false

feign.hystrix.enabled=true

修改启动类,增加相关注解

 

启动消费者,访问 http://localhost:8081/list

 如果把服务提供者停掉,再次访问

优化项目结构

如果你细心你会发现,打开IDEA的maven管理是存在多个root的,所以我们修改一下pom文件,让工程更合理一些。

也就是:1. 子工程依赖父工程。2. 公共部分放在parent的pom文件里。优化之后:

parent

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>cloud-parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <!--模块-->
    <modules>
        <module>cloud-api</module>
        <module>cloud-eureka</module>
        <module>cloud-provider</module>
        <module>cloud-consumer</module>
    </modules>

    <!--公共属性-->
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <!--Cloud依赖-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!--构建参数-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-clean-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

API

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-parent</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>


</project>

 

Eureka

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>cloud-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>cloud-eureka</artifactId>
    <name>cloud-eureka</name>
    <description>Demo project for Spring Boot</description>


    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies业余草 SpringCloud教程 | 第三篇: 服务消费者(Feign)(Finchley版本)

史上最简单的SpringCloud教程 | 第三篇: 服务消费者(Feign)

SpringCloud之Feign:REST客户端

Spring Cloud第七篇 | 声明式服务调用Feign

史上最简单的SpringCloud教程 | 第三篇: 服务消费者(Feign)

SpringCloud http客户端Feign -- Feign替代RestTemplate