一、简介

在微服务中,服务消费者需要请求服务生产者的接口进行消费,可以使用SpringBoot自带的RestTemplate或者HttpClient实现,但是都过于麻烦。

这时,就可以使用Feign了,它可以帮助我们更加便捷、优雅地调用HTTP API。

本文代码全部已上传至我的github,点击这里获取

二、为服务消费者整合Feign

1.复制项目microservice-consumer-movie,并修改为microservice-consumer-movie-feign

2.pom文件添加依赖:

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

3.创建一个Feign接口,并添加@FeignClient注解

 package cn.sp.client;

 import cn.sp.bean.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; /**
* @author ship
* @Description
* @Date: 2018-07-17 13:25
*/
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient { @GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}

@FeignClient注解中的microservice-provider-user是一个任意的客户端名称,用于创建Ribbon负载均衡器。

再这里,由于使用了Eureka,所以Ribbon会把microservice-provider-user解析为Eureka Server服务注册表中的服务。

4.Controller层代码

 /**
* 请求用户微服务的API
* Created by 2YSP on 2018/7/8.
*/
@RestController
public class MovieController { @Autowired
private UserFeignClient userFeignClient; @GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return userFeignClient.findById(id);
}
}

5.修改启动类,添加@EnableFeignClients注解

 /**
* 使用Feign进行声明式的REST Full API调用
*/
@EnableFeignClients(basePackages = {"cn.sp.client"})
@EnableEurekaClient
@SpringBootApplication
public class MicroserviceConsumerMovieFeignApplication { public static void main(String[] args) {
SpringApplication.run(MicroserviceConsumerMovieFeignApplication.class, args);
}
}

这里我添加了basePackages属性指定扫描的包,开始没添加报错了。

这样,电影微服务就可以调用用户微服务的API了。

1.启动microservice-discovery-eureka 

2.启动生产者microservice-provider-user

3.启动microservice-consumer-movie-feign

4.访问http://localhost:8010/user/1获得返回数据。

三、手动创建Feign

1.复制microservice-provider-user并修改artifactId为microservice-provider-user-with-auth

2.添加依赖

 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

3.代码部分

 package cn.sp.conf;

 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; /**
* @author ship
* @Description
* @Date: 2018-07-18 09:51
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired
CustomUserDetailService userDetailService; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(userDetailService).passwordEncoder(this.passwordEncoder());
} @Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//所有请求都需要经过HTTP basic认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
} @Bean
public PasswordEncoder passwordEncoder(){
//明文编码器,这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的
return NoOpPasswordEncoder.getInstance();
} } @Component
class CustomUserDetailService implements UserDetailsService{
/**
* 模拟两个账号:用户名user和用户名admin
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("user".equals(username)){
return new SecurityUser("user","password1","user-role");
}else if ("admin".equals(username)){
return new SecurityUser("admin","password2","admin-role");
}
return null;
}
} class SecurityUser implements UserDetails{ private Long id;
private String username;
private String password;
private String role; public SecurityUser(String username,String password,String role){
this.username = username;
this.password = password;
this.role = role;
} public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public void setUsername(String username) {
this.username = username;
} public void setPassword(String password) {
this.password = password;
} public String getRole() {
return role;
} public void setRole(String role) {
this.role = role;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);
authorities.add(authority);
return authorities;
} @Override
public String getPassword() {
return this.password;
} @Override
public String getUsername() {
return this.username;
} @Override
public boolean isAccountNonExpired() {
return true;
} @Override
public boolean isAccountNonLocked() {
return true;
} @Override
public boolean isCredentialsNonExpired() {
return true;
} @Override
public boolean isEnabled() {
return true;
}
}
 package cn.sp.controller;

 import cn.sp.bean.User;
import cn.sp.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; import java.util.Collection; /**
* Created by 2YSP on 2018/7/8.
*/
@RestController
public class UserController { @Autowired
private UserService userService; private static final Logger log = LoggerFactory.getLogger(UserController.class); @GetMapping("/{id}")
public User findById(@PathVariable Long id){
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails){
UserDetails user = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority g : authorities){
//打印用户当前信息
log.info("当前用户是:{},角色是:{}",user.getUsername(),g.getAuthority());
}
}else {
//do other things
}
return userService.findById(id);
}
}

4.测试修改后的用户服务

先启动microservice-discovery-eureka,再启动microservice-provider-user-with-auth,访问http://localhost:8000/1,即可弹出一个登录框,输入用户名和密码(user/password1和admin/password)才能获取结果。

5.修改电影服务,复制microservice-consumer-movie-feign并改为microservice-consumer-movie-feign-manual

6.去掉Feign接口的@FeignClient注解,去掉启动类的@EnableFeignClients注解

7.controller层代码修改如下。

/**
* 请求用户微服务的API
* Created by 2YSP on 2018/7/8.
*/
@Import(FeignClientsConfiguration.class)
@RestController
public class MovieController { private UserFeignClient userUserFeignClient; private UserFeignClient adminUserFeignClient; @Autowired
public MovieController(Decoder decoder, Encoder encoder, Client client, Contract contract){
this.userUserFeignClient = Feign.builder().client(client).decoder(decoder).encoder(encoder)
.contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("user","password1"))
.target(UserFeignClient.class,"http://microservice-provider-user/"); this.adminUserFeignClient = Feign.builder().client(client).decoder(decoder).encoder(encoder)
.contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("admin","password2"))
.target(UserFeignClient.class,"http://microservice-provider-user/");
} @GetMapping("/user-user/{id}")
public User findByIdUser(@PathVariable Long id){
return userUserFeignClient.findById(id);
} @GetMapping("/user-admin/{id}")
public User findByIdAdmin(@PathVariable Long id){
return adminUserFeignClient.findById(id);
}
}

