在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接口抛出业务异常以供调用者处理。
来源:https://www.cnblogs.com/pypua/p/10140096.html