书接上文,之前的代码使用字符串拼接的方式构造我们调用的 URL,目前这个 URL 有一个参数,如果有很多参数,我们就需要构造一个哈希表,URL 上面挂满了&参数连接符
Feign 是 Netflix 开发的声明式、模板化的 HTTP 客户端,帮助我们更加优雅的调用 HTTP API。
集成 Feign
我们再 movie 微服务里面集成 Feign,首先添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.3.5.RELEASE</version> </dependency>
之后我们需要构造一个接口,这个接口是专门处理掉 HTTP API 的
为了方便,这里建一个 feign 包,在这个包下面建立一个接口UserFeignClient
,用来调用用户接口的 feign REST 接口
package cn.ts.ms.movie.feign;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 cn.ts.ms.movie.model.User;@FeignClient(name="MS-SIMPLE-PROVIDER-USER")public interface UserFeignClient { @RequestMapping(value="/user/find?id={id}",method=RequestMethod.GET) public User findById(@PathVariable("id") Integer id); }
说明:@FeignClient(name="MS-SIMPLE-PROVIDER-USER")
标明是向 user 这个未付发起请求的;
在 requestMapping 里面构造了请求路径,合在一起就是 user 微服务的 HTTP 调用;这里的 REST 格式不正确,姑且先走通程序。
接下来在 Controller 里面调用 feign 来进行数据请求
@RestControllerpublic class MovieController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient; @Autowired private UserFeignClient userFeignClient; @GetMapping("/user/{id}") public User findByUserId(@PathVariable Integer id){ //return restTemplate.getForObject("http://MS-SIMPLE-PROVIDER-USER/user/find?id="+id, User.class); return userFeignClient.findById(id); } @GetMapping("/log") public void printLog(){ ServiceInstance instance = loadBalancerClient.choose("MS-SIMPLE-PROVIDER-USER"); System.out.println("Now:"+instance.getServiceId()+"---"+instance.getHost()+":"+instance.getPort()); } }
接下来修改启动类,增加@EnableFeignClients 注解
@SpringBootApplication@EnableEurekaClient@EnableFeignClientspublic class MsSimpleConsumerMovieApplication { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(MsSimpleConsumerMovieApplication.class, args); } }
测试和启动
启动 eureka1和 eureka2以及两个 user,最后启动 movie
从浏览器里面访问
不仅仅如此,我们的请求是分别随机请求到 user 的两个微服务,也就是实现的负载均衡
自定义 Feign
实际应用开发过程中,上面的例子是不能使用的,用户接口需要账号才能访问,而且不同用户访问结果不一样才行
下面就来修改之前的微服务达到这个目的
首先修改用户微服务
增加security 的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
接下来我们就需要配置 security 的配置类,关于 Spring security将在后续介绍,这里直接引入他人的基本配置
package cn.ts.ms.user;import java.util.ArrayList;import java.util.Collection;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;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled=true)public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { //所有请求都需要经过 HTTP Basic 认证 http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); } @Bean public PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); } @Autowired private CustomerUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(this.passwordEncoder()); } @Component class CustomerUserDetailsService implements UserDetailsService { //构造用户,可以查询数据库来完成 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if("user".equals(username)){ return new SecurityUser("user","123","role"); }else if("admin".equals(username)){ return new SecurityUser("admin","123","admin"); } return null; } } class SecurityUser implements UserDetails{ private static final long serialVersionUID = 1L; public SecurityUser() {} public SecurityUser(String name,String password,String role) { this.name=name; this.password=password; this.role=role; } private Integer id; private String name; private String password; private String role; //手机用户的角色列表 @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities=new ArrayList<>(); SimpleGrantedAuthority role=new SimpleGrantedAuthority(this.role); authorities.add(role); return authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.name; } @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.ts.ms.user.controller;import java.util.Collection;import javax.annotation.Resource;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;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.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import cn.ts.ms.user.mapper.UserMapper;import cn.ts.ms.user.model.User;@RestController @RequestMapping("/user") @EnableAutoConfiguration public class UserController { @Resource private UserMapper userMapper; @RequestMapping("/find") public User findUserById(@RequestParam String id){ Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if(principal instanceof UserDetails){ UserDetails userDetails=(UserDetails)principal; Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); for(GrantedAuthority auth:authorities){ System.out.println("当前用户:"+userDetails.getUsername()+" 拥有角色"+auth.getAuthority()); } } System.out.println("=================================="); return userMapper.findUserById(id); } }
在浏览器里面访问 user 微服务接口,看到需要登录
输入 user/123 或者 admin/123 可以进入正常浏览
接下来修改电影微服务
去掉UserFeignClient的注解;
去掉启动类的 EnableFeign的注解
修改 Controller 类如下
package cn.ts.ms.movie.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;import org.springframework.cloud.netflix.feign.FeignClientsConfiguration;import org.springframework.context.annotation.Import;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import cn.ts.ms.movie.feign.UserFeignClient;import cn.ts.ms.movie.model.User;import feign.Client;import feign.Contract;import feign.Feign;import feign.auth.BasicAuthRequestInterceptor;import feign.codec.Decoder;import feign.codec.Encoder;@Import(FeignClientsConfiguration.class)@RestControllerpublic class MovieController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient;// @Autowired// private UserFeignClient userFeignClient; private UserFeignClient userUserFeignClient; private UserFeignClient adminUserFeignClient; @Autowired public MovieController(Decoder decoder,Encoder encoder,Client client,Contract contract){ this.userUserFeignClient=Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("user","123")) .target(UserFeignClient.class,"http://MS-SIMPLE-PROVIDER-USER/"); this.adminUserFeignClient=Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("admin","123")) .target(UserFeignClient.class,"http://MS-SIMPLE-PROVIDER-USER/"); } @GetMapping("/user-user/{id}") public User fingByUserIdWithUser(@PathVariable Integer id){ return this.userUserFeignClient.findById(id); } @GetMapping("/user-admin/{id}") public User fingByUserIdWithAdmin(@PathVariable Integer id){ return this.adminUserFeignClient.findById(id); } // @GetMapping("/user/{id}")// public User findByUserId(@PathVariable Integer id){// //return restTemplate.getForObject("http://MS-SIMPLE-PROVIDER-USER/user/find?id="+id, User.class);// return userFeignClient.findById(id);// } @GetMapping("/log") public void printLog(){ ServiceInstance instance = loadBalancerClient.choose("MS-SIMPLE-PROVIDER-USER"); System.out.println("Now:"+instance.getServiceId()+"---"+instance.getHost()+":"+instance.getPort()); } }
再次访问 movie 的接口,用不同的 URL,可以发现在user 微服务打印的日志不一样http://127.0.0.1:8010/user-user/1
对应的日志当前用户:user 拥有角色role
http://127.0.0.1:8010/user-admin/1
对应的日志当前用户:admin 拥有角色admin
Feign 支持压缩功能,在 yml 配置里面增加对应的配置即可,版本不同配置不一样貌似。
Feign 的多参数问题
1、在接口入参的地方,每个接口参数对应一个请求参数;
2、配置 map 表传递到接口
Post 请求多参数,直接使用@RequestBody 注解类来进行,Feign 接口也同样使用即可
作者:breezedancer
链接:https://www.jianshu.com/p/04218c285a73