为了账号安全,请及时绑定邮箱和手机立即绑定

Springcloud微服务架构-使用 Feign实现声明式 REST 调用

标签:
Spring Cloud

书接上文,之前的代码使用字符串拼接的方式构造我们调用的 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


webp

从浏览器里面访问


webp


不仅仅如此,我们的请求是分别随机请求到 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 微服务接口,看到需要登录


webp


输入 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


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消