您的位置:首页 > 编程语言 > Java开发

Spring Cloud版——电影售票系统<三>使用Feign实现声明式REST调用

2017-09-11 00:00 1106 查看
GitHub地址:https://github.com/leebingbin/SpringCloud.MovieTicketing

Spring Cloud版——电影售票系统<三>使用Feign实现声明式REST调用

一、Feign简介

Feign是Netflix开发的声明式、模块化的HTTP客户端,其灵感来自Retrofit, JAXRS-2.0以及WebSocket。Feign可帮助我们更好更快的便捷、优雅地调用HTTP API。

在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注释,代码就OK了。Feign 支持多种注释,例如Feign自带的注解或者JAX-RS注解等。

Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和 Eureka,从而让Feign 的使用更加方便。

二、为服务消费者整合Feign

之前的电影微服务是使用RestTemplate(负载均衡通过整合Ribbon实现)调用RESTful API的。现在进一步完善优化项目使用Feign,实现声明式的RESTful API调用。

添加Feign的依赖:



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



Tips: @FeignClient注解中的"movieticketing-provider-user"是一个任意的客户端名称,用于创建Ribbon负载均衡器。由于使用了Eureka ,所以Ribbon会把"movieticketing-provider-user"解析成Eureka Server服务注册表中的服务。当然也可以,使用service.ribbon.listOfServer属性配置;还可以使用,url属性指定请求的URL (URL可以是完整的URL或者主机名),例如@FeignClient(name = "movieticketing-provider-user", url = "http://localhost:8000/")

修改Controller,让其调用Feign接口:



修改启动类,为其添加@EnableFeignClients注解:



三、自定义Feign配置

在Spring Cloud中,Feign的默认配置类是FeignClientsConfiguration, 该类定义了Feign默认使用的编码器、解码器、所使用的契约等。

Spring Cloud 允许通过注解@FeignClient的configuration属性自定义Feign的配置,自定义配置的优先级比FeignClientsConfiguration更高。在Spring Cloud中,Feign默认使用的契约是SpringMvcContract,因此它可以使用Spring MVC的注解。

四、手动创建Feign

为项目添加以下依赖:



创建Spring Security的配置类:

package com.bingbinlee.springcloud.micro.security;

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;

/**
* Spring Security 的配置类
* @author libingbin2015@aliyun.com
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
// 所有的请求,都需要经过HTTP basic认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}

@Bean
public PasswordEncoder passwordEncoder() {
// 明文编码器。这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的。A password encoder that does nothing. Useful for testing where working with plain text
return NoOpPasswordEncoder.getInstance();
}

@Autowired
private CustomUserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService).passwordEncoder(this.passwordEncoder());
}

@Component
class CustomUserDetailsService implements UserDetailsService {
/**
* 模拟两个账户:
* ① 账号是user,密码是user,角色是user-role
* ② 账号是admin,密码是admin,角色是admin-role
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("user".equals(username)) {
return new SecurityUser("user", "user", "user-role");
} else if ("admin".equals(username)) {
return new SecurityUser("admin", "admin", "admin-role");
} else {
return null;
}
}
}

class SecurityUser implements UserDetails {
private static final long serialVersionUID = 1L;

public SecurityUser(String username, String password, String role) {
super();
this.username = username;
this.password = password;
this.role = role;
}

public SecurityUser() {
}

private Long id;
private String username;
private String password;
private String role;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);
authorities.add(authority);
return authorities;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

@Override
public String getPassword() {
return this.password;
}

@Override
public String getUsername() {
return this.username;
}

public Long getId() {
return this.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 this.role;
}

public void setRole(String role) {
this.role = role;
}
}
}

修改Controller,测试打印当前登录的用户信息:

package com.bingbinlee.springcloud.micro.controller;

import com.bingbinlee.springcloud.micro.entity.User;
import com.bingbinlee.springcloud.micro.repository.UserRepository;
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;

/**
* UserController
* @author libingbin2015@aliyun.com
*/
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;

private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

/*  @GetMapping("/{id}")
public User findById(@PathVariable Long id){
User findOne = this.userRepository.findOne(id);
return findOne;
}*/

/**
* 打印当前登录的用户信息
* @author libingbin2015@aliyun.com
*/
@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> collection = user.getAuthorities();
for (GrantedAuthority c : collection) {
// 打印当前登录用户的信息
UserController.LOGGER.info("当前用户是{},角色是{}", user.getUsername(), c.getAuthority());
}
} else {
// do other things
}
User findOne = this.userRepository.findOne(id);
return findOne;
}

}

五、Feign支持继承。使用继承,可将一些公共操作分组到一些父接口中,从而简化Feign的开发。尽管Feign的继承可帮助我们进一步简化Feign的开发,但Spring Cloud官方也指出——通常情况下,不建议在服务器端与客户端之间共享接口,因为这种方式会造成服务器端和客户端代码的紧耦合。并且,Feign本身并不使用Spring MVC的工作机制(方法参数映射不被继承)。但我个人认为,放弃开发的方便性或者接受代码的紧耦合,应该在具体问题下权衡利弊,取其利。

六、Feign对日志的处理非常灵活,可为每个Feign客户端指定日志记录策略,每个Feign客户端都会创建一个logger。默认下,logger的名称是Feign接口的完整类名。但是,Feign的日志打印只会对DEBUG级别做出响应。我们可为每个Feign客户端配置各自的Logger.Level对象,Logger.Level的值有以下选择:

NONE:不记录任何日志(默认值)
BASIC:仅记录请求方法、URL、响应状态代码以及执行时间
HEADERS:记录BASIC级别基础上,记录请求和响应的header
FULL:记录请求和响应的header、body和元数据

编写Feign配置类:



修改Feign接口,指定配置类:



在application.yml中添加以下内容:



七、也可以使用Feign构造多参数请求
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