zoukankan      html  css  js  c++  java
  • spring cloud 声明式rest客户端feign调用远程http服务

    在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。Feign就是Spring Cloud提供的一种声明式REST客户端。可以通过Feign访问调用远端微服务提供的REST接口。现在我们就用Feign来调用SERVICE-HELLOWORLD暴露的REST接口,以获取到“Hello World”信息。在使用Feign时,Spring Cloud集成了Ribbon和Eureka来提供HTTP客户端的负载均衡。

    下面我们就采用Feign的方式来调用服务集群。

    1. 创建Maven工程,加入spring-cloud-starter-feign依赖

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

    完整的pom.xml

    <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>com.pupeiyuan.springcloud</groupId>
            <artifactId>spring-Cloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <artifactId>springcloud-moveServer</artifactId>
    
        <dependencies>
            <!-- 单元测试 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
            </dependency>
            <!-- 支持springWEB web支持: 1、web mvc; 2、restful; 3、jackjson支持; 4、aop ........ -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- AOP依赖模块 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.8.7</version>
            </dependency>
            <!-- jdbc -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <scope>provided</scope>
            </dependency>
    
            <!-- Mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
            <!-- 通用Mapper -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>RELEASE</version>
            </dependency>
            <!-- 分页助手 -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.1.0</version>
            </dependency>
            <dependency>
                <groupId>com.github.jsqlparser</groupId>
                <artifactId>jsqlparser</artifactId>
                <version>0.9.1</version>
            </dependency>
    
            <!-- mysql 数据库驱动. -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!-- 连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.9</version>
            </dependency>
            <!-- jstl -->
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>taglibs</groupId>
                <artifactId>standard</artifactId>
                <version>1.1.2</version>
            </dependency>
            <!-- JSP相关 -->
            <dependency>
                <groupId>com.github.jsqlparser</groupId>
                <artifactId>jsqlparser</artifactId>
                <version>0.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
                <scope>provided</scope>
            </dependency>
            <!-- httpclient -->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
                <scope>true</scope>
            </dependency>
    
            <!-- spring cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-config</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-eureka</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-amqp</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-feign</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- 这是spring boot devtool plugin -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->
                        <fork>true</fork>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    2. 创建启动类,需呀加上@EnableFeignClients注解以使用Feign, 使用@EnableDiscoveryClient开启服务自动发现

    mainApplication.java

    package com.pupeiyuan.config;
    
    
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.support.SpringBootServletInitializer;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.cloud.netflix.feign.EnableFeignClients;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.web.client.RestTemplate;
    
    import feign.Logger;
    
    @Configuration
    //扫描bean
    @ComponentScan(basePackages = "com.pupeiyuan.*")
    //不用自动配置数据源
    @EnableDiscoveryClient
    @SpringBootApplication(exclude=DataSourceAutoConfiguration.class)
    @EnableFeignClients(basePackages="com.pupeiyuan.feignClient")
    public class MainApplication extends SpringBootServletInitializer {
    
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
         //相当于xml中的bean标签 用于调用当前方法获取到指定的对象
        @Bean(name="remoteRestTemplate")
        @LoadBalanced
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(MainApplication.class);
        }
    }

    3. 添加配置文件application-test.properties, 使用端口8085, 名字定义为springcloud-moveServer, 并注册到eureka服务中心

    application-test.properties

    server.port=8085
    server.servlet-path=/
    
    #log4j
    #logging.level.org.springframework=DEBUG
    
    #SpringMVC
    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp
    
    #mybatis && Mapper
    mybatis.configuration.mapUnderscoreToCamelCase=true
    mapper.mappers=com.karle.tk.TkMapper
    mapper.identity=MYSQL
    
    #banner
    banner.charset= UTF-8
    
    #jsp
    server.jsp-servlet.init-parameters.development=true
    
    # pagehelper properties
    pagehelper.offsetAsPageNum=true
    pagehelper.rowBoundsWithCount=true
    pagehelper.pageSizeZero=true
    pagehelper.reasonable=false
    pagehelper.params=pageNum=pageHelperStart;pageSize=pageHelperRows;
    pagehelper.supportMethodsArguments=false
    
    #mq
    spring.rabbitmq.host=localhost
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=guest
    spring.rabbitmq.password=guest
    
    management.security.enabled=false
    logging.level.com.pupeiyuan.feignClient.FeignClient2=DEBUG

    bootstrap.properties

    spring.application.name=moveServer
    spring.cloud.config.discovery.enabled=true
    spring.cloud.config.discovery.service-id=pringcloud-configServer
    spring.cloud.config.profile=test
    spring.cloud.config.label=master
    
    eureka.client.serviceUrl.defaultZone=http://root:123456@peer1:8000/eureka
    eureka.instance.prefer-ip-address=true

    4. 定义Feign:一个用@FeignClient注解的接口类,UserFeignClient.java

    package com.pupeiyuan.feignClient;
    
    import java.util.List;
    
    import org.springframework.cloud.netflix.feign.FeignClient;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import com.pupeiyuan.bean.NhReportStatusHistory;
    
    @FeignClient("MULTIPLE")
    public interface UserFeignClient {
    
          @RequestMapping(value = "/getDate/{id}", method = RequestMethod.GET)
          public List<NhReportStatusHistory> dataList(@PathVariable("id") Long id); // 两个坑:1. @GetMapping不支持   2. @PathVariable得设置value
        
          /*@RequestMapping(value = "/user", method = RequestMethod.POST)
          public User postUser(@RequestBody User user);
        
          // 该请求不会成功,只要参数是复杂对象,即使指定了是GET方法,feign依然会以POST方法进行发送请求。可能是我没找到相应的注解或使用方法错误。
          // 如勘误,请@lilizhou2008  eacdy0000@126.com
          @RequestMapping(value = "/get-user", method = RequestMethod.GET)
          public User getUser(User user);*/
            }

    Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里

    5. 定义一个WebController。

    注入之前通过@FeignClient定义生成的bean, 

    package com.pupeiyuan.controller;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    import com.pupeiyuan.bean.NhReportStatusHistory;
    import com.pupeiyuan.feignClient.FeignClient2;
    import com.pupeiyuan.feignClient.UserFeignClient;
    
    @RestController
    public class FeignClientController {
    
        @Autowired
        private UserFeignClient userFeignClient;
         
        @GetMapping("/movie/{id}")
          public List<NhReportStatusHistory> list(@PathVariable Long id) {
            return this.userFeignClient.dataList(id);
          }
    }

     

    6. 被调用端的WebController--被代理的方法实现

    package com.pupeiyuan.controller;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    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.RestController;
    
    import com.github.pagehelper.PageHelper;
    import com.pupeiyuan.bean.NhReportStatusHistory;
    import com.pupeiyuan.core.DataSourceKey;
    import com.pupeiyuan.core.DynamicDataSourceContextHolder;
    import com.pupeiyuan.services.NhReportService;
    
    @RestController
    @RefreshScope
    public class ConfigClientController {
    
        //services层注入
        @Autowired NhReportService nhReportService;
    
          @GetMapping("/getDate/{id}")
          public List<NhReportStatusHistory> getDate(@PathVariable Long id) {
              //参数容器
                Map<String, Object> params = new HashMap<String, Object>();
                PageHelper.startPage(1, 2);
                DynamicDataSourceContextHolder.set(DataSourceKey.DB_SLAVE1);
                List<NhReportStatusHistory> findList = nhReportService.findList(params);
                return findList;
          }
    }

    7. 启动Feign应用, 访问http://localhost:8085/movie/1, 多次刷新,可以看到和前一章Ribbon里面的应用一样, 两个Hello World服务的输出交替出现。说明通过Feign访问服务, Spring Cloud已经缺省使用了Ribbon负载均衡。

     

    8. 在Feign中使用Apache HTTP Client

    Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址gwai会保持一个长连接,即利用HTTP的persistence connection 。我们可以用Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient依赖:

    复制代码
      <!-- 使用Apache HttpClient替换Feign原生httpclient -->
              <dependency>
                  <groupId>org.apache.httpcomponents</groupId>
                  <artifactId>httpclient</artifactId>
              </dependency>
              <dependency>
                  <groupId>com.netflix.feign</groupId>
                  <artifactId>feign-httpclient</artifactId>
                  <version>${feign-httpclient}</version>
             </dependency>
    复制代码
    然后在application.properties中添加:
    feign.httpclient.enabled=true

     7. Feign的Encoder、Decoder和ErrorDecoder

    Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为Java对象是由解码器(Decoder)完成的。默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。注意,如果在@RequetMapping中的method将请求方式指定为POST,那么所有未标注解的参数将会被忽略,例如:

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
    void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);

    此时因为声明的是GET请求没有请求体,所以obj参数就会被忽略。

    在Spring Cloud环境下,Feign的Encoder只会用来编码没有添加注解的参数。如果你自定义了Encoder, 那么只有在编码obj参数时才会调用你的Encoder。对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理。

  • 相关阅读:
    卡特兰数
    混合运算改进(代码)
    典型用户和场景
    混合运算
    四则运算
    计算
    感想
    git
    附加导航 affix,side--toolbar(可结合博客园使用~)
    对python-rrdtool模块的浅研究。
  • 原文地址:https://www.cnblogs.com/pypua/p/10140096.html
Copyright © 2011-2022 走看看