8.启动microservice-consumer-movie-feign-manual,并访问http://localhost:8010/user-user/4获取结果同时看到用户微服务打印日志。

-- ::06.320  INFO  --- [nio--exec-] cn.sp.controller.UserController          : 当前用户是:user,角色是:user-role

访问http://localhost:8010/user-admin/4打印日志:

-- ::13.772  INFO  --- [nio--exec-] cn.sp.controller.UserController          : 当前用户是:admin,角色是:admin-role

四、Feign对继承的支持

Feign还支持继承,将一些公共操作弄到父接口,从而简化开发

比如,先写一个基础接口:UserService.java

 public interface UserService {
@RequestMapping(method= RequestMethod.GET,value="/user/{id}")
User getUser(@PathVariable("id") long id);
}

服务提供者Controller:UserResource.java

 @RestController
public class UserResource implements UserService { //...
}

服务消费者:UserClient.java

 @FeignClient("users")
public interface UserClient extends UserService {
}

虽然很方便但是官方不建议这样做。

五、Feign对压缩的支持

Feign还可以对传输的数据进行压缩,只需要在appllication.properties文件添加如下配置即可。

feign.compression.request.enable=true
feign.compression.response.enable=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

六、设置Feign的日志

1.复制项目microservice-consumer-movie-feign,修改为microservice-consumer-movie-feign-logging

2.编写Feign配置类

 package cn.sp.conf;

 import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* Created by 2YSP on 2018/7/18.
*/
@Configuration
public class FeignLogConfiguration { /**
* NONE:不记录任何日志(默认)
* BASIC:仅记录请求方法、URL、响应状态代码以及执行时间
* HEADERS:记录BASIC级别的基础上,记录请求和响应的header
* FULL:记录请求和响应的header,body和元数据
* @return
*/
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

3.修改Feign,使用指定配置类

 @FeignClient(name = "microservice-provider-user",configuration = FeignLogConfiguration.class)
public interface UserFeignClient { @GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}

4.在application.yml中添加如下内容,设置日志级别,注意:Feign的日志打印只会对DEBUG级别做出响应

5测试:启动microservice-discovery-eurekamicroservice-provider-usermicroservice-consumer-movie-feign-logging,访问http://localhost:8010/user/1,可看到日志结果。

七、使用Feign构造多参数请求

当我们用Get请求多参数的URL的时候,比如:http://microservice-provider-user/get?id=1&username=zhangsan,可能会采取如下的方式

 @FeignClient(name = "microservice-provider-user")
public interface UserFeignClient { @RequestMapping(value = "/get",method = RequestMethod.GET)
User get0(User user); }

然而会报错,尽管指定了GET方法,Feign仍然会使用POST方法发起请求。

正确处理方式一:使用@RequestParam注解

  @RequestMapping(value = "/get",method = RequestMethod.GET)
User get1(@RequestParam("id") Long id,@RequestParam("username") String username);

但是这种方法也有个缺点,如果参数比较多就要写很长的参数列表。

正确处理方式二:使用map接收

 @RequestMapping(value = "/get",method = RequestMethod.GET)
User get2(Map<String,Object> map);

处理方式三:如果请求方式没有限制的话,换成POST方式

  @RequestMapping(value = "/get",method = RequestMethod.POST)
User get3(User user);

排版比较乱敬请见谅,参考资料:SpringCloud与Docker微服务架构实战。

最新文章

  1. R平方
  2. 哈希 poj 3274
  3. Android利用调试器调试程序
  4. 关于redhat6的服务说明
  5. 【4】JAVA---地址App小软件(UpdatePanel.class)(表现层)
  6. Android中ExpandableListView控件基本使用
  7. octopress command memo
  8. 对比Tornado和Twisted两种异步Python框架
  9. eclipse调试的方法和技巧
  10. C# DataConstruct 数据结构关于 Array,ArrayList,List,HashTable,Dictionnary的学习记录
  11. 南京邮电大学 JavaA期末复习要点总结
  12. JSP动作元素&lt;jsp:include&gt;和&lt;jsp:param&gt;的搭配使用
  13. 元素视差方向移动jQuery插件-类似github 404页面效果
  14. 基于Android的百度地图实现输入地址返回经纬度信息
  15. python代码自动补全配置及Django入门Demo
  16. linux shell脚本编程笔记(五): 重定向
  17. 浅谈 Underscore.js 中 _.throttle 和 _.debounce 的差异[转]
  18. centos7更改主机名
  19. B/S供应链(打印管理)问题答疑
  20. MVC Html.DropDownList 和DropDownListFor 的常用方法

热门文章

  1. Codechef-BLACKCOM(树形背包dp)
  2. 如何扩展ArcGIS中的元数据编辑器
  3. 【转】海量数据处理算法-Bloom Filter
  4. Android切图注意事项
  5. Android NFC近场通信02----读写卡的准备工作
  6. Solidworks drwdot文件如何打开,如何制作Solidworks工程图模板
  7. CF 234 C Weather(粗暴方法)
  8. JIRA运行太慢,修改JVM
  9. Android反复闹钟(每天)的实现
  10. 关于Android中物理按键不响应的可能的一个问题。